From f66e89662c091e082bd1d2feb6ac91513ccff273 Mon Sep 17 00:00:00 2001
From: Rafael Cavazin <rafaelcavazin@gmail.com>
Date: Sun, 21 Jul 2013 09:59:00 -0400
Subject: [PATCH] Merge branch 'master' of https://github.com/gitblit/gitblit

---
 src/main/java/com/gitblit/wicket/panels/RepositoriesPanel.html                    |    0 
 src/main/java/com/gitblit/GCExecutor.java                                         |  239 
 src/site/fancybox/fancybox.png                                                    |    0 
 src/main/java/.gitignore                                                          |    1 
 src/main/java/com/gitblit/AccessRestrictionFilter.java                            |    0 
 src/main/java/com/gitblit/client/EditUserDialog.java                              |    0 
 src/main/java/com/gitblit/wicket/GitBlitWebApp.java                               |  199 
 src/main/java/com/gitblit/wicket/pages/UsersPage.html                             |   15 
 src/main/java/com/gitblit/wicket/panels/ReflogPanel.java                          |  309 
 src/site/screenshots/m07.png                                                      |    0 
 src/test/java/com/gitblit/tests/Base64Test.java                                   |    0 
 src/test/java/com/gitblit/tests/GitDaemonStopTest.java                            |   33 
 src/main/java/com/gitblit/utils/CompressionUtils.java                             |    0 
 src/test/java/com/gitblit/tests/MailTest.java                                     |   42 
 src/main/java/com/gitblit/wicket/pages/prettify/lang-lua.js                       |    2 
 src/test/java/de/akquinet/devops/test/ui/view/RepoListView.java                   |    0 
 src/main/distrib/data/groovy/jenkins.groovy                                       |    0 
 src/site/fancybox/fancy_close.png                                                 |    0 
 src/site/screenshots/00c.png                                                      |    0 
 src/main/java/com/gitblit/utils/TimeUtils.java                                    |  353 
 src/main/java/com/gitblit/wicket/pages/prettify/lang-vb.js                        |    2 
 src/site/setup_client.mkd                                                         |   46 
 src/main/java/com/gitblit/RedmineUserService.java                                 |  187 
 src/main/java/com/gitblit/wicket/pages/prettify/lang-scala.js                     |    2 
 src/main/distrib/data/users.conf                                                  |    0 
 src/main/java/com/gitblit/wicket/pages/prettify/run_prettify.js                   |   34 
 src/main/java/com/gitblit/client/BooleanCellRenderer.java                         |    0 
 src/main/java/com/gitblit/wicket/pages/ProjectsPage.java                          |  136 
 src/site/releases.mkd                                                             |  550 
 src/main/java/com/gitblit/GitblitUserService.java                                 |  337 
 src/main/java/com/gitblit/client/EditRepositoryDialog.java                        |  811 
 src/site/fancybox/fancy_title_main.png                                            |    0 
 src/main/distrib/data/groovy/.gitignore                                           |    0 
 src/main/java/com/gitblit/wicket/pages/PatchPage.java                             |   72 
 src/main/resources/bootstrap/font/iconic_fill.woff                                |    0 
 src/main/resources/bootstrap/font/iconic_fill.afm                                 |  170 
 src/site/fancybox/fancy_loading.png                                               |    0 
 src/site/.gitignore                                                               |    0 
 src/test/java/com/gitblit/tests/PermissionsTest.java                              | 2881 ++
 src/main/java/com/gitblit/wicket/pages/RootSubPage.html                           |   26 
 src/main/distrib/data/groovy/protect-refs.groovy                                  |    0 
 src/main/java/com/gitblit/wicket/pages/prettify/lang-matlab.js                    |    6 
 src/main/distrib/win/installService.cmd                                           |   38 
 src/test/java/de/akquinet/devops/test/ui/view/Exp.java                            |    0 
 src/main/java/com/gitblit/wicket/pages/BlobDiffPage.html                          |    0 
 src/main/java/com/gitblit/wicket/pages/FederationRegistrationPage.java            |  100 
 src/main/java/com/gitblit/LuceneExecutor.java                                     |    0 
 src/main/java/com/gitblit/wicket/pages/prettify/lang-mumps.js                     |    2 
 src/main/java/com/gitblit/Launcher.java                                           |  148 
 src/main/java/com/gitblit/utils/ActivityUtils.java                                |  233 
 src/main/java/com/gitblit/wicket/panels/DropDownMenu.java                         |    0 
 src/main/resources/lock_pull_16x16.png                                            |    0 
 src/main/java/welcome_es.mkd                                                      |    0 
 src/main/resources/arrow_up.png                                                   |    0 
 src/main/java/com/gitblit/wicket/pages/TagsPage.html                              |    0 
 src/main/java/com/gitblit/client/GitblitManager.java                              |  458 
 src/main/java/com/gitblit/models/GitNote.java                                     |    0 
 src/main/java/com/gitblit/utils/JGitUtils.java                                    | 1917 +
 src/main/java/com/gitblit/client/PropertiesTableModel.java                        |    0 
 src/main/java/com/gitblit/wicket/pages/EditRepositoryPage.html                    |  113 
 src/main/resources/arrow_off.png                                                  |    0 
 src/main/java/com/gitblit/DownloadZipServlet.java                                 |  218 
 src/main/java/com/gitblit/wicket/pages/ForkPage.java                              |    0 
 src/site/fancybox/fancy_title_left.png                                            |    0 
 src/main/java/com/gitblit/wicket/pages/SummaryPage.html                           |   58 
 src/main/java/com/gitblit/models/IssueModel.java                                  |    0 
 src/site/screenshots/02.png                                                       |    0 
 src/main/java/com/gitblit/wicket/panels/DigestsPanel.java                         |  273 
 src/site/screenshots/10.png                                                       |    0 
 src/main/resources/file_vs_16x16.png                                              |    0 
 src/main/java/com/gitblit/utils/Base64.java                                       |    0 
 src/main/resources/federated_16x16.png                                            |    0 
 src/main/java/login_pt_br.mkd                                                     |    0 
 src/site/setup_clientmenus.mkd                                                    |   48 
 src/test/java/com/gitblit/tests/FanoutServiceTest.java                            |  171 
 src/main/java/com/gitblit/wicket/pages/prettify/lang-xq.js                        |    3 
 src/main/resources/arrow_project.png                                              |    0 
 src/main/java/com/gitblit/utils/ConnectionUtils.java                              |  218 
 src/main/distrib/data/groovy/localclone.groovy                                    |    0 
 src/main/java/com/gitblit/utils/ContainerUtils.java                               |    0 
 src/main/resources/search-icon.png                                                |    0 
 src/main/distrib/win/add-indexed-branch.cmd                                       |   20 
 src/main/java/com/gitblit/wicket/GitBlitWebApp_pl.properties                      |  451 
 src/main/distrib/data/certs/mail.tmpl                                             |    0 
 src/main/resources/shield_16x16.png                                               |    0 
 src/main/java/com/gitblit/wicket/panels/PathBreadcrumbsPanel.java                 |    0 
 src/main/distrib/win/uninstallService.cmd                                         |    0 
 src/main/java/com/gitblit/wicket/GitBlitWebApp_nl.properties                      |  504 
 src/site/screenshots/m00.png                                                      |    0 
 src/main/distrib/data/groovy/blockpush.groovy                                     |    0 
 src/main/java/com/gitblit/wicket/pages/prettify/lang-dart.js                      |    3 
 src/main/java/com/gitblit/wicket/charting/GoogleChart.java                        |  107 
 src/site/siteindex.mkd                                                            |   89 
 src/site/screenshots/m06.png                                                      |    0 
 src/main/java/com/gitblit/wicket/GitblitRedirectException.java                    |    0 
 src/main/java/com/gitblit/wicket/pages/RootPage.html                              |   67 
 src/site/screenshots/09.png                                                       |    0 
 src/main/java/com/gitblit/wicket/pages/GitSearchPage.html                         |   25 
 src/main/java/com/gitblit/wicket/pages/DocsPage.html                              |    0 
 src/site/upgrade_go.mkd                                                           |   42 
 src/main/java/com/gitblit/wicket/pages/BlamePage.java                             |  174 
 src/main/java/com/gitblit/wicket/panels/FilterableRepositoryList.html             |   10 
 src/main/java/com/gitblit/client/splash.png                                       |    0 
 src/test/java/com/gitblit/tests/Issue0271Test.java                                |   76 
 src/main/java/com/gitblit/wicket/pages/ProjectPage.html                           |   57 
 src/site/fancybox/fancy_title_right.png                                           |    0 
 src/main/resources/mail_16x16.png                                                 |    0 
 src/test/java/de/akquinet/devops/GitBlit4UITests.java                             |   25 
 src/main/java/com/gitblit/wicket/pages/prettify/lang-tex.js                       |    1 
 src/main/resources/gitblt2_white.png                                              |    0 
 src/main/java/com/gitblit/wicket/freemarker/templates/FilterableRepositoryList.fm |   19 
 src/main/resources/star_16x16.png                                                 |    0 
 src/main/resources/gitblt-favicon.png                                             |    0 
 src/main/resources/lock_go_16x16.png                                              |    0 
 src/main/resources/bullet_feed.png                                                |    0 
 src/main/java/com/gitblit/wicket/pages/EmptyRepositoryPage_pl.html                |   58 
 src/test/java/de/akquinet/devops/test/ui/view/GitblitPageView.java                |    0 
 src/main/java/com/gitblit/client/RepositoriesTableModel.java                      |    0 
 src/main/java/com/gitblit/wicket/pages/prettify/lang-lisp.js                      |    3 
 src/main/java/com/gitblit/wicket/pages/ChangePasswordPage.java                    |    0 
 src/test/java/com/gitblit/tests/JsonUtilsTest.java                                |   61 
 src/main/java/com/gitblit/utils/SyndicationUtils.java                             |    0 
 src/main/java/com/gitblit/wicket/pages/LuceneSearchPage.html                      |   95 
 src/main/distrib/win/ia64/gitblit.exe                                             |    0 
 src/main/java/com/gitblit/client/DateCellRenderer.java                            |    0 
 src/site/fancybox/jquery.fancybox-1.3.4.pack.js                                   |    0 
 src/main/java/com/gitblit/wicket/pages/RepositoriesPage.java                      |  187 
 src/main/java/com/gitblit/wicket/pages/TicketPage.java                            |   86 
 src/main/java/com/gitblit/wicket/pages/UserPage.java                              |    0 
 src/main/java/com/gitblit/models/Metric.java                                      |    0 
 src/main/java/welcome_zh_CN.mkd                                                   |    0 
 src/main/java/com/gitblit/RpcServlet.java                                         |  348 
 src/main/java/com/gitblit/wicket/pages/CommitPage.html                            |   99 
 src/main/java/com/gitblit/wicket/panels/LinkPanel.html                            |    0 
 src/main/resources/bullet_black.png                                               |    0 
 src/main/distrib/data/groovy/sendmail-html.groovy                                 |  516 
 src/main/java/com/gitblit/client/IndicatorsRenderer.java                          |    0 
 src/main/java/com/gitblit/wicket/panels/CommitLegendPanel.html                    |    0 
 src/main/java/com/gitblit/wicket/pages/ActivityPage.java                          |  222 
 src/main/java/com/gitblit/wicket/pages/ComparePage.html                           |   81 
 src/main/java/com/gitblit/wicket/pages/EmptyRepositoryPage_pt_BR.html             |   55 
 src/site/screenshots/00d.png                                                      |    0 
 src/main/java/com/gitblit/ConfigUserService.java                                  | 1105 
 src/main/java/com/gitblit/fanout/FanoutNioService.java                            |    0 
 src/main/java/com/gitblit/authority/NewCertificateConfig.java                     |    0 
 src/main/java/WEB-INF/weblogic.xml                                                |    9 
 src/main/java/com/gitblit/models/UserPreferences.java                             |   93 
 src/main/java/com/gitblit/wicket/pages/EmptyRepositoryPage_nl.html                |   55 
 src/main/java/com/gitblit/authority/DefaultOidsPanel.java                         |    0 
 src/main/java/com/gitblit/models/PathModel.java                                   |  127 
 src/site/templates/rss.ftl                                                        |    2 
 src/site/fancybox/fancy_shadow_ne.png                                             |    0 
 src/main/resources/bullet_error.png                                               |    0 
 src/test/java/com/gitblit/tests/ActivityTest.java                                 |    0 
 src/main/java/com/gitblit/wicket/pages/EditTeamPage.java                          |  255 
 src/main/java/com/gitblit/wicket/pages/OverviewPage.html                          |   60 
 src/test/java/com/gitblit/tests/GroovyScriptTest.java                             |    0 
 src/test/java/com/gitblit/tests/SyndicationUtilsTest.java                         |    0 
 src/main/java/com/gitblit/wicket/panels/SearchPanel.html                          |    0 
 src/test/java/com/gitblit/tests/MarkdownUtilsTest.java                            |    0 
 src/main/java/com/gitblit/models/GravatarProfile.java                             |    0 
 src/main/resources/fork_16x16.png                                                 |    0 
 src/main/java/com/gitblit/client/SettingCellRenderer.java                         |    0 
 src/main/java/com/gitblit/git/RepositoryResolver.java                             |  113 
 src/site/screenshots/m01.png                                                      |    0 
 src/site/setup_hooks.mkd                                                          |   96 
 src/site/screenshots/11.png                                                       |    0 
 src/main/java/com/gitblit/fanout/FanoutServiceConnection.java                     |    0 
 src/main/java/com/gitblit/models/ForkModel.java                                   |    0 
 src/main/java/com/gitblit/models/UserModel.java                                   |  680 
 src/main/java/com/gitblit/models/RepositoryModel.java                             |  252 
 src/main/resources/fork-black_16x16.png                                           |    0 
 src/main/java/com/gitblit/utils/PatchFormatter.java                               |    0 
 src/main/java/com/gitblit/wicket/panels/TeamsPanel.java                           |    0 
 src/main/resources/clipboard_13x13.png                                            |    0 
 src/site/setup_proxy.mkd                                                          |   91 
 src/main/java/com/gitblit/wicket/GitblitParamUrlCodingStrategy.java               |    0 
 src/main/resources/gitblit.css                                                    | 1480 +
 src/site/fancybox/jquery.fancybox-1.3.4.css                                       |    0 
 src/main/java/com/gitblit/fanout/FanoutClient.java                                |    0 
 src/main/distrib/linux/install-service-centos.sh                                  |    3 
 src/main/resources/folder_star_16x16.png                                          |    0 
 src/main/distrib/data/groovy/thebuggenie.groovy                                   |    0 
 src/main/java/com/gitblit/wicket/pages/TreePage.java                              |  189 
 src/main/java/com/gitblit/authority/X509CertificateViewer.java                    |    0 
 src/main/java/com/gitblit/client/ClosableTabComponent.java                        |    0 
 src/main/resources/bootstrap/font/iconic_fill.eot                                 |    0 
 src/site/faq.mkd                                                                  |  179 
 src/main/resources/arrow_down.png                                                 |    0 
 src/main/java/com/gitblit/wicket/panels/LogPanel.html                             |   32 
 src/test/java/com/gitblit/tests/PushLogTest.java                                  |   41 
 src/main/java/com/gitblit/wicket/panels/HistoryPanel.html                         |    0 
 src/main/java/com/gitblit/wicket/panels/FederationTokensPanel.html                |    0 
 src/main/java/login_nl.mkd                                                        |    0 
 src/main/java/com/gitblit/wicket/panels/CommitHeaderPanel.html                    |    0 
 src/main/java/com/gitblit/wicket/panels/NavigationPanel.java                      |   77 
 src/main/java/com/gitblit/wicket/pages/ReflogPage.html                            |   25 
 src/main/java/com/gitblit/models/TeamModel.java                                   |  370 
 src/main/java/welcome_ko.mkd                                                      |    0 
 src/test/java/com/gitblit/tests/Issue0259Test.java                                |  213 
 src/main/java/com/gitblit/authority/NewClientCertificateDialog.java               |    0 
 build.xml                                                                         | 1870 
 src/main/resources/script_16x16.png                                               |    0 
 src/main/java/com/gitblit/wicket/pages/ForksPage.java                             |  160 
 src/main/java/com/gitblit/wicket/pages/GitSearchPage.java                         |   77 
 src/main/java/com/gitblit/wicket/pages/prettify/prettify.css                      |    1 
 src/main/resources/file_php_16x16.png                                             |    0 
 src/main/java/com/gitblit/JsonServlet.java                                        |  130 
 src/site/administration.mkd                                                       |  200 
 src/main/java/com/gitblit/git/GitDaemonClient.java                                |  131 
 src/main/java/com/gitblit/wicket/pages/prettify/lang-go.js                        |    1 
 src/main/resources/bootstrap/css/bootstrap.css                                    |    0 
 src/main/java/com/gitblit/SparkleShareInviteServlet.java                          |  113 
 src/site/federation.odg                                                           |    0 
 src/main/java/com/gitblit/wicket/panels/RegistrantPermissionsPanel.java           |    0 
 src/main/resources/git-black_210x210.png                                          |    0 
 src/main/java/com/gitblit/utils/CommitCache.java                                  |  270 
 src/main/java/com/gitblit/client/SettingPanel.java                                |    0 
 src/main/resources/bullet_key.png                                                 |    0 
 src/main/java/com/gitblit/wicket/pages/ProjectPage.java                           |  253 
 src/main/java/com/gitblit/wicket/pages/prettify/lang-rd.js                        |    1 
 src/main/java/com/gitblit/LdapUserService.java                                    |  496 
 src/main/distrib/win/x86/gitblit.exe                                              |    0 
 src/main/java/com/gitblit/client/BranchRenderer.java                              |   83 
 src/main/resources/clipboard_16x16.png                                            |    0 
 src/main/java/com/gitblit/client/TeamsTableModel.java                             |    0 
 src/main/java/com/gitblit/wicket/charting/GoogleCharts.java                       |    0 
 src/main/distrib/linux/install-service-ubuntu.sh                                  |    3 
 src/main/java/welcome.mkd                                                         |    0 
 src/main/java/com/gitblit/authority/UserCertificateTableModel.java                |    0 
 src/main/distrib/data/certs/authority.conf                                        |    0 
 src/site/fancybox/fancy_shadow_sw.png                                             |    0 
 src/site/features.mkd                                                             |   82 
 src/main/resources/users_16x16.png                                                |    0 
 src/site/screenshots/01.png                                                       |    0 
 src/main/java/com/gitblit/models/ProjectModel.java                                |    0 
 src/main/resources/cold_16x16.png                                                 |    0 
 src/main/java/com/gitblit/client/RegistrantPermissionsPanel.java                  |    0 
 src/site/fancybox/fancy_shadow_se.png                                             |    0 
 src/main/java/com/gitblit/wicket/panels/TagsPanel.html                            |   51 
 src/site/setup_war.mkd                                                            |   20 
 src/main/distrib/data/clientapps.json                                             |   81 
 src/main/resources/git-black.png                                                  |    0 
 src/main/resources/bootstrap/font/iconic_fill.ttf                                 |    0 
 src/main/java/com/gitblit/models/SubmoduleModel.java                              |    0 
 src/main/java/com/gitblit/utils/ClientLogger.java                                 |    0 
 src/main/java/com/gitblit/wicket/pages/CommitPage.java                            |  233 
 src/main/java/com/gitblit/wicket/pages/EmptyRepositoryPage_es.html                |   58 
 src/main/java/com/gitblit/wicket/panels/CommitLegendPanel.java                    |    0 
 src/site/setup_viewer.mkd                                                         |   47 
 src/main/java/com/gitblit/wicket/pages/ComparePage.java                           |  280 
 src/main/java/com/gitblit/wicket/panels/SearchPanel.java                          |    0 
 src/site/fancybox/jquery.mousewheel-3.0.4.pack.js                                 |    0 
 src/site/screenshots/04.png                                                       |    0 
 src/main/resources/file_settings_16x16.png                                        |    0 
 src/main/java/com/gitblit/utils/GitBlitDiffFormatter.java                         |  178 
 src/main/java/com/gitblit/authority/UserCertificatePanel.java                     |    0 
 src/site/templates/releasecurrent.ftl                                             |   25 
 src/main/java/com/gitblit/git/GitServlet.java                                     |   42 
 src/main/java/com/gitblit/wicket/pages/EditTeamPage.html                          |    0 
 src/main/java/com/gitblit/wicket/pages/OverviewPage.java                          |  152 
 src/main/java/com/gitblit/wicket/SessionlessForm.java                             |    0 
 src/test/java/com/gitblit/tests/TimeUtilsTest.java                                |  111 
 src/main/java/com/gitblit/models/RefLogEntry.java                                 |  360 
 src/main/java/com/gitblit/client/SubscriptionsDialog.java                         |    0 
 src/main/resources/commit_up_16x16.png                                            |    0 
 src/test/java/com/gitblit/tests/IssuesTest.java                                   |    0 
 src/test/java/com/gitblit/tests/DiffUtilsTest.java                                |    0 
 src/main/java/com/gitblit/client/UsersPanel.java                                  |    0 
 src/main/resources/tag_16x16.png                                                  |    0 
 src/main/java/com/gitblit/wicket/panels/PagerPanel.java                           |    0 
 src/main/resources/blank.png                                                      |    0 
 src/main/java/com/gitblit/wicket/pages/BranchesPage.html                          |    0 
 src/main/java/com/gitblit/wicket/panels/BranchesPanel.html                        |   52 
 src/main/java/com/gitblit/client/GitblitWorker.java                               |    0 
 src/main/java/com/gitblit/wicket/panels/UsersPanel.java                           |    0 
 src/main/java/com/gitblit/client/GitblitClient.java                               |    0 
 src/site/screenshots/m09.png                                                      |    0 
 src/main/java/com/gitblit/PagesFilter.java                                        |  126 
 src/main/resources/file_doc_16x16.png                                             |    0 
 src/main/resources/folder_16x16.png                                               |    0 
 src/main/resources/bootstrap/font/iconic_stroke.otf                               |    0 
 src/main/java/com/gitblit/wicket/pages/prettify/lang-ml.js                        |    2 
 src/main/java/com/gitblit/client/EditRegistrationDialog.java                      |    0 
 src/main/java/com/gitblit/client/FeedsTableModel.java                             |    0 
 src/main/java/com/gitblit/wicket/pages/prettify/lang-wiki.js                      |    2 
 src/main/java/com/gitblit/client/StatusPanel.java                                 |    0 
 src/main/java/com/gitblit/client/GitblitRegistration.java                         |    0 
 src/site/openshift.mkd                                                            |    0 
 src/main/java/com/gitblit/FederationClient.java                                   |  143 
 src/main/resources/arrow_page.png                                                 |    0 
 src/main/java/com/gitblit/wicket/pages/MetricsPage.java                           |  192 
 src/main/java/com/gitblit/FileSettings.java                                       |    0 
 src/main/java/com/gitblit/client/RepositoriesPanel.java                           |    0 
 src/main/java/com/gitblit/utils/IssueUtils.java                                   |    0 
 src/site/fancybox/fancy_shadow_w.png                                              |    0 
 src/site/permissions_matrix.ods                                                   |    0 
 src/main/resources/bug_16x16.png                                                  |    0 
 src/main/java/com/gitblit/GitBlitException.java                                   |    0 
 src/main/java/com/gitblit/FederationPullExecutor.java                             |  523 
 src/main/java/com/gitblit/models/FederationModel.java                             |    0 
 .classpath                                                                        |  106 
 src/main/java/com/gitblit/Constants.java                                          |  494 
 src/main/java/com/gitblit/utils/FederationUtils.java                              |    0 
 src/main/java/com/gitblit/utils/DeepCopier.java                                   |    0 
 src/site/fancybox/fancy_shadow_n.png                                              |    0 
 src/main/resources/rosette_16x16.png                                              |    0 
 src/main/distrib/linux/service-centos.sh                                          |    0 
 src/main/java/com/gitblit/wicket/pages/BasePage.java                              |  437 
 src/main/java/com/gitblit/wicket/pages/TagPage.java                               |  107 
 src/test/java/com/gitblit/tests/GitBlitSuite.java                                 |  256 
 src/main/resources/file_world_16x16.png                                           |    0 
 src/test/java/com/gitblit/tests/GitDaemonTest.java                                |  312 
 src/main/java/com/gitblit/fanout/FanoutService.java                               |    0 
 src/main/java/com/gitblit/GitblitTrustManager.java                                |    0 
 src/main/java/com/gitblit/authority/AuthorityWorker.java                          |    0 
 src/main/java/com/gitblit/authority/NewSSLCertificateDialog.java                  |    0 
 src/main/java/com/gitblit/wicket/pages/RepositoryPage.html                        |   68 
 src/main/java/com/gitblit/wicket/pages/LogPage.html                               |   25 
 src/site/fancybox/fancy_shadow_e.png                                              |    0 
 src/main/java/com/gitblit/wicket/panels/FilterableProjectList.java                |  139 
 src/main/resources/health_16x16.png                                               |    0 
 src/main/java/welcome_pt_br.mkd                                                   |    0 
 src/main/java/com/gitblit/git/GitDaemon.java                                      |  350 
 src/test/java/com/gitblit/tests/mock/MemorySettings.java                          |    0 
 src/main/resources/file_java_16x16.png                                            |    0 
 src/main/java/com/gitblit/utils/JsonUtils.java                                    |  345 
 src/main/java/com/gitblit/wicket/ng/NgController.java                             |   80 
 src/site/setup_lucene.mkd                                                         |   50 
 src/main/java/com/gitblit/wicket/pages/ReviewProposalPage.java                    |    0 
 src/main/java/login_pl.mkd                                                        |    0 
 src/site/fancybox/jquery.fancybox-1.3.4.js                                        |    0 
 src/test/java/com/gitblit/tests/RedmineUserServiceTest.java                       |    0 
 src/main/java/com/gitblit/wicket/GitBlitWebApp.properties                         |  504 
 src/main/java/com/gitblit/models/AnnotatedLine.java                               |   54 
 src/main/java/com/gitblit/wicket/GitBlitWebApp_es.properties                      |  504 
 src/main/java/com/gitblit/wicket/panels/ProjectRepositoryPanel.java               |  196 
 src/test/java/com/gitblit/tests/LuceneExecutorTest.java                           |  172 
 src/main/resources/bootstrap/font/iconic_fill.svg                                 |  539 
 src/main/java/com/gitblit/wicket/pages/DashboardPage.java                         |  244 
 src/main/resources/bootstrap/js/jquery.js                                         |    0 
 src/site/resources/architecture.png                                               |    0 
 src/main/config/checkstyle.xml                                                    |    0 
 src/main/java/com/gitblit/utils/FileUtils.java                                    |    0 
 src/main/java/com/gitblit/wicket/panels/RefsPanel.html                            |    0 
 src/test/java/com/gitblit/tests/ArrayUtilsTest.java                               |    0 
 src/main/java/com/gitblit/wicket/pages/GravatarProfilePage.html                   |   22 
 src/main/java/com/gitblit/wicket/panels/ShockWaveComponent.java                   |    0 
 releases.moxie                                                                    |  920 
 src/main/java/com/gitblit/wicket/pages/PatchPage.html                             |    0 
 src/main/resources/file_excel_16x16.png                                           |    0 
 src/main/resources/bootstrap/img/glyphicons-halflings-white.png                   |    0 
 src/main/java/com/gitblit/wicket/pages/MarkdownPage.java                          |   81 
 src/main/resources/file_c_16x16.png                                               |    0 
 src/main/resources/gitblt2.png                                                    |    0 
 src/test/java/com/gitblit/tests/RepositoryModelTest.java                          |    0 
 src/main/java/com/gitblit/utils/ObjectCache.java                                  |   98 
 src/main/distrib/data/groovy/fogbugz.groovy                                       |    0 
 src/site/resources/ldapSample.png                                                 |    0 
 src/site/screenshots/03.png                                                       |    0 
 src/main/java/com/gitblit/client/JPalette.java                                    |    0 
 src/site/setup_go.mkd                                                             |  139 
 src/main/java/com/gitblit/wicket/pages/MyDashboardPage.java                       |  271 
 src/main/java/com/gitblit/wicket/panels/FederationProposalsPanel.html             |    0 
 src/main/resources/bootstrap/font/iconic_fill.css                                 |    1 
 src/main/java/com/gitblit/models/Activity.java                                    |  163 
 src/site/design.mkd                                                               |   85 
 src/main/java/com/gitblit/wicket/pages/HistoryPage.java                           |   73 
 src/main/java/com/gitblit/wicket/pages/SummaryPage.java                           |  215 
 src/main/resources/git-orange-16x16.png                                           |    0 
 src/site/releasehistory.mkd                                                       |  488 
 src/site/fancybox/blank.gif                                                       |    0 
 src/main/java/login_zh_CN.mkd                                                     |    0 
 src/main/java/com/gitblit/models/SettingModel.java                                |    0 
 src/main/java/com/gitblit/wicket/pages/EditRepositoryPage.java                    |  725 
 src/main/java/com/gitblit/wicket/pages/prettify/lang-n.js                         |    4 
 src/main/java/com/gitblit/client/SettingsPanel.java                               |    0 
 src/main/java/com/gitblit/wicket/pages/SendProposalPage.html                      |    0 
 src/main/java/com/gitblit/wicket/pages/EmptyRepositoryPage.html                   |   55 
 src/main/resources/lock_16x16.png                                                 |    0 
 src/main/resources/github_32x32.png                                               |    0 
 src/site/screenshots/00b.png                                                      |    0 
 src/main/distrib/data/certs/instructions.tmpl                                     |    0 
 src/main/java/com/gitblit/authority/GitblitAuthority.java                         |  923 
 src/main/resources/settings_16x16.png                                             |    0 
 src/main/java/com/gitblit/client/GitblitPanel.java                                |    0 
 src/main/java/com/gitblit/client/Utils.java                                       |    0 
 src/main/java/com/gitblit/wicket/panels/RepositoryUrlPanel.java                   |  470 
 src/main/java/com/gitblit/wicket/panels/FederationRegistrationsPanel.html         |    0 
 src/main/java/com/gitblit/authority/UserOidsPanel.java                            |   95 
 src/main/java/com/gitblit/RobotsTxtServlet.java                                   |    0 
 src/main/java/com/gitblit/wicket/pages/RawPage.java                               |  173 
 src/test/java/com/gitblit/tests/MetricUtilsTest.java                              |    0 
 src/main/java/com/gitblit/client/HeaderPanel.java                                 |    0 
 src/main/java/com/gitblit/wicket/GitBlitWebApp_pt_BR.properties                   |  447 
 src/main/java/com/gitblit/wicket/pages/prettify/lang-apollo.js                    |    2 
 src/main/java/com/gitblit/wicket/pages/LogoutPage.java                            |   50 
 src/site/rpc.mkd                                                                  |  295 
 src/site/screenshots/m08.png                                                      |    0 
 src/main/java/com/gitblit/wicket/pages/CommitDiffPage.html                        |   44 
 src/main/java/welcome_pl.mkd                                                      |    0 
 src/main/java/com/gitblit/wicket/charting/SecureChart.java                        |  633 
 build.moxie                                                                       |  160 
 src/main/resources/bullet_blue.png                                                |    0 
 src/main/java/com/gitblit/client/UsersTableModel.java                             |    0 
 src/main/java/com/gitblit/wicket/pages/FederationPage.html                        |   17 
 src/main/java/com/gitblit/wicket/pages/prettify/lang-clj.js                       |   18 
 src/main/distrib/win/amd64/gitblit.exe                                            |    0 
 src/main/java/com/gitblit/wicket/panels/FilterableRepositoryList.java             |  154 
 src/site/resources/stjude_150x150.gif                                             |    0 
 src/main/java/com/gitblit/models/TicketModel.java                                 |    0 
 src/main/resources/bootstrap/font/iconic_stroke.woff                              |    0 
 src/main/java/com/gitblit/models/UserRepositoryPreferences.java                   |   40 
 src/main/java/com/gitblit/wicket/panels/LinkPanel.java                            |  116 
 src/main/distrib/data/gitblit.properties                                          | 1513 +
 src/main/java/com/gitblit/wicket/pages/ActivityPage.html                          |   25 
 src/test/java/com/gitblit/tests/X509UtilsTest.java                                |    0 
 src/main/java/com/gitblit/wicket/pages/LogoutPage.html                            |   33 
 src/main/java/com/gitblit/wicket/panels/BulletListPanel.html                      |    0 
 src/main/java/com/gitblit/wicket/pages/RootPage.java                              |  591 
 src/main/java/com/gitblit/wicket/panels/RefsPanel.java                            |  179 
 src/main/java/com/gitblit/git/GitblitReceivePackFactory.java                      |  103 
 src/main/java/com/gitblit/wicket/pages/UserPage.html                              |   51 
 src/main/java/com/gitblit/wicket/pages/LogPage.java                               |   72 
 src/main/java/com/gitblit/wicket/pages/ReviewProposalPage.html                    |    0 
 src/main/java/com/gitblit/wicket/panels/LogPanel.java                             |  176 
 src/main/java/com/gitblit/models/FeedModel.java                                   |    0 
 src/main/java/com/gitblit/utils/GitWebDiffFormatter.java                          |    0 
 src/main/java/com/gitblit/wicket/PageRegistration.java                            |  243 
 src/main/java/com/gitblit/LogoServlet.java                                        |   96 
 src/main/resources/star_32x32.png                                                 |    0 
 src/main/java/com/gitblit/wicket/pages/LuceneSearchPage.java                      |    0 
 src/main/java/com/gitblit/wicket/pages/prettify/lang-hs.js                        |    2 
 src/main/java/com/gitblit/wicket/pages/RepositoriesPage.html                      |   16 
 src/main/resources/file_ppt_16x16.png                                             |    0 
 src/main/java/login.mkd                                                           |    0 
 src/site/screenshots/14.png                                                       |    0 
 src/main/resources/file_16x16.png                                                 |    0 
 src/site/templates/macros.ftl                                                     |  147 
 src/main/java/com/gitblit/IUserService.java                                       |  325 
 src/site/screenshots/m03.png                                                      |    0 
 src/site/custom.less                                                              |   94 
 README.markdown                                                                   |    7 
 src/site/upgrade_express.mkd                                                      |   14 
 src/main/resources/bootstrap/font/iconic_stroke.ttf                               |    0 
 src/site/setup_express.mkd                                                        |   56 
 src/test/config/test-users.conf                                                   |    0 
 src/site/roadmap.mkd                                                              |   17 
 src/site/fancybox/jquery.easing-1.3.pack.js                                       |    0 
 src/main/java/com/gitblit/utils/ArrayUtils.java                                   |    0 
 src/main/java/com/gitblit/wicket/pages/ReflogPage.java                            |   58 
 src/main/java/com/gitblit/wicket/pages/TicketsPage.java                           |   72 
 src/main/java/com/gitblit/wicket/panels/BasePanel.java                            |  105 
 src/main/resources/bootstrap/font/iconic_stroke.css                               |    1 
 src/main/resources/commit_merge_16x16.png                                         |    0 
 src/main/java/com/gitblit/wicket/panels/FederationTokensPanel.java                |    0 
 src/test/java/de/akquinet/devops/test/ui/view/GitblitDashboardView.java           |    0 
 src/site/federation.mkd                                                           |  339 
 src/main/java/com/gitblit/wicket/charting/GooglePieChart.java                     |   86 
 src/main/java/com/gitblit/wicket/pages/EditUserPage.java                          |  266 
 src/site/resources/fed_aggregation.png                                            |    0 
 src/main/java/com/gitblit/authority/CertificateStatusRenderer.java                |    0 
 src/main/distrib/linux/java-proxy-config.sh                                       |    0 
 src/main/java/welcome_nl.mkd                                                      |    0 
 src/main/java/com/gitblit/wicket/pages/prettify/lang-proto.js                     |    1 
 src/test/java/de/akquinet/devops/test/ui/generic/AbstractUITest.java              |    0 
 src/site/releasecurrent.mkd                                                       |   67 
 src/test/java/com/gitblit/tests/RpcTests.java                                     |  390 
 src/site/screenshots/06.png                                                       |    0 
 src/test/java/com/gitblit/tests/LdapUserServiceTest.java                          |    0 
 src/main/java/com/gitblit/wicket/pages/HistoryPage.html                           |   25 
 src/main/java/com/gitblit/wicket/pages/EmptyRepositoryPage_zh_CN.html             |   57 
 src/main/java/com/gitblit/DownloadZipFilter.java                                  |    0 
 src/main/java/com/gitblit/wicket/pages/SessionPage.java                           |   69 
 src/test/java/com/gitblit/tests/StringUtilsTest.java                              |    0 
 src/main/java/com/gitblit/wicket/panels/FederationRegistrationsPanel.java         |    0 
 src/main/java/com/gitblit/client/SubscribedRepositoryRenderer.java                |    0 
 src/main/resources/bullet_orange.png                                              |    0 
 src/site/resources/permissions_matrix.png                                         |    0 
 src/main/java/com/gitblit/wicket/pages/prettify/lang-r.js                         |    2 
 src/test/java/com/gitblit/tests/TicgitUtilsTest.java                              |    0 
 src/main/java/com/gitblit/models/FederationSet.java                               |    0 
 src/main/java/logo.png                                                            |    0 
 src/site/properties.mkd                                                           |    0 
 src/main/java/com/gitblit/wicket/pages/BlobPage.java                              |  224 
 src/site/templates/releasehistory.ftl                                             |   21 
 src/main/java/com/gitblit/SyndicationFilter.java                                  |    0 
 src/main/java/com/gitblit/wicket/panels/NavigationPanel.html                      |    0 
 src/main/java/com/gitblit/wicket/panels/CommitHeaderPanel.java                    |   49 
 src/main/java/com/gitblit/SyndicationServlet.java                                 |    0 
 src/main/java/com/gitblit/models/RepositoryCommit.java                            |  130 
 src/main/java/com/gitblit/wicket/pages/UsersPage.java                             |    0 
 src/test/java/de/akquinet/devops/test/ui/cases/UI_MultiAdminSupportTest.java      |   93 
 src/main/java/com/gitblit/WindowsUserService.java                                 |  194 
 src/main/java/com/gitblit/utils/TicgitUtils.java                                  |    0 
 src/main/java/com/gitblit/wicket/panels/RepositoriesPanel.java                    |  560 
 src/main/resources/gitblt_25_white.png                                            |    0 
 src/main/java/com/gitblit/authority/CertificatesTableModel.java                   |  169 
 src/main/java/com/gitblit/wicket/pages/prettify/lang-erlang.js                    |    2 
 src/main/resources/tower_32x32.png                                                |    0 
 src/main/java/com/gitblit/wicket/panels/GravatarImage.html                        |    0 
 src/test/java/com/gitblit/tests/resources/ldapUserServiceSampleData.ldif          |    0 
 src/test/resources/issue0259.conf                                                 |   32 
 src/main/java/com/gitblit/wicket/freemarker/templates/FilterableProjectList.fm    |   15 
 src/main/java/com/gitblit/fanout/FanoutConstants.java                             |    0 
 src/site/setup_authentication.mkd                                                 |  125 
 src/main/java/com/gitblit/client/FeedEntryTableModel.java                         |    0 
 src/main/java/com/gitblit/wicket/pages/EmptyRepositoryPage_ko.html                |   59 
 src/main/java/com/gitblit/models/ServerSettings.java                              |    0 
 src/site/resources/screenshots.js                                                 |    0 
 src/main/resources/folder_star_32x32.png                                          |    0 
 src/main/resources/file_code_16x16.png                                            |    0 
 src/main/java/com/gitblit/wicket/pages/prettify/lang-llvm.js                      |    1 
 src/site/screenshots/15.png                                                       |    0 
 src/main/distrib/linux/gitblit.sh                                                 |    2 
 src/main/java/com/gitblit/wicket/pages/prettify/prettify.js                       |   30 
 src/site/fancybox/fancy_nav_left.png                                              |    0 
 src/main/java/com/gitblit/models/GitClientApplication.java                        |   87 
 src/main/java/com/gitblit/wicket/GitBlitWebSession.java                           |  165 
 src/main/java/com/gitblit/client/TeamsPanel.java                                  |    0 
 src/main/resources/background.png                                                 |    0 
 src/main/distrib/data/groovy/sendmail.groovy                                      |    0 
 src/main/java/com/gitblit/fanout/FanoutSocketService.java                         |    0 
 src/main/distrib/federation.properties                                            |   83 
 src/site/fancybox/fancy_title_over.png                                            |    0 
 src/main/java/login_ko.mkd                                                        |    0 
 src/main/resources/sourcetree_32x32.png                                           |    0 
 src/site/fancybox/fancybox-x.png                                                  |    0 
 src/main/java/com/gitblit/utils/ByteFormat.java                                   |    0 
 src/main/resources/bootstrap/font/iconic_stroke.svg                               |  553 
 src/main/java/com/gitblit/models/FeedEntryModel.java                              |    0 
 src/main/java/com/gitblit/wicket/panels/ActivityPanel.java                        |  149 
 src/main/resources/git-black_32x32.png                                            |    0 
 src/site/screenshots/00.png                                                       |    0 
 src/main/java/com/gitblit/MailExecutor.java                                       |  226 
 src/main/java/com/gitblit/wicket/pages/ProjectsPage.html                          |   37 
 src/main/resources/commit_join_16x16.png                                          |    0 
 .gitignore                                                                        |   57 
 src/main/java/com/gitblit/wicket/pages/prettify/lang-tcl.js                       |    3 
 src/main/java/com/gitblit/wicket/panels/CompressedDownloadsPanel.java             |   78 
 src/main/java/com/gitblit/wicket/pages/BlobDiffPage.java                          |   93 
 src/main/java/com/gitblit/wicket/CacheControl.java                                |   40 
 src/main/java/com/gitblit/authority/Launcher.java                                 |  165 
 src/main/distrib/win/gitblitw.exe                                                 |    0 
 src/main/java/com/gitblit/PagesServlet.java                                       |  261 
 src/main/java/com/gitblit/wicket/pages/TicketPage.html                            |    0 
 src/main/java/com/gitblit/wicket/pages/ForkPage.html                              |    0 
 src/site/fancybox/fancy_shadow_nw.png                                             |    0 
 src/main/java/com/gitblit/authority/CertificateStatus.java                        |    0 
 src/main/java/com/gitblit/wicket/panels/DropDownMenu.html                         |    0 
 src/main/resources/user_16x16.png                                                 |    0 
 src/main/java/com/gitblit/wicket/pages/ChangePasswordPage.html                    |    0 
 src/main/resources/bootstrap/css/iconic.css                                       |  567 
 src/test/resources/issue0271.conf                                                 |   20 
 src/main/resources/git-black-16x16.png                                            |    0 
 src/test/config/test-ui-users.conf                                                |    0 
 src/test/java/de/akquinet/devops/LaunchWithUITestConfig.java                      |  126 
 src/main/java/com/gitblit/IStoredSettings.java                                    |  344 
 src/main/java/com/gitblit/fanout/FanoutStats.java                                 |    0 
 src/main/java/com/gitblit/utils/MetricUtils.java                                  |    0 
 src/main/java/com/gitblit/wicket/charting/GoogleLineChart.java                    |    0 
 src/test/java/de/akquinet/devops/GitblitRunnable.java                             |  133 
 src/main/java/com/gitblit/wicket/panels/UsersPanel.html                           |    0 
 src/test/java/de/akquinet/devops/ManualUITestLaunch.java                          |   15 
 src/main/distrib/win/authority.cmd                                                |    1 
 src/main/resources/file_zip_16x16.png                                             |    0 
 src/main/java/com/gitblit/AddIndexedBranch.java                                   |  149 
 src/main/resources/bootstrap/font/iconic_fill.otf                                 |    0 
 src/site/screenshots/05.png                                                       |    0 
 src/main/java/com/gitblit/models/SearchResult.java                                |    0 
 src/main/resources/file_cpp_16x16.png                                             |    0 
 src/main/java/com/gitblit/wicket/ExternalImage.java                               |    0 
 src/main/java/com/gitblit/wicket/pages/prettify/lang-css.js                       |    2 
 src/main/java/com/gitblit/wicket/panels/DigestsPanel.html                         |   42 
 src/main/resources/commit_changes_16x16.png                                       |    0 
 src/main/java/com/gitblit/utils/StringUtils.java                                  |  736 
 src/main/java/com/gitblit/wicket/charting/SecureChartDataEncoding.java            |   99 
 src/main/java/log4j.properties                                                    |    0 
 src/main/java/login_es.mkd                                                        |    0 
 src/site/screenshots/01c.png                                                      |    0 
 src/main/java/com/gitblit/wicket/AuthorizationStrategy.java                       |   85 
 src/test/java/de/akquinet/devops/test/ui/view/RepoEditView.java                   |  158 
 src/main/java/com/gitblit/wicket/pages/TagPage.html                               |   37 
 src/test/java/com/gitblit/tests/GitBlitTest.java                                  |  187 
 src/main/java/com/gitblit/client/RegistrationsTableModel.java                     |    0 
 src/test/java/com/gitblit/tests/JGitUtilsTest.java                                |  472 
 src/test/java/de/akquinet/devops/test/ui/TestUISuite.java                         |   33 
 src/main/java/com/gitblit/wicket/pages/MetricsPage.html                           |    0 
 src/main/java/com/gitblit/wicket/panels/GravatarImage.java                        |   84 
 src/main/java/com/gitblit/wicket/pages/DocsPage.java                              |   85 
 src/main/resources/gitblt-logo.png                                                |    0 
 src/main/java/com/gitblit/wicket/pages/BlamePage.html                             |   41 
 src/main/resources/arrow_left.png                                                 |    0 
 src/main/resources/bootstrap/css/bootstrap-responsive.css                         |  810 
 src/main/java/com/gitblit/wicket/pages/prettify/lang-vhdl.js                      |    3 
 src/main/java/com/gitblit/models/RepositoryUrl.java                               |   49 
 src/main/resources/bullet_delete.png                                              |    0 
 src/main/resources/rosette_32x32.png                                              |    0 
 src/test/java/com/gitblit/tests/ByteFormatTest.java                               |    0 
 .checkstyle                                                                       |    2 
 src/main/java/com/gitblit/wicket/pages/BranchesPage.java                          |   37 
 src/main/resources/information_16x16.png                                          |    0 
 src/main/distrib/win/gitblit.cmd                                                  |    1 
 src/main/java/com/gitblit/wicket/panels/ActivityPanel.html                        |   37 
 src/site/fancybox/fancybox-y.png                                                  |    0 
 src/main/java/com/gitblit/wicket/GitBlitWebApp_ko.properties                      |  504 
 src/main/java/com/gitblit/wicket/pages/MyDashboardPage.html                       |   79 
 src/main/resources/bootstrap/js/bootstrap.js                                      |    0 
 src/main/java/com/gitblit/wicket/pages/BasePage.html                              |   53 
 src/main/java/com/gitblit/wicket/panels/PagerPanel.html                           |    0 
 src/main/resources/bullet_red.png                                                 |    0 
 src/main/java/com/gitblit/utils/X509Utils.java                                    |    0 
 src/main/java/com/gitblit/wicket/pages/prettify/lang-basic.js                     |    3 
 src/main/java/com/gitblit/wicket/panels/FederationProposalsPanel.java             |    0 
 src/main/java/com/gitblit/wicket/pages/prettify/lang-pascal.js                    |    3 
 src/main/java/com/gitblit/models/DailyLogEntry.java                               |   81 
 src/main/java/com/gitblit/wicket/pages/GravatarProfilePage.java                   |    0 
 src/main/resources/bootstrap/font/iconic_stroke.afm                               |  170 
 src/site/screenshots/m05.png                                                      |    0 
 src/test/java/com/gitblit/tests/UserServiceTest.java                              |    0 
 src/site/screenshots/08.png                                                       |    0 
 src/main/java/com/gitblit/wicket/pages/MarkdownPage.html                          |    0 
 src/main/java/com/gitblit/models/ServerStatus.java                                |   83 
 gitblit.iml                                                                       |  338 
 src/main/java/com/gitblit/FileUserService.java                                    | 1146 +
 src/main/java/com/gitblit/wicket/pages/prettify/lang-yaml.js                      |    2 
 NOTICE                                                                            |   48 
 src/main/java/com/gitblit/RpcFilter.java                                          |    0 
 src/main/java/com/gitblit/models/FederationProposal.java                          |    0 
 src/main/java/com/gitblit/wicket/freemarker/Freemarker.java                       |   46 
 src/site/screenshots/12.png                                                       |    0 
 src/main/java/com/gitblit/wicket/panels/TagsPanel.java                            |  170 
 src/test/java/de/akquinet/devops/GitBlitServer4UITests.java                       |   62 
 src/main/java/WEB-INF/web.xml                                                     |  292 
 src/site/fancybox/jquery-1.4.3.min.js                                             |    0 
 src/main/java/com/gitblit/AuthenticationFilter.java                               |  187 
 src/main/resources/smartgithg_32x32.png                                           |    0 
 src/main/java/com/gitblit/client/RegistrationsDialog.java                         |    0 
 src/main/java/com/gitblit/git/GitblitUploadPackFactory.java                       |  107 
 src/main/java/com/gitblit/client/NameRenderer.java                                |    0 
 src/test/java/com/gitblit/tests/ObjectCacheTest.java                              |    0 
 src/main/java/com/gitblit/wicket/panels/CompressedDownloadsPanel.html             |    0 
 src/main/resources/bootstrap/font/iconic_stroke.eot                               |    0 
 src/main/java/com/gitblit/models/RegistrantAccessPermission.java                  |    0 
 src/main/resources/clippy.swf                                                     |    0 
 src/main/distrib/win/gitblit-stop.cmd                                             |    1 
 src/site/screenshots/m02.png                                                      |    0 
 src/main/java/com/gitblit/wicket/panels/TeamsPanel.html                           |    0 
 src/main/java/com/gitblit/wicket/freemarker/FreemarkerPanel.java                  |  308 
 src/site/screenshots.mkd                                                          |  143 
 src/main/java/com/gitblit/client/GitblitManagerLauncher.java                      |  164 
 src/main/java/com/gitblit/wicket/panels/RegistrantPermissionsPanel.html           |    0 
 src/site/templates/atom.ftl                                                       |    2 
 src/main/java/com/gitblit/wicket/pages/prettify/lang-sql.js                       |    2 
 src/main/java/com/gitblit/client/MessageRenderer.java                             |    0 
 src/main/resources/gitweb-favicon.png                                             |    0 
 src/main/java/com/gitblit/client/Translation.java                                 |   59 
 src/main/java/com/gitblit/wicket/panels/HistoryPanel.java                         |  341 
 src/site/resources/fed_mirror.png                                                 |    0 
 src/main/java/com/gitblit/wicket/GitblitWicketFilter.java                         |  145 
 src/main/java/com/gitblit/wicket/pages/EmptyRepositoryPage.java                   |   76 
 src/main/java/com/gitblit/wicket/pages/CommitDiffPage.java                        |  166 
 src/main/resources/file_acrobat_16x16.png                                         |    0 
 src/main/java/com/gitblit/wicket/pages/TreePage.html                              |    0 
 src/site/architecture.odg                                                         |    0 
 src/test/java/com/gitblit/tests/FileUtilsTest.java                                |   86 
 src/main/java/com/gitblit/utils/HttpUtils.java                                    |    0 
 src/main/java/com/gitblit/wicket/pages/SendProposalPage.java                      |  157 
 src/main/java/com/gitblit/utils/RpcUtils.java                                     |  637 
 src/main/java/com/gitblit/GitblitSslContextFactory.java                           |    0 
 src/main/java/com/gitblit/git/GitDaemonService.java                               |  165 
 src/main/java/com/gitblit/wicket/pages/RootSubPage.java                           |    0 
 src/main/resources/feed_16x16.png                                                 |    0 
 src/main/java/com/gitblit/GitFilter.java                                          |  246 
 src/main/resources/commit_divide_16x16.png                                        |    0 
 src/main/java/com/gitblit/wicket/pages/FederationPage.java                        |    0 
 src/main/resources/bullet_yellow.png                                              |    0 
 src/main/resources/file_h_16x16.png                                               |    0 
 src/main/resources/pixel.png                                                      |    0 
 src/site/screenshots/01b.png                                                      |    0 
 src/main/resources/gitblt_25.png                                                  |    0 
 src/main/distrib/linux/add-indexed-branch.sh                                      |   21 
 src/main/resources/add_16x16.png                                                  |    0 
 src/main/java/com/gitblit/wicket/RequiresAdminRole.java                           |    0 
 src/main/java/com/gitblit/wicket/WicketUtils.java                                 |  629 
 src/main/java/com/gitblit/wicket/panels/RepositoryUrlPanel.html                   |  114 
 src/main/resources/commit_branch_16x16.png                                        |    0 
 src/main/java/com/gitblit/wicket/panels/ReflogPanel.html                          |   43 
 src/main/java/com/gitblit/wicket/pages/ForksPage.html                             |    0 
 src/main/resources/file_cs_16x16.png                                              |    0 
 src/site/screenshots/m04.png                                                      |    0 
 src/main/java/com/gitblit/utils/RefLogUtils.java                                  |  666 
 src/main/resources/bullet_white.png                                               |    0 
 src/site/screenshots/07.png                                                       |    0 
 src/main/java/com/gitblit/.gitignore                                              |    0 
 src/test/java/com/gitblit/tests/GitServletTest.java                               |  920 
 src/main/java/com/gitblit/GitBlit.java                                            | 3889 +++
 src/main/java/com/gitblit/wicket/StringChoiceRenderer.java                        |    0 
 src/main/resources/vcard_16x16.png                                                |    0 
 src/main/java/com/gitblit/wicket/panels/BulletListPanel.java                      |    0 
 src/main/java/com/gitblit/client/FeedsPanel.java                                  |    0 
 src/main/java/com/gitblit/client/SearchDialog.java                                |    0 
 src/main/java/com/gitblit/wicket/GitBlitWebApp_ja.properties                      |  451 
 src/main/java/com/gitblit/client/EditTeamDialog.java                              |    0 
 src/main/java/com/gitblit/authority/UserCertificateModel.java                     |    0 
 src/site/screenshots/13.png                                                       |    0 
 src/main/java/com/gitblit/authority/RequestFocusListener.java                     |   80 
 src/main/java/com/gitblit/client/RegistrantPermissionsTableModel.java             |    0 
 src/test/config/test-gitblit.properties                                           |   88 
 src/main/java/com/gitblit/FederationServlet.java                                  |    0 
 src/site/screenshots/image_processing.txt                                         |    0 
 src/site/screenshots/m10.png                                                      |    0 
 src/main/java/com/gitblit/wicket/pages/TagsPage.java                              |   43 
 src/site/gitblit_logo_white.xcf                                                   |    0 
 src/main/resources/book_16x16.png                                                 |    0 
 src/main/java/com/gitblit/authority/Utils.java                                    |    0 
 src/main/java/com/gitblit/git/ReceiveHook.java                                    |  361 
 src/main/java/com/gitblit/wicket/pages/TicketsPage.html                           |   24 
 src/main/java/com/gitblit/EnforceAuthenticationFilter.java                        |  102 
 src/main/java/com/gitblit/models/RefModel.java                                    |    0 
 src/main/java/com/gitblit/client/SettingsTableModel.java                          |    0 
 src/main/java/com/gitblit/GitBlitServer.java                                      |  642 
 src/main/java/com/gitblit/wicket/panels/ProjectRepositoryPanel.html               |   77 
 release.template                                                                  |   52 
 src/main/distrib/data/projects.conf                                               |    0 
 src/main/resources/settings_32x32.png                                             |    0 
 src/main/resources/sparkleshare_32x32.png                                         |    0 
 src/main/java/com/gitblit/wicket/ng/angular.js                                    |  163 
 src/main/java/com/gitblit/wicket/pages/FederationRegistrationPage.html            |   39 
 src/main/java/com/gitblit/wicket/GitBlitWebApp_zh_CN.properties                   |  504 
 src/main/java/com/gitblit/wicket/pages/EditUserPage.html                          |    0 
 src/main/java/com/gitblit/SalesforceUserService.java                              |  137 
 src/main/java/com/gitblit/wicket/pages/RepositoryPage.java                        |  685 
 src/main/java/com/gitblit/wicket/panels/ObjectContainer.java                      |    0 
 src/main/resources/file_ruby_16x16.png                                            |    0 
 src/site/upgrade_war.mkd                                                          |   17 
 src/main/java/com/gitblit/authority/UserCertificateConfig.java                    |    0 
 src/main/java/com/gitblit/wicket/panels/FilterableProjectList.html                |   10 
 src/site/fancybox/fancy_nav_right.png                                             |    0 
 src/main/java/com/gitblit/wicket/pages/BlobPage.html                              |   66 
 src/main/distrib/linux/authority.sh                                               |    2 
 /dev/null                                                                         |    0 
 src/main/java/com/gitblit/WebXmlSettings.java                                     |    0 
 src/test/java/com/gitblit/tests/FederationTests.java                              |  173 
 src/site/fancybox/fancy_shadow_s.png                                              |    0 
 src/main/distrib/data/git/project.mkd                                             |   12 
 src/main/distrib/linux/service-ubuntu.sh                                          |    0 
 src/main/java/com/gitblit/wicket/panels/PathBreadcrumbsPanel.html                 |    0 
 src/test/config/test-ui-gitblit.properties                                        |    0 
 src/main/resources/heart_16x16.png                                                |    0 
 src/main/resources/bullet_green.png                                               |    0 
 src/main/java/com/gitblit/utils/MarkdownUtils.java                                |    0 
 src/main/java/com/gitblit/wicket/panels/BranchesPanel.java                        |    0 
 src/main/distrib/linux/gitblit-stop.sh                                            |    2 
 src/main/java/com/gitblit/utils/DiffUtils.java                                    |  282 
 src/main/resources/clippy.png                                                     |    0 
 src/main/resources/bootstrap/img/glyphicons-halflings.png                         |    0 
 757 files changed, 66,576 insertions(+), 1,133 deletions(-)

diff --git a/.checkstyle b/.checkstyle
index d0c93e3..58e2377 100644
--- a/.checkstyle
+++ b/.checkstyle
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 
 <fileset-config file-format-version="1.2.0" simple-config="true" sync-formatter="false">
-  <local-check-config name="Gitblit" location="checkstyle.xml" type="project" description="">
+  <local-check-config name="Gitblit" location="src/main/config/checkstyle.xml" type="project" description="">
     <additional-data name="protect-config-file" value="false"/>
   </local-check-config>
   <fileset name="all" enabled="true" check-config-name="Gitblit" local="true">
diff --git a/.classpath b/.classpath
index 365b6d4..179bfdf 100644
--- a/.classpath
+++ b/.classpath
@@ -1,55 +1,65 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <classpath>
-	<classpathentry kind="src" path="src" />
-	<classpathentry kind="src" path="resources" />
-	<classpathentry kind="src" path="tests" output="bin/test-classes" />
-	<classpathentry kind="lib" path="ext/jcommander-1.17.jar" sourcepath="ext/src/jcommander-1.17-sources.jar" />
-	<classpathentry kind="lib" path="ext/log4j-1.2.17.jar" sourcepath="ext/src/log4j-1.2.17-sources.jar" />
-	<classpathentry kind="lib" path="ext/slf4j-api-1.6.6.jar" sourcepath="ext/src/slf4j-api-1.6.6-sources.jar" />
-	<classpathentry kind="lib" path="ext/slf4j-log4j12-1.6.6.jar" sourcepath="ext/src/slf4j-log4j12-1.6.6-sources.jar" />
-	<classpathentry kind="lib" path="ext/mail-1.4.3.jar" sourcepath="ext/src/mail-1.4.3-sources.jar" />
-	<classpathentry kind="lib" path="ext/javax.servlet-api-3.0.1.jar" sourcepath="ext/src/javax.servlet-api-3.0.1-sources.jar" />
-	<classpathentry kind="lib" path="ext/jetty-webapp-7.6.8.v20121106.jar" sourcepath="ext/src/jetty-webapp-7.6.8.v20121106-sources.jar" />
-	<classpathentry kind="lib" path="ext/jetty-ajp-7.6.8.v20121106.jar" sourcepath="ext/src/jetty-ajp-7.6.8.v20121106-sources.jar" />
-	<classpathentry kind="lib" path="ext/wicket-1.4.21.jar" sourcepath="ext/src/wicket-1.4.21-sources.jar" />
-	<classpathentry kind="lib" path="ext/wicket-auth-roles-1.4.21.jar" sourcepath="ext/src/wicket-auth-roles-1.4.21-sources.jar" />
-	<classpathentry kind="lib" path="ext/wicket-extensions-1.4.21.jar" sourcepath="ext/src/wicket-extensions-1.4.21-sources.jar" />
-	<classpathentry kind="lib" path="ext/googlecharts-1.4.21.jar" sourcepath="ext/src/googlecharts-1.4.21-sources.jar" />
-	<classpathentry kind="lib" path="ext/lucene-core-3.6.1.jar" sourcepath="ext/src/lucene-core-3.6.1-sources.jar" />
-	<classpathentry kind="lib" path="ext/lucene-highlighter-3.6.1.jar" sourcepath="ext/src/lucene-highlighter-3.6.1-sources.jar" />
-	<classpathentry kind="lib" path="ext/lucene-memory-3.6.1.jar" sourcepath="ext/src/lucene-memory-3.6.1-sources.jar" />
-	<classpathentry kind="lib" path="ext/lucene-queries-3.6.1.jar" sourcepath="ext/src/lucene-queries-3.6.1-sources.jar" />
+	<classpathentry kind="src" path="src/main/java" />
+	<classpathentry kind="src" path="src/test/java" output="bin/test-classes" />
+	<classpathentry kind="src" path="src/main/resources" />
+	<classpathentry kind="lib" path="ext/jcommander-1.17.jar" sourcepath="ext/src/jcommander-1.17.jar" />
+	<classpathentry kind="lib" path="ext/log4j-1.2.17.jar" sourcepath="ext/src/log4j-1.2.17.jar" />
+	<classpathentry kind="lib" path="ext/slf4j-api-1.6.6.jar" sourcepath="ext/src/slf4j-api-1.6.6.jar" />
+	<classpathentry kind="lib" path="ext/slf4j-log4j12-1.6.6.jar" sourcepath="ext/src/slf4j-log4j12-1.6.6.jar" />
+	<classpathentry kind="lib" path="ext/mail-1.4.3.jar" sourcepath="ext/src/mail-1.4.3.jar" />
+	<classpathentry kind="lib" path="ext/javax.servlet-api-3.0.1.jar" sourcepath="ext/src/javax.servlet-api-3.0.1.jar" />
+	<classpathentry kind="lib" path="ext/jetty-webapp-7.6.8.v20121106.jar" sourcepath="ext/src/jetty-webapp-7.6.8.v20121106.jar" />
+	<classpathentry kind="lib" path="ext/jetty-ajp-7.6.8.v20121106.jar" sourcepath="ext/src/jetty-ajp-7.6.8.v20121106.jar" />
+	<classpathentry kind="lib" path="ext/wicket-1.4.21.jar" sourcepath="ext/src/wicket-1.4.21.jar" />
+	<classpathentry kind="lib" path="ext/wicket-auth-roles-1.4.21.jar" sourcepath="ext/src/wicket-auth-roles-1.4.21.jar" />
+	<classpathentry kind="lib" path="ext/wicket-extensions-1.4.21.jar" sourcepath="ext/src/wicket-extensions-1.4.21.jar" />
+	<classpathentry kind="lib" path="ext/googlecharts-1.4.21.jar" sourcepath="ext/src/googlecharts-1.4.21.jar" />
+	<classpathentry kind="lib" path="ext/lucene-core-3.6.1.jar" sourcepath="ext/src/lucene-core-3.6.1.jar" />
+	<classpathentry kind="lib" path="ext/lucene-highlighter-3.6.1.jar" sourcepath="ext/src/lucene-highlighter-3.6.1.jar" />
+	<classpathentry kind="lib" path="ext/lucene-memory-3.6.1.jar" sourcepath="ext/src/lucene-memory-3.6.1.jar" />
+	<classpathentry kind="lib" path="ext/lucene-queries-3.6.1.jar" sourcepath="ext/src/lucene-queries-3.6.1.jar" />
 	<classpathentry kind="lib" path="ext/jakarta-regexp-1.4.jar" />
-	<classpathentry kind="lib" path="ext/markdownpapers-core-1.3.2.jar" sourcepath="ext/src/markdownpapers-core-1.3.2-sources.jar" />
-	<classpathentry kind="lib" path="ext/org.eclipse.jgit-2.2.0.201212191850-r.jar" sourcepath="ext/src/org.eclipse.jgit-2.2.0.201212191850-r-sources.jar" />
-	<classpathentry kind="lib" path="ext/jsch-0.1.44-1.jar" sourcepath="ext/src/jsch-0.1.44-1-sources.jar" />
-	<classpathentry kind="lib" path="ext/org.eclipse.jgit.http.server-2.2.0.201212191850-r.jar" sourcepath="ext/src/org.eclipse.jgit.http.server-2.2.0.201212191850-r-sources.jar" />
-	<classpathentry kind="lib" path="ext/bcprov-jdk15on-1.47.jar" sourcepath="ext/src/bcprov-jdk15on-1.47-sources.jar" />
-	<classpathentry kind="lib" path="ext/bcmail-jdk15on-1.47.jar" sourcepath="ext/src/bcmail-jdk15on-1.47-sources.jar" />
-	<classpathentry kind="lib" path="ext/bcpkix-jdk15on-1.47.jar" sourcepath="ext/src/bcpkix-jdk15on-1.47-sources.jar" />
-	<classpathentry kind="lib" path="ext/rome-0.9.jar" sourcepath="ext/src/rome-0.9-sources.jar" />
-	<classpathentry kind="lib" path="ext/jdom-1.0.jar" sourcepath="ext/src/jdom-1.0-sources.jar" />
-	<classpathentry kind="lib" path="ext/gson-1.7.2.jar" sourcepath="ext/src/gson-1.7.2-sources.jar" />
-	<classpathentry kind="lib" path="ext/groovy-all-1.8.8.jar" sourcepath="ext/src/groovy-all-1.8.8-sources.jar" />
-	<classpathentry kind="lib" path="ext/unboundid-ldapsdk-2.3.0.jar" sourcepath="ext/src/unboundid-ldapsdk-2.3.0-sources.jar" />
-	<classpathentry kind="lib" path="ext/ivy-2.2.0.jar" sourcepath="ext/src/ivy-2.2.0-sources.jar" />
+	<classpathentry kind="lib" path="ext/markdownpapers-core-1.3.2.jar" sourcepath="ext/src/markdownpapers-core-1.3.2.jar" />
+	<classpathentry kind="lib" path="ext/org.eclipse.jgit-3.0.0.201306101825-r.jar" sourcepath="ext/src/org.eclipse.jgit-3.0.0.201306101825-r.jar" />
+	<classpathentry kind="lib" path="ext/jsch-0.1.46.jar" sourcepath="ext/src/jsch-0.1.46.jar" />
+	<classpathentry kind="lib" path="ext/JavaEWAH-0.5.6.jar" sourcepath="ext/src/JavaEWAH-0.5.6.jar" />
+	<classpathentry kind="lib" path="ext/org.eclipse.jgit.http.server-3.0.0.201306101825-r.jar" sourcepath="ext/src/org.eclipse.jgit.http.server-3.0.0.201306101825-r.jar" />
+	<classpathentry kind="lib" path="ext/bcprov-jdk15on-1.47.jar" sourcepath="ext/src/bcprov-jdk15on-1.47.jar" />
+	<classpathentry kind="lib" path="ext/bcmail-jdk15on-1.47.jar" sourcepath="ext/src/bcmail-jdk15on-1.47.jar" />
+	<classpathentry kind="lib" path="ext/bcpkix-jdk15on-1.47.jar" sourcepath="ext/src/bcpkix-jdk15on-1.47.jar" />
+	<classpathentry kind="lib" path="ext/rome-0.9.jar" sourcepath="ext/src/rome-0.9.jar" />
+	<classpathentry kind="lib" path="ext/jdom-1.0.jar" sourcepath="ext/src/jdom-1.0.jar" />
+	<classpathentry kind="lib" path="ext/gson-1.7.2.jar" sourcepath="ext/src/gson-1.7.2.jar" />
+	<classpathentry kind="lib" path="ext/groovy-all-1.8.8.jar" sourcepath="ext/src/groovy-all-1.8.8.jar" />
+	<classpathentry kind="lib" path="ext/unboundid-ldapsdk-2.3.0.jar" sourcepath="ext/src/unboundid-ldapsdk-2.3.0.jar" />
+	<classpathentry kind="lib" path="ext/ivy-2.2.0.jar" sourcepath="ext/src/ivy-2.2.0.jar" />
 	<classpathentry kind="lib" path="ext/jcalendar-1.3.2.jar" />
-	<classpathentry kind="lib" path="ext/commons-compress-1.4.1.jar" sourcepath="ext/src/commons-compress-1.4.1-sources.jar" />
-	<classpathentry kind="lib" path="ext/xz-1.0.jar" sourcepath="ext/src/xz-1.0-sources.jar" />
-	<classpathentry kind="lib" path="ext/junit-4.10.jar" sourcepath="ext/src/junit-4.10-sources.jar" />
-	<classpathentry kind="lib" path="ext/hamcrest-core-1.1.jar" />
-	<classpathentry kind="lib" path="ext/seleniumhq/selenium-java-2.28.0.jar"/>
-	<classpathentry kind="lib" path="ext/seleniumhq/selenium-api-2.28.0.jar"/>
-	<classpathentry kind="lib" path="ext/seleniumhq/selenium-remote-driver-2.28.0.jar"/>
-	<classpathentry kind="lib" path="ext/seleniumhq/selenium-support-2.28.0.jar"/>
-	<classpathentry kind="lib" path="ext/seleniumhq/guava-12.0.jar"/>
-	<classpathentry kind="lib" path="ext/seleniumhq/json-20080701.jar"/>
-	<classpathentry kind="lib" path="ext/seleniumhq/commons-exec-1.1.jar"/>
-	<classpathentry kind="lib" path="ext/seleniumhq/httpcore-4.2.1.jar"/>
-	<classpathentry kind="lib" path="ext/seleniumhq/httpmime-4.2.1.jar"/>
-	<classpathentry kind="lib" path="ext/seleniumhq/httpclient-4.2.1.jar"/>
-	<classpathentry kind="lib" path="ext/seleniumhq/commons-logging-1.1.1.jar"/>
-	<classpathentry kind="lib" path="ext/seleniumhq/selenium-firefox-driver-2.28.0.jar"/>
+	<classpathentry kind="lib" path="ext/commons-compress-1.4.1.jar" sourcepath="ext/src/commons-compress-1.4.1.jar" />
+	<classpathentry kind="lib" path="ext/xz-1.0.jar" sourcepath="ext/src/xz-1.0.jar" />
+	<classpathentry kind="lib" path="ext/force-partner-api-24.0.0.jar" sourcepath="ext/src/force-partner-api-24.0.0.jar" />
+	<classpathentry kind="lib" path="ext/force-wsc-24.0.0.jar" sourcepath="ext/src/force-wsc-24.0.0.jar" />
+	<classpathentry kind="lib" path="ext/js-1.7R2.jar" sourcepath="ext/src/js-1.7R2.jar" />
+	<classpathentry kind="lib" path="ext/freemarker-2.3.19.jar" sourcepath="ext/src/freemarker-2.3.19.jar" />
+	<classpathentry kind="lib" path="ext/waffle-jna-1.5.jar" sourcepath="ext/src/waffle-jna-1.5.jar" />
+	<classpathentry kind="lib" path="ext/platform-3.5.0.jar" sourcepath="ext/src/platform-3.5.0.jar" />
+	<classpathentry kind="lib" path="ext/jna-3.5.0.jar" sourcepath="ext/src/jna-3.5.0.jar" />
+	<classpathentry kind="lib" path="ext/guava-13.0.1.jar" sourcepath="ext/src/guava-13.0.1.jar" />
+	<classpathentry kind="lib" path="ext/junit-4.11.jar" sourcepath="ext/src/junit-4.11.jar" />
+	<classpathentry kind="lib" path="ext/hamcrest-core-1.3.jar" sourcepath="ext/src/hamcrest-core-1.3.jar" />
+	<classpathentry kind="lib" path="ext/selenium-java-2.28.0.jar" sourcepath="ext/src/selenium-java-2.28.0.jar" />
+	<classpathentry kind="lib" path="ext/selenium-support-2.28.0.jar" sourcepath="ext/src/selenium-support-2.28.0.jar" />
+	<classpathentry kind="lib" path="ext/selenium-firefox-driver-2.28.0.jar" sourcepath="ext/src/selenium-firefox-driver-2.28.0.jar" />
+	<classpathentry kind="lib" path="ext/selenium-remote-driver-2.28.0.jar" sourcepath="ext/src/selenium-remote-driver-2.28.0.jar" />
+	<classpathentry kind="lib" path="ext/cglib-nodep-2.1_3.jar" sourcepath="ext/src/cglib-nodep-2.1_3.jar" />
+	<classpathentry kind="lib" path="ext/json-20080701.jar" sourcepath="ext/src/json-20080701.jar" />
+	<classpathentry kind="lib" path="ext/selenium-api-2.28.0.jar" sourcepath="ext/src/selenium-api-2.28.0.jar" />
+	<classpathentry kind="lib" path="ext/httpclient-4.2.1.jar" sourcepath="ext/src/httpclient-4.2.1.jar" />
+	<classpathentry kind="lib" path="ext/httpcore-4.2.1.jar" sourcepath="ext/src/httpcore-4.2.1.jar" />
+	<classpathentry kind="lib" path="ext/commons-logging-1.1.1.jar" sourcepath="ext/src/commons-logging-1.1.1.jar" />
+	<classpathentry kind="lib" path="ext/commons-codec-1.6.jar" sourcepath="ext/src/commons-codec-1.6.jar" />
+	<classpathentry kind="lib" path="ext/commons-exec-1.1.jar" sourcepath="ext/src/commons-exec-1.1.jar" />
+	<classpathentry kind="lib" path="ext/commons-io-2.2.jar" sourcepath="ext/src/commons-io-2.2.jar" />
 	<classpathentry kind="output" path="bin/classes" />
 	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER" />
 </classpath>
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 65b74ab..f77b6ff 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,35 +1,24 @@
-/temp
-/lib
-/ext
-/build
-/keystore
-/*.zip
-/gitblit.properties
-/users.properties
-/site
-/git
-/target
-/build.properties
-/war
-/*.war
-/proposals
-/*.jar
-/federation.properties
-/mailtest.properties
-/.settings/*.prefs
-/src/WEB-INF/reference.properties
-/bin/
-/.settings/
-/javadoc
-/express
-/build-demo.xml
-/users.conf
-*.directory
-/.gradle
-/projects.conf
-/pom.xml
-/deploy
-/*.jks
-/x509test
-/certs
+/temp
+/lib
+/ext
+/build
+/site
+/git
+/build.properties
+/federation.properties
+/mailtest.properties
+/test-users.conf
+/.settings/
+/src/main/java/reference.properties
+/src/main/java/WEB-INF/reference.properties
+/bin/
+/build-demo.xml
+*.directory
+/.gradle
+/pom.xml
+/x509test
 /data
+/*.conf
+/build-next.xml
+/*.ps1
+/*.sh
diff --git a/NOTICE b/NOTICE
index 4daa120..9bd356a 100644
--- a/NOTICE
+++ b/NOTICE
@@ -255,3 +255,51 @@
 
    http://tukaani.org/xz/java.html
 
+---------------------------------------------------------------------------
+Iconic
+---------------------------------------------------------------------------
+   Iconic, release under the
+   Creative Commons Share Alike 3.0 License.
+   
+   http://somerandomdude.com/work/iconic
+   
+---------------------------------------------------------------------------
+AngularJS
+---------------------------------------------------------------------------
+   AngularJS, release under the
+   MIT License.
+   
+   http://angularjs.org/   
+   
+---------------------------------------------------------------------------
+FreeMarker
+---------------------------------------------------------------------------
+   FreeMarker, release under a
+   modified BSD License. (http://www.freemarker.org/docs/app_license.html)
+   
+   http://www.freemarker.org/
+
+---------------------------------------------------------------------------
+Waffle
+---------------------------------------------------------------------------
+   Waffle, release under the
+   Eclipse Public License, version 1.0
+   
+   http://dblock.github.io/waffle
+
+---------------------------------------------------------------------------
+JNA
+---------------------------------------------------------------------------
+   JNA, release under the
+   Lesser GNU Public License, version 2.1
+   
+   https://github.com/twall/jna
+   
+---------------------------------------------------------------------------
+Guava
+---------------------------------------------------------------------------
+   Guava, release under the
+   Apache License 2.0.
+   
+   https://code.google.com/p/guava-libraries
+  
\ No newline at end of file
diff --git a/README.markdown b/README.markdown
index e1a5fa8..b6587df 100644
--- a/README.markdown
+++ b/README.markdown
@@ -32,4 +32,9 @@
 3. Select your gitblit project root and **Refresh** the project, this should correct all build problems.
 4. Using JUnit, execute the `com.gitblit.tests.GitBlitSuite` test suite.<br/>
 *This will clone some repositories from the web and run through the unit tests.*
-5. Execute the *com.gitblit.Launcher* class to start Gitblit.   
\ No newline at end of file
+5. Execute the *com.gitblit.GitBlitServer* class to start Gitblit GO.
+
+Building Tips & Tricks
+----------------------
+1. If you are running Ant from an ANSI-capable console, consider setting the `MX_COLOR` ennvironment variable before executing Ant.<pre>set MX_COLOR=true</pre>
+2. The build script will honor your Maven proxy settings.  If you need to fine-tune this, please review the [settings.moxie](http://gitblit.github.io/moxie/settings.html) documentation.
\ No newline at end of file
diff --git a/build.moxie b/build.moxie
new file mode 100644
index 0000000..d15d916
--- /dev/null
+++ b/build.moxie
@@ -0,0 +1,160 @@
+#
+# Gitblit project descriptor
+#
+
+# Specify minimum Moxie version required to build
+requires: 0.7.4
+
+# Project Metadata
+name: Gitblit
+description: pure Java Git solution
+groupId: com.gitblit
+artifactId: gitblit
+version: 1.3.1-SNAPSHOT
+inceptionYear: 2011
+
+# Current stable release
+releaseVersion: 1.3.0
+releaseDate: 2013-07-14
+
+# Project urls
+url: 'http://gitblit.com'
+issuesUrl: 'http://code.google.com/p/gitblit/issues/list'
+socialNetworkUrl: 'https://plus.google.com/114464678392593421684'
+forumUrl: 'http://groups.google.com/group/gitblit'
+
+# Licenses section included for POM generation
+licenses:
+- {
+    name: Apache ASL v2.0
+    url: 'http://www.apache.org/licenses/LICENSE-2.0.html'
+  }
+
+# Developers section included for POM generation
+developers:
+- {
+  id: james
+  name: James Moger
+  url: 'https://plus.google.com/u/0/116428776452027956920'
+  organization: VAS
+  organizationUrl: 'http://www.vas.com'
+  roles: developer
+  }
+
+# SCM section included for POM generation
+scm: {
+  connection: 'scm:git:git://github.com/gitblit/gitblit.git'
+  developerConnection: 'scm:git:https://github.com/gitblit/gitblit.git'
+  url: 'https://github.com/gitblit/gitblit'
+  tag: HEAD
+  }
+
+# Mainclass is used for setting jar manifests and using the mx:run target
+mainclass: com.gitblit.GitBlitServer
+
+# Moxie supports multiple source directories and allows you to assign
+# a scope to each directory.
+sourceDirectories:
+- compile 'src/main/java'
+- test 'src/test/java'
+# Moxie supports one site-scoped directory for mx:doc
+- site 'src/site'
+
+resourceDirectories:
+- compile 'src/main/resources'
+- site 'src/site/resources'
+
+# compile for Java 6 class format
+tasks: {
+	'mx:javac' : {
+        source: 1.6
+        target: 1.6
+        compiler: javac1.6
+        encoding: UTF-8
+        # stop complaints about bootstrap classpath when compiling with Java 7
+        compilerArgs: '-Xlint:-options'
+    }
+}
+
+# Generate Eclipse project files.
+# Generate IntelliJ IDEA module files.
+# Generate a distribution Maven POM (not suitable for building with Maven).
+apply: eclipse, intellij, pom
+
+# Copy all retrieved dependencies to the "ext" directory.
+# Generated IDE settings (.classpath, etc) will use the artifacts
+# from this project-relative directory. This allows the IDE settings
+# to be version-controlled and shared.
+dependencyDirectory: ext
+
+# Register the Eclipse JGit Maven repositories
+registeredRepositories:
+- { id: jgit, url: 'http://download.eclipse.org/jgit/maven' }
+- { id: jgit-snapshots, url: 'https://repo.eclipse.org/content/groups/snapshots' }
+
+# Source all dependencies from the following repositories in the specified order
+repositories: central, jgit-snapshots, jgit
+
+# Convenience properties for dependencies
+properties: {
+  jetty.version  : 7.6.8.v20121106
+  wicket.version : 1.4.21
+  lucene.version : 3.6.1
+  jgit.version   : 3.0.0.201306101825-r
+  groovy.version : 1.8.8
+  bouncycastle.version : 1.47
+  selenium.version : 2.28.0
+  }
+
+# Dependencies
+#
+#   May be tagged with ":label" notation to group dependencies.
+#
+#   "@extension" fetches the artifact with the specified extension
+#   and ignores all transitive dependencies.
+#
+#   "!groupId" or "!groupId:artifactId" excludes all matching transitive
+#   dependencies in that dependency's dependency graph.
+#
+
+dependencies:
+# Standard dependencies
+- compile 'com.beust:jcommander:1.17' :fedclient :authority
+- compile 'log4j:log4j:1.2.17' :war :fedclient :authority
+- compile 'org.slf4j:slf4j-api:1.6.6' :war :fedclient :authority
+- compile 'org.slf4j:slf4j-log4j12:1.6.6' :war :fedclient :authority
+- compile 'javax.mail:mail:1.4.3' :war :fedclient :authority
+- compile 'javax.servlet:javax.servlet-api:3.0.1' :fedclient
+- compile 'org.eclipse.jetty.aggregate:jetty-webapp:${jetty.version}' @jar
+- compile 'org.eclipse.jetty:jetty-ajp:${jetty.version}' @jar
+- compile 'org.apache.wicket:wicket:${wicket.version}' :war !org.mockito
+- compile 'org.apache.wicket:wicket-auth-roles:${wicket.version}' :war !org.mockito
+- compile 'org.apache.wicket:wicket-extensions:${wicket.version}' :war !org.mockito
+- compile 'org.wicketstuff:googlecharts:${wicket.version}' :war
+- compile 'org.apache.lucene:lucene-core:${lucene.version}' :war :fedclient
+- compile 'org.apache.lucene:lucene-highlighter:${lucene.version}' :war :fedclient
+- compile 'org.apache.lucene:lucene-memory:${lucene.version}' :war :fedclient
+- compile 'org.tautua.markdownpapers:markdownpapers-core:1.3.2' :war
+- compile 'org.eclipse.jgit:org.eclipse.jgit:${jgit.version}' :war :fedclient :manager :authority
+- compile 'org.eclipse.jgit:org.eclipse.jgit.http.server:${jgit.version}' :war :fedclient :manager :authority
+- compile 'org.bouncycastle:bcprov-jdk15on:${bouncycastle.version}' :war :authority
+- compile 'org.bouncycastle:bcmail-jdk15on:${bouncycastle.version}' :war :authority
+- compile 'org.bouncycastle:bcpkix-jdk15on:${bouncycastle.version}' :war :authority
+- compile 'rome:rome:0.9' :war :manager :api
+- compile 'com.google.code.gson:gson:1.7.2' :war :fedclient :manager :api
+- compile 'org.codehaus.groovy:groovy-all:${groovy.version}' :war
+- compile 'com.unboundid:unboundid-ldapsdk:2.3.0' :war
+- compile 'org.apache.ivy:ivy:2.2.0' :war
+- compile 'com.toedter:jcalendar:1.3.2' :authority
+- compile 'org.apache.commons:commons-compress:1.4.1' :war
+- compile 'com.force.api:force-partner-api:24.0.0' :war
+- compile 'org.freemarker:freemarker:2.3.19' :war
+- compile 'com.github.dblock.waffle:waffle-jna:1.5' :war
+- test 'junit'
+# Dependencies for Selenium web page testing
+- test 'org.seleniumhq.selenium:selenium-java:${selenium.version}' @jar
+- test 'org.seleniumhq.selenium:selenium-support:${selenium.version}' @jar
+- test 'org.seleniumhq.selenium:selenium-firefox-driver:${selenium.version}'
+# Dependencies with the "build" scope are retrieved
+# and injected into the Ant runtime classpath
+- build 'jacoco'
diff --git a/build.xml b/build.xml
index 869c370..4939f7f 100644
--- a/build.xml
+++ b/build.xml
@@ -1,117 +1,68 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<project name="gitblit" default="compile" basedir=".">
-
-	<!-- Google Code upload task -->
-	<taskdef classname="net.bluecow.googlecode.ant.GoogleCodeUploadTask" 
-		classpath="${basedir}/tools/ant-googlecode-0.0.3.jar" name="gcupload"/>
-
-	<!-- GenJar task -->
-	<taskdef resource="genjar.properties" classpath="${basedir}/tools/GenJar.jar" />
-
-	<!-- Project Properties -->
-	<property name="project.jar" value="gitblit.jar" />
-	<property name="project.mainclass" value="com.gitblit.Launcher" />
-	<property name="project.build.dir" value="${basedir}/build" />
-	<property name="project.deploy.dir" value="${basedir}/deploy" />	
-	<property name="project.war.dir" value="${basedir}/war" />
-	<property name="project.jar.dir" value="${basedir}/jar" />
-	<property name="project.site.dir" value="${basedir}/target/site" />
-	<property name="project.target.dir" value="${basedir}/target" />
-	<property name="project.resources.dir" value="${basedir}/resources" />	
-	<property name="project.express.dir" value="${basedir}/express" />
-	<property name="project.maven.repo.url" value="enter here your Maven repo URL" />
-	<property name="project.maven.repo.id" value="gitblit.maven.repo" />
-	<available property="hasBuildProps" file="${basedir}/build.properties"/>
+<project name="gitblit" default="compile" xmlns:mx="antlib:org.moxie">
 
 	<!--
 		~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-		Load build.properties, if available
-		~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-	-->
-	<target name="buildprops" if="hasBuildProps">
-		<!-- Load publication servers, paths, and credentials --> 
-		<loadproperties>
-			<file file="${basedir}/build.properties" />
-		</loadproperties>
-	</target>
-	
-	
-	<!--
-		~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-		Scrape the version info from code and setup the build properties 
-		~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-	-->
-	<target name="buildinfo" depends="buildprops">
-	
-		<!-- extract Gitblit version number from source code -->
-		<loadfile property="gb.version" srcfile="${basedir}/src/com/gitblit/Constants.java">
-			<filterchain>
-				<linecontains>
-					<contains value="public static final String VERSION = " />
-				</linecontains>
-				<striplinebreaks />
-				<tokenfilter>
-					<replacestring from="public static final String VERSION = &quot;" to="" />
-					<replacestring from="&quot;;" to="" />
-					<trim />
-				</tokenfilter>
-			</filterchain>
-		</loadfile>
-
-		<!-- extract Gitblit version date from source code -->
-		<loadfile property="gb.versionDate" srcfile="${basedir}/src/com/gitblit/Constants.java">
-			<filterchain>
-				<linecontains>
-					<contains value="public static final String VERSION_DATE = " />
-				</linecontains>
-				<striplinebreaks />
-				<tokenfilter>
-					<replacestring from="public static final String VERSION_DATE = &quot;" to="" />
-					<replacestring from="&quot;;" to="" />
-					<trim />
-				</tokenfilter>
-			</filterchain>
-		</loadfile>
-					
-		<!-- extract JGit version number from source code -->
-		<loadfile property="jgit.version" srcfile="${basedir}/src/com/gitblit/Constants.java">
-			<filterchain>
-				<linecontains>
-					<contains value="public static final String JGIT_VERSION = " />
-				</linecontains>
-				<striplinebreaks />
-				<tokenfilter>
-					<replacestring from="public static final String JGIT_VERSION = &quot;" to="" />
-					<replacestring from="&quot;;" to="" />
-					<trim />
-				</tokenfilter>
-			</filterchain>
-		</loadfile>	
-		<property name="distribution.zipfile" value="gitblit-${gb.version}.zip" />
-		<property name="distribution.warfile" value="gitblit-${gb.version}.war" />
-		<property name="distribution.jarfile" value="gitblit-${gb.version}.jar" />
-		<property name="distribution.pomfile" value="${basedir}/pom.xml" />
-		<property name="fedclient.zipfile" value="fedclient-${gb.version}.zip" />
-		<property name="manager.zipfile" value="manager-${gb.version}.zip" />
-		<property name="authority.zipfile" value="authority-${gb.version}.zip" />
-		<property name="gbapi.zipfile" value="gbapi-${gb.version}.zip" />
-		<property name="express.zipfile" value="express-${gb.version}.zip" />
-		<property name="distribution.pomfileTmplt" value="tmplt.pom.xml" />
-	</target>
-	
-	
-	<!--
-		~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-		Compile
-		~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-	-->
-	<target name="compile" depends="buildinfo" description="Retrieves dependencies and compiles Gitblit from source">
-
-		<!-- cleanup old builds -->
-		<delete dir="${project.target.dir}" />
-		<mkdir dir="${project.target.dir}" />
+		Retrieve Moxie Toolkit
 		
-		<!-- cleanup old builds -->
+		documentation @ http://gitblit.github.io/moxie
+		~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+	-->
+	<property name="moxie.version" value="0.7.4" />
+	<property name="moxie.url" value="http://gitblit.github.io/moxie/maven" />
+	<property name="moxie.jar" value="moxie-toolkit-${moxie.version}.jar" />
+	<property name="moxie.dir" value="${user.home}/.moxie" />
+	
+	<!-- Download Moxie from it's Maven repository to user.home -->
+	<mkdir dir="${moxie.dir}" />
+	<get src="${moxie.url}/org/moxie/moxie-toolkit/${moxie.version}/${moxie.jar}"
+		dest="${moxie.dir}" skipexisting="true" verbose="true" />
+	
+	<!-- Register Moxie tasks -->
+	<taskdef uri="antlib:org.moxie">
+		<classpath location="${moxie.dir}/${moxie.jar}" />
+	</taskdef>
+	
+	<!-- Project directories -->
+	<property name="project.src.dir" value="${basedir}/src/main/java" />	
+	<property name="project.resources.dir" value="${basedir}/src/main/resources" />	
+	<property name="project.distrib.dir" value="${basedir}/src/main/distrib" />
+	
+	<!--
+		~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+		Initialize Moxie and setup build properties
+		~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+	-->
+	<target name="prepare">
+	
+		<!-- Setup Ant build from build.moxie and resolve dependencies.
+		     If it exists, build.properties is automatically loaded.
+		     Explicitly set mxroot allowing CI servers to override the default. -->
+		<mx:init verbose="no" mxroot="${moxie.dir}" />
+				
+		<!-- Set Ant project properties -->
+		<property name="distribution.zipfile" value="gitblit-${project.version}.zip" />
+		<property name="distribution.tgzfile" value="gitblit-${project.version}.tar.gz" />
+		<property name="distribution.warfile" value="gitblit-${project.version}.war" />
+		<property name="fedclient.zipfile" value="fedclient-${project.version}.zip" />
+		<property name="manager.zipfile" value="manager-${project.version}.zip" />
+		<property name="authority.zipfile" value="authority-${project.version}.zip" />
+		<property name="gbapi.zipfile" value="gbapi-${project.version}.zip" />
+		<property name="express.zipfile" value="express-${project.version}.zip" />
+		
+		<!-- Download links -->
+		<property name="gc.url" value="http://code.google.com/p/gitblit/downloads/detail?name=" />
+	</target>
+
+	
+	<!--
+		~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+		Cleanup all build artifacts and directories
+		~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+	-->
+	<target name="clean" depends="prepare" description="Cleanup all build artifacts and directories">
+		<!-- cleanup legacy build structure -->
+		<!-- this can be eliminated after 1.3.0 release -->
 		<delete>
 			<fileset dir="${basedir}">
 				<include name="*.zip" />
@@ -119,33 +70,47 @@
 				<include name="*.jar" />
 			</fileset>
 		</delete>
+		<delete dir="${basedir}/deploy" failonerror="false" />
+		<delete dir="${basedir}/express" failonerror="false" />
+		<delete dir="${basedir}/jar" failonerror="false" />
+		<delete dir="${basedir}/javadoc" failonerror="false" />
+		<delete dir="${basedir}/site" failonerror="false" />
+		<delete dir="${basedir}/temp" failonerror="false" />
+		<delete dir="${basedir}/war" failonerror="false" />
 		
-		<!-- copy required distribution files to data folder -->
+		<!-- Clean build and target directories -->
+		<mx:clean />
+
+	</target>
+
+	
+	<!--
+		~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+		Setup
+		~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+	-->
+	<target name="setup" depends="prepare" description="Setup up project">
+
+		<!-- copy distrib/data to project data directory -->
 		<mkdir dir="${basedir}/data" />
 		<copy todir="${basedir}/data" overwrite="false">
-			<fileset dir="${basedir}/distrib">
-				<include name="gitblit.properties" />
-				<include name="users.conf" />
-				<include name="projects.conf" />
-			</fileset>
+			<fileset dir="${project.distrib.dir}/data" />
 		</copy>
 		
-		<!-- copy required distribution files to project folder -->
-		<mkdir dir="${basedir}/data/certs" />
-		<copy todir="${basedir}/data/certs" overwrite="false">
-			<fileset dir="${basedir}/distrib">
-				<include name="authority.conf" />
-				<include name="*.tmpl" />
-			</fileset>
-		</copy>
+		<!-- copy gitblit.properties to the source directory.
+		     this file is only used for parsing setting descriptions. -->
+		<copy tofile="${project.src.dir}/reference.properties" overwrite="true"
+			file="${project.distrib.dir}/data/gitblit.properties" />
+
+		<!-- copy clientapps.json to the source directory.
+		     this file is only used if a local file is not provided. -->
+		<copy tofile="${project.src.dir}/clientapps.json" overwrite="true"
+			file="${project.distrib.dir}/data/clientapps.json" />
 		
-		<!-- copy required distribution files to project folder -->
-		<mkdir dir="${basedir}/data/groovy" />
-		<copy todir="${basedir}/data/groovy" overwrite="false">
-			<fileset dir="${basedir}/distrib/groovy" />
-		</copy>
-		
-		<!-- upgrade existing workspace to data folder -->
+		<!-- 
+			upgrade existing workspace to data directory
+			this code can be eliminated after 1.3.0 release
+		 -->
 		<move todir="${basedir}/data" overwrite="true" failonerror="false">
 			<fileset dir="${basedir}">
 				<include name="users.conf" />
@@ -164,274 +129,126 @@
 		<move todir="${basedir}/data/proposals" overwrite="true" failonerror="false">
 			<fileset dir="${basedir}/proposals" />
 		</move>
-		<delete dir="${basedir}/javadoc" failonerror="false" />
-		<delete dir="${basedir}/site" failonerror="false" />
-		<delete dir="${basedir}/temp" failonerror="false" />
-
-		<!-- copy gitblit.properties to the WEB-INF folder.
-		     this file is only used for parsing setting descriptions. -->
-		<copy tofile="${basedir}/src/WEB-INF/reference.properties" overwrite="true"
-			file="${basedir}/distrib/gitblit.properties" />
-
-		<!-- Compile the build tool and execute it.
-			 This downloads missing compile-time dependencies from Maven. -->
-
-		<delete dir="${project.build.dir}" />
-		<mkdir dir="${project.build.dir}" />
-		<javac debug="true" srcdir="${basedir}/src" destdir="${project.build.dir}" includeantruntime="false">
-			<include name="com/gitblit/build/Build.java" />			
-			<include name="com/gitblit/Constants.java" />
-			<include name="com/gitblit/utils/StringUtils.java" />			
-		</javac>
-
-		<java classpath="${project.build.dir}" classname="com.gitblit.build.Build" failonerror="true">
-			<syspropertyset id="proxy.properties">
-				<propertyref prefix="java.net.useSystemProxies"/>
-				<propertyref prefix="http."/>
-				<propertyref prefix="https."/>
-				<propertyref prefix="ftp."/>
-				<propertyref prefix="socksProxy"/>
-			</syspropertyset>
-		</java>
-
-		<!-- Compile Project -->
-		<path id="master-classpath">
-			<fileset dir="${basedir}/ext">
-				<include name="*.jar" />
-			</fileset>
-			<pathelement path="${project.build.dir}" />				
-		</path>
-		<javac debug="true" destdir="${project.build.dir}" failonerror="false" includeantruntime="false">
-			<src path="${basedir}/src" />
-			<classpath refid="master-classpath" />
-		</javac>
-		<copy todir="${project.build.dir}">
-			<fileset dir="${basedir}/src" excludes="**/*.java,**/thumbs.db" />
-		</copy>
 	</target>
 
+
+	<!--
+		~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+		Compile
+		~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+	-->
+	<target name="compile" depends="setup" description="compiles Gitblit from source">
+		
+		<!-- Generate the Keys class from the properties file -->
+		<mx:keys propertiesfile="${project.distrib.dir}/data/gitblit.properties"
+			 	outputclass="com.gitblit.Keys"
+			 	todir="${project.src.dir}" />
+
+		<!-- Compile project -->
+		<mx:javac scope="compile" clean="true" />
+		
+	</target>
+
+		
+	<!--
+		~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+		Report the compile dependencies on the console
+		~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+	-->
+	<target name="report" depends="prepare" description="generate dependency report">
+		
+		<!-- Report compile dependencies to the console -->
+		<mx:report scope="compile" destfile="${project.targetDirectory}/dependencies.txt" />
+		
+	</target>
+
+	
+	<!--
+		~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+		Test
+		~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+	-->
+	<target name="test" depends="compile" description="compiles Gitblit from source and runs unit tests">
+		
+		<!-- Compile unit tests -->
+		<mx:javac scope="test" />
+		
+		<!-- Run unit tests -->
+		<mx:test failonerror="true" />
+		
+	</target>
+
+
+	<!--
+		~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+		Run Gitblit GO
+		~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+	-->
+	<target name="run" depends="compile" description="Run Gitblit GO">
+		
+		<!-- run the mainclass in a separate JVM -->
+		<mx:run fork="true" />
+		
+	</target>			
+			
 	
 	<!--
 		~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 		Build Gitblit GO
 		~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 	-->
-	<target name="buildGO" depends="compile,buildAuthority" description="Build Gitblit GO distribution">
+	<target name="buildGO" depends="compile" description="Build Gitblit GO distribution">
 		
-		<echo>Building Gitblit GO ${gb.version}</echo>
+		<echo>Building Gitblit GO ${project.version}</echo>
 
-		<!-- Delete the deploy folder -->
-		<delete dir="${project.deploy.dir}" />
+		<local name="go.dir" />
+		<property name="go.dir" value="${project.outputDirectory}/go" />	
+		<delete dir="${go.dir}" />
 
-		<!-- Create deployment folder structure -->
-		<mkdir dir="${project.deploy.dir}" />
-		<copy todir="${project.deploy.dir}">
-			<fileset dir="${basedir}/distrib">
-				<include name="**/*" />
-				<exclude name="federation.properties" />
-				<exclude name="openshift.mkd" />
-				<exclude name="authority.conf" />
-				<exclude name="users.conf" />
-				<exclude name="projects.conf" />
-				<exclude name="gitblit.properties" />
-				<exclude name="*.tmpl" />
-				<exclude name="groovy/**" />
-			</fileset>
-			<fileset dir="${basedir}">
+		<prepareDataDirectory toDir="${go.dir}/data" />
+		
+		<!-- Build jar -->
+		<mx:jar destfile="${go.dir}/gitblit.jar" includeresources="true">
+			<mainclass name="com.gitblit.GitBlitServer" />
+			<launcher paths="ext" />
+		</mx:jar>
+
+		<!-- Generate the docs for the GO build -->
+		<generateDocs toDir="${go.dir}/docs" />
+		
+		<!-- Create GO Windows Zip deployment -->
+		<mx:zip basedir="${go.dir}">
+			<!-- LICENSE and NOTICE -->
+			<fileset dir="${basedir}" >
 				<include name="LICENSE" />
 				<include name="NOTICE" />
-			</fileset>			
-		</copy>
-		
-		<!-- Copy the supported Groovy hook scripts -->
-		<mkdir dir="${project.deploy.dir}/data/groovy" />
-		<copy todir="${project.deploy.dir}/data/groovy">
-			<fileset dir="${basedir}/distrib/groovy">
-				<include name="sendmail.groovy" />
-				<include name="sendmail-html.groovy" />
-				<include name="jenkins.groovy" />
-				<include name="protect-refs.groovy" />
-				<include name="fogbugz.groovy" />
-				<include name="thebuggenie.groovy" />
 			</fileset>
-		</copy>
-		
-		<copy tofile="${project.deploy.dir}/authority.jar" file="${project.target.dir}/authority-${gb.version}.jar" />
-		
-		<!-- Prepare the data folder -->
-		<mkdir dir="${project.deploy.dir}/data"/>
-		<copy todir="${project.deploy.dir}/data">
-			<fileset dir="${basedir}/distrib">
-				<include name="users.conf" />
-				<include name="projects.conf" />
-				<include name="gitblit.properties" />
-			</fileset>
-		</copy>
-						
-		<!-- Certificate templates -->
-		<mkdir dir="${project.deploy.dir}/data/certs"/>
-		<mkdir dir="${project.deploy.dir}/data/certs"/>
-		<copy todir="${project.deploy.dir}/data/certs">
-			<fileset dir="${basedir}/distrib">
-				<include name="*.tmpl" />
-				<include name="authority.conf" />
-			</fileset>
-		</copy>
-						
-		<!-- Build jar -->
-		<jar jarfile="${project.deploy.dir}/${project.jar}">
-			<fileset dir="${project.build.dir}">
-				<include name="**/*" />
-				<exclude name="com/gitblit/client/**" />				
-			</fileset>
-			<fileset dir="${project.resources.dir}">
-				<exclude name="thumbs.db" />
-			</fileset>
-			<manifest>
-				<attribute name="Main-Class" value="${project.mainclass}" />
-			</manifest>
-		</jar>
+			<!-- Windows distrib files -->
+			<zipfileset dir="${project.distrib.dir}/win" />
+			<!-- Gitblit Authority data -->
+			<zipfileset dir="${project.distrib.dir}/data/certs" prefix="data/certs" />
+			<!-- include all dependencies -->
+			<dependencies prefix="ext" />
+		</mx:zip>
 
-		<!-- Gitblit library dependencies -->
-		<mkdir dir="${project.deploy.dir}/ext"/>
-		<copy todir="${project.deploy.dir}/ext">
-			<fileset dir="${basedir}/ext">
-				<exclude name="src/**" />
-				<exclude name="junit*.jar" />
-				<exclude name="hamcrest*.jar" />
-				<exclude name="commons-net*.jar" />
+		<!-- Create GO Linux/OSX tar.gz deployment -->
+		<mx:tar basedir="${go.dir}" longfile="gnu" compression="gzip">
+			<!-- LICENSE and NOTICE -->
+			<fileset dir="${basedir}" >
+				<include name="LICENSE" />
+				<include name="NOTICE" />
 			</fileset>
-		</copy>
-		
-		<!-- Build the docs for the deploy -->
-		<antcall target="buildDocs" inheritall="true" inheritrefs="true">
-			<param name="docs.output.dir" value="${project.deploy.dir}/docs" />
-		</antcall>
-		
-		<!-- Create Zip deployment -->		
-		<zip destfile="${project.target.dir}/${distribution.zipfile}">
-			<fileset dir="${project.deploy.dir}">
-				<include name="**/*" />
-			</fileset>
-		</zip>
+			<!-- Linux/OSX distrib files -->
+			<tarfileset dir="${project.distrib.dir}/linux" filemode="755" />
+			<!-- Gitblit Authority data -->
+			<zipfileset dir="${project.distrib.dir}/data/certs" prefix="data/certs" />
+			<!-- include all dependencies -->
+			<dependencies prefix="ext" />
+		</mx:tar>		
 
 	</target>
 	
 	
-	<!--
-		~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-		Build Gitblit Docs which are bundled with GO and WAR downloads
-		~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-	-->
-	<target name="buildDocs">
-	<!-- Build Docs -->
-			<mkdir dir="${docs.output.dir}" />
-			<copy todir="${docs.output.dir}">
-				<!-- Copy selected Gitblit resources -->
-				<fileset dir="${project.resources.dir}">
-					<include name="bootstrap/**/*" />
-					<include name="gitblit.css" />
-					<include name="gitblt_25_white.png" />
-					<include name="gitblt-favicon.png" />
-					<include name="lock_go_16x16.png" />
-					<include name="lock_pull_16x16.png" />
-					<include name="shield_16x16.png" />
-					<include name="cold_16x16.png" />
-					<include name="bug_16x16.png" />
-					<include name="book_16x16.png" />
-					<include name="blank.png" />
-					<include name="federated_16x16.png" />
-					<include name="arrow_page.png" />
-				</fileset>
-
-				<!-- Copy Doc images -->
-				<fileset dir="${basedir}/docs">
-					<include name="*.png" />
-					<include name="*.gif" />
-				</fileset>
-			</copy>
-
-			<!-- Copy google-code-prettify -->
-			<mkdir dir="${docs.output.dir}/prettify" />
-			<copy todir="${docs.output.dir}/prettify">
-				<fileset dir="${basedir}/src/com/gitblit/wicket/pages/prettify">
-					<exclude name="thumbs.db" />
-				</fileset>
-			</copy>
-
-			<!-- Build deployment doc pages -->
-			<java classpath="${project.build.dir}" classname="com.gitblit.build.BuildSite">
-				<classpath refid="master-classpath" />
-				<arg value="--sourceFolder" />
-				<arg value="${basedir}/docs" />
-
-				<arg value="--outputFolder" />
-				<arg value="${docs.output.dir}" />
-
-				<arg value="--pageHeader" />
-				<arg value="${basedir}/docs/doc_header.html" />
-
-				<arg value="--pageFooter" />
-				<arg value="${basedir}/docs/doc_footer.html" />
-
-				<arg value="--skip" />
-				<arg value="screenshots" />
-
-				<arg value="--skip" />
-				<arg value="releases" />
-
-				<arg value="--alias" />
-				<arg value="index=overview" />
-
-				<arg value="--alias" />
-				<arg value="properties=settings" />
-
-				<arg value="--substitute" />
-				<arg value="%VERSION%=${gb.version}" />
-
-				<arg value="--substitute" />
-				<arg value="%GO%=${distribution.zipfile}" />
-
-				<arg value="--substitute" />
-				<arg value="%WAR%=${distribution.warfile}" />
-
-				<arg value="--substitute" />
-				<arg value="%FEDCLIENT%=${fedclient.zipfile}" />
-
-				<arg value="--substitute" />
-				<arg value="%MANAGER%=${manager.zipfile}" />
-
-				<arg value="--substitute" />
-				<arg value="%API%=${gbapi.zipfile}" />
-
-				<arg value="--substitute" />
-				<arg value="%EXPRESS%=${express.zipfile}" />
-
-				<arg value="--substitute" />
-				<arg value="%BUILDDATE%=${gb.versionDate}" />
-
-				<arg value="--substitute" />
-				<arg value="%JGIT%=${jgit.version}" />
-
-				<arg value="--properties" />
-				<arg value="%PROPERTIES%=${basedir}/distrib/gitblit.properties" />
-
-				<arg value="--nomarkdown" />
-				<arg value="%BEGINCODE%:%ENDCODE%" />
-
-				<arg value="--substitute" />
-				<arg value="&quot;%BEGINCODE%=&lt;pre class='prettyprint lang-java'&gt;&quot;" />
-
-				<arg value="--substitute" />
-				<arg value="%ENDCODE%=&lt;/pre&gt;" />
-				
-				<arg value="--regex" />
-				<arg value="&quot;\b(issue)(\s*[#]?|-){0,1}(\d+)\b!!!&lt;a href='http://code.google.com/p/gitblit/issues/detail?id=$3'&gt;issue $3&lt;/a&gt;&quot;" />
-		
-			</java>
-	</target>
-	
-				
 	<!--
 		~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 		Build Gitblit WAR
@@ -439,180 +256,72 @@
 	-->
 	<target name="buildWAR" depends="compile" description="Build Gitblit WAR">
 		
-		<echo>Building Gitblit WAR ${gb.version}</echo>
+		<echo>Building Gitblit WAR ${project.version}</echo>
+
+		<local name="war.dir" />
+		<property name="war.dir" value="${project.outputDirectory}/war" />
+		<delete dir="${war.dir}" />
 		
-		<delete dir="${project.war.dir}" />
+		<local name="webinf" />
+		<property name="webinf" value="${war.dir}/WEB-INF" />
 
-		<!-- Copy web.xml and users.conf to WEB-INF -->
-		<copy todir="${project.war.dir}/WEB-INF">
-			<fileset dir="${basedir}/src/WEB-INF">
-			 	<include name="web.xml" />
-			</fileset>
-			<fileset dir="${basedir}">
-				<include name="LICENSE" />
-				<include name="NOTICE" />
-			</fileset>
-		</copy>
-		
-		<!-- Copy gitblit.properties as reference.properties -->
-		<copy tofile="${project.war.dir}/WEB-INF/reference.properties" 
-			file="${basedir}/distrib/gitblit.properties"/>
-		
-		<!-- Build the docs for the WAR build -->
-		<antcall target="buildDocs" inheritall="true" inheritrefs="true">
-			<param name="docs.output.dir" value="${project.war.dir}/WEB-INF/docs" />
-		</antcall>
+		<!-- Generate the docs for the WAR build -->
+		<generateDocs toDir="${webinf}/docs" />
 
-		<!-- Copy users.conf to WEB-INF/data -->
-		<mkdir dir="${project.war.dir}/WEB-INF/data" />
-		<copy todir="${project.war.dir}/WEB-INF/data">
-			<fileset dir="${basedir}/distrib">
-			 	<include name="users.conf" />
-				<include name="projects.conf" />
-			 	<include name="gitblit.properties" />
-			</fileset>
-		</copy>
+		<!-- Prepare the data directory -->
+		<prepareDataDirectory toDir="${webinf}/data" />
 
-		<!-- Copy the supported Groovy hook scripts -->
-		<mkdir dir="${project.war.dir}/WEB-INF/data/groovy" />
-		<copy todir="${project.war.dir}/WEB-INF/data/groovy">
-			<fileset dir="${basedir}/distrib/groovy">
-				<include name="sendmail.groovy" />
-				<include name="sendmail-html.groovy" />
-				<include name="jenkins.groovy" />
-				<include name="protect-refs.groovy" />
-				<include name="fogbugz.groovy" />
-				<include name="thebuggenie.groovy" />
-			</fileset>
-		</copy>
+		<!-- Build the WAR web.xml from the prototype web.xml -->
+		<mx:webxml sourcefile="${project.src.dir}/WEB-INF/web.xml" destfile="${webinf}/web.xml">
+			<replace token="@gb.version@" value="${project.version}" />
+		</mx:webxml>
 
-		<!-- Build the WAR web.xml from the prototype web.xml --> 
-		<java classpath="${project.build.dir}" classname="com.gitblit.build.BuildWebXml">
-			<classpath refid="master-classpath" />
-			
-			<arg value="--sourceFile" />
-			<arg value="${basedir}/src/WEB-INF/web.xml" />
-					
-			<arg value="--destinationFile" />
-			<arg value="${project.war.dir}/WEB-INF/web.xml" />
-			
-		</java>
-
-		<!-- Gitblit resources -->
-		<copy todir="${project.war.dir}">
-			<fileset dir="${project.resources.dir}">
-				<exclude name="thumbs.db" />
-			</fileset>
-		</copy>
-		
-		<!-- Gitblit library dependencies -->
-		<mkdir dir="${project.war.dir}/WEB-INF/lib"/>
-		<copy todir="${project.war.dir}/WEB-INF/lib">
-			<fileset dir="${basedir}/ext">
-				<exclude name="src/**" />
-				<exclude name="jcommander*.jar" />
-				<exclude name="jetty*.jar" />
-				<exclude name="junit*.jar" />
-				<exclude name="hamcrest*.jar" />
-				<exclude name="servlet*.jar" />
-				<exclude name="javax.servlet*.jar" />
-			</fileset>
-		</copy>
-
-		<!-- Gitblit classes -->
-		<mkdir dir="${project.war.dir}/WEB-INF/classes"/>
-		<copy todir="${project.war.dir}/WEB-INF/classes">
-			<fileset dir="${project.build.dir}">
-				<exclude name="WEB-INF/" />
-				<exclude name="com/gitblit/tests/" />
-				<exclude name="com/gitblit/build/**" />
-				<exclude name="com/gitblit/client/**" />
-				<exclude name="com/gitblit/AddIndexedBranch*.class" />
-				<exclude name="com/gitblit/GitBlitServer*.class" />
-				<exclude name="com/gitblit/Launcher*.class" />
-				<exclude name="com/gitblit/authority/**" />
-			</fileset>
-		</copy>
+		<!-- Gitblit jar -->
+		<mx:genjar destfile="${webinf}/lib/gitblit.jar" includeresources="false" excludeclasspathjars="true">
+			<!-- Specify all web.xml servlets and filters -->
+			<class name="com.gitblit.GitBlit" />
+			<class name="com.gitblit.Keys" />
+			<class name="com.gitblit.DownloadZipFilter" />
+			<class name="com.gitblit.DownloadZipServlet" />
+			<class name="com.gitblit.EnforceAuthenticationFilter" />
+			<class name="com.gitblit.FederationServlet" />
+			<class name="com.gitblit.GitFilter" />
+			<class name="com.gitblit.git.GitServlet" />
+			<class name="com.gitblit.LogoServlet" />
+			<class name="com.gitblit.PagesFilter" />
+			<class name="com.gitblit.PagesServlet" />
+			<class name="com.gitblit.RobotsTxtServlet" />
+			<class name="com.gitblit.RpcFilter" />
+			<class name="com.gitblit.RpcServlet" />
+			<class name="com.gitblit.SyndicationFilter" />
+			<class name="com.gitblit.SyndicationServlet" />
+			<class name="com.gitblit.SparkleShareInviteServlet" />
+			<class name="com.gitblit.wicket.GitBlitWebApp" />
+			<!-- Manually include alternative User Services -->
+			<class name="com.gitblit.LdapUserService" />
+			<class name="com.gitblit.RedmineUserService" />
+			<class name="com.gitblit.SalesforceUserService" />
+			<class name="com.gitblit.WindowsUserService" />
+		</mx:genjar>
 
 		<!-- Build the WAR file -->
-		<jar basedir="${project.war.dir}" destfile="${project.target.dir}/${distribution.warfile}" compress="true" />
-	</target>
-
-	
-	<!--
-		~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-		Build Gitblit JAR for usage in other projects plug-ins (i.e. Gerrit)
-		~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-	-->
-	<target name="buildJAR" depends="compile" description="Build Gitblit JAR">
-
-		<echo>Building Gitblit JAR ${gb.version}</echo>
-
-		<delete dir="${project.jar.dir}" />
-
-		<!-- Gitblit classes -->
-		<mkdir dir="${project.jar.dir}"/>
-		<copy todir="${project.jar.dir}">
-			<fileset dir="${project.build.dir}">
-				<exclude name="WEB-INF/" />
-				<exclude name="com/gitblit/tests/" />
-				<exclude name="com/gitblit/build/**" />
-				<exclude name="com/gitblit/client/**" />
-				<exclude name="com/gitblit/authority/**" />
-				<exclude name="com/gitblit/AddIndexedBranch*.class" />
-				<exclude name="com/gitblit/GitBlitServer*.class" />
-				<exclude name="com/gitblit/Launcher*.class" />
-			</fileset>
-		</copy>
-		<copy todir="${project.jar.dir}/static">
+		<mx:zip basedir="${war.dir}" destfile="${project.targetDirectory}/${distribution.warfile}" compress="true" >
+			<!-- Resources in root -->
 			<fileset dir="${project.resources.dir}">
 				<exclude name="thumbs.db" />
+				<exclude name="*.mkd" />
 			</fileset>
-		</copy>
-
-		<!-- Build the JAR file -->
-		<jar basedir="${project.jar.dir}" destfile="${distribution.jarfile}" compress="true" />
+			<!-- WEB-INF directory -->
+			<zipfileset prefix="WEB-INF" dir="${basedir}" >
+				<include name="LICENSE" />
+				<include name="NOTICE" />
+			</zipfileset>
+			<zipfileset prefix="WEB-INF" file="${project.compileOutputDirectory}/WEB-INF/weblogic.xml" />
+			<!-- include "war" tagged dependencies -->
+			<dependencies prefix="WEB-INF/lib" tag="war" />
+		</mx:zip>
 	</target>
 
-	<!--
-		~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-		Build pom.xml for GitBlit JAR Maven module
-		~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-	-->
-	<target name="buildMaven" depends="buildJAR" description="Build pom.xml for Gitblit JAR Maven module">
-		<copy tofile="${distribution.pomfile}" file="${distribution.pomfileTmplt}"/>
-		<replace file="${distribution.pomfile}" token="@gb.version@" value="${gb.version}" />
-	</target>
-
-	<!--
-		~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-		Install Gitblit JAR for usage as Maven module
-		~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-	-->
-	<target name="installMaven" depends="buildMaven" description="Install Gitblit JAR as Maven module">
-		<exec executable="mvn">
-			<arg value="install:install-file" />
-			<arg value="-Dfile=${distribution.jarfile}" />
-			<arg value="-DpomFile=${distribution.pomfile}" />
-			<arg value="-DcreateChecksum=true" />
-		</exec>
-	</target>
-
-	<!--
-    	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-    	Upload Gitblit JAR to remote Maven repository
-    	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-    -->
-	<target name="uploadMaven" depends="buildJAR" description="Upload Gitblit JAR to remote Maven repository">
-		<exec executable="mvn">
-			<arg value="deploy:deploy-file" />
-			<arg value="-Dfile=${distribution.jarfile}" />
-			<arg value="-DpomFile=${distribution.pomfile}" />
-			<arg value="-Durl=${project.maven.repo.url}" />
-			<arg value="-DrepositoryId=${project.maven.repo.id}" />
-			<arg value="-DcreateChecksum=true" />
-		</exec>
-	</target>
 
 	<!-- 
 		~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -620,156 +329,116 @@
 		~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 	-->
 	<target name="buildFederationClient" depends="compile" description="Builds the stand-alone Gitblit federation client">
-		<echo>Building Gitblit Federation Client ${gb.version}</echo>
+		<echo>Building Gitblit Federation Client ${project.version}</echo>
 	
-		<genjar jarfile="${project.target.dir}/fedclient.jar">
-			<class name="com.gitblit.FederationClientLauncher" />
-			<resource file="${project.build.dir}/log4j.properties" />
-			<classfilter>
-				<exclude name="org.apache." />
-				<exclude name="org.bouncycastle." />
-				<exclude name="org.eclipse." />
-				<exclude name="org.slf4j." />
-				<exclude name="com.beust." />
-				<exclude name="com.google." />
-				<exclude name="com.unboundid." />
-			</classfilter>
-			<classpath refid="master-classpath" />
-			<manifest>
-				<attribute name="Main-Class" value="com.gitblit.FederationClientLauncher" />
-				<attribute name="Specification-Version" value="${gb.version}" />
-				<attribute name="Release-Date" value="${gb.versionDate}" />
-			</manifest>
-		</genjar>
+		<!-- generate jar by traversing the class hierarchy of the specified
+			 classes, exclude any classes in classpath jars -->
+		<mx:genjar tag="" includeresources="false" excludeClasspathJars="true"
+			destfile="${project.targetDirectory}/fedclient.jar">
+			<mainclass name="com.gitblit.FederationClient" />
+			<class name="com.gitblit.Keys" />
+			<launcher paths="ext" />
+			<resource file="${project.compileOutputDirectory}/log4j.properties" />
+		</mx:genjar>
 		
 		<!-- Build the federation client zip file -->
-		<zip destfile="${project.target.dir}/${fedclient.zipfile}">
+		<mx:zip destfile="${project.targetDirectory}/${fedclient.zipfile}">
 			<fileset dir="${basedir}">
 				<include name="LICENSE" />
 				<include name="NOTICE" />
 			</fileset>
-			<fileset dir="${project.target.dir}">
+			<fileset dir="${project.targetDirectory}">
 				<include name="fedclient.jar" />
 			</fileset>
-			<fileset dir="${basedir}/distrib">
+			<fileset dir="${project.distrib.dir}">
 				<include name="federation.properties" />
 			</fileset>
-		</zip>
+			<!-- include "fedclient" tagged dependencies -->
+			<dependencies prefix="ext" tag="fedclient" />
+		</mx:zip>
 		
 		<!-- Cleanup -->
-		<delete file="${project.target.dir}/fedclient.jar" />
+		<delete file="${project.targetDirectory}/fedclient.jar" />
+		
 	</target>
 
 
 	<!-- 
 		~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-		Build a Gitblit filesystem for deployment to RedHat OpenShif Expresst
+		Build a Gitblit filesystem for deployment to RedHat OpenShift Express
 		~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 	-->
 	<target name="buildExpress" depends="compile" description="Build exploded WAR file suitable for deployment to OpenShift Express">
-		<echo>Building Gitblit Express for RedHat OpenShift ${gb.version}</echo>
+		<echo>Building Gitblit Express for RedHat OpenShift ${project.version}</echo>
 		
-		<delete dir="${project.express.dir}" />
+		<local name="express.dir" />
+		<property name="express.dir" value="${project.outputDirectory}/express" />		
+		<delete dir="${express.dir}" />
 		
 		<!-- Create the OpenShift filesystem -->
-		<property name="deployments.root" value="${project.express.dir}/deployments/ROOT.war"/>
+		<local name="deployments.root" />
+		<property name="deployments.root" value="${express.dir}/deployments/ROOT.war"/>
 		<mkdir dir="${deployments.root}" />
-		<touch file="${project.express.dir}/deployments/ROOT.war.dodeploy" />
+		<touch file="${express.dir}/deployments/ROOT.war.dodeploy" />
 
-		<!-- Copy the Gitblit OpenShift readme file -->
-		<copy tofile="${project.express.dir}/README.gitblit" 
-			file="${basedir}/distrib/openshift.mkd"/>
+		<local name="webinf" />
+		<property name="webinf" value="${deployments.root}/WEB-INF" />
 
-		<!-- Copy LICENSE and NOTICE to WEB-INF -->
-		<copy todir="${deployments.root}/WEB-INF">
+		<!-- Prepare the data directory -->
+		<prepareDataDirectory toDir="${webinf}/data" />
+					
+		<!-- Build the Express web.xml from the prototype web.xml and gitblit.properties -->
+		<!-- THIS FILE IS NOT OVERRIDDEN ONCE IT IS BUILT!!! -->
+		<mx:webxml sourcefile="${project.src.dir}/WEB-INF/web.xml" destfile="${webinf}/web.xml"
+			propertiesFile="${project.distrib.dir}/data/gitblit.properties"
+			skip="server.*">
+			<replace token="@gb.version@" value="${project.version}" />
+		</mx:webxml>
+
+		<!-- Gitblit classes -->
+		<mx:genjar destfile="${webinf}/lib/gitblit.jar" includeresources="false" excludeclasspathjars="true">
+			<class name="com.gitblit.Keys" />
+			<!-- Specify all web.xml servlets and filters -->
+			<class name="com.gitblit.GitBlit" />
+			<class name="com.gitblit.DownloadZipFilter" />
+			<class name="com.gitblit.DownloadZipServlet" />
+			<class name="com.gitblit.EnforceAuthenticationFilter" />
+			<class name="com.gitblit.FederationServlet" />
+			<class name="com.gitblit.GitFilter" />
+			<class name="com.gitblit.git.GitServlet" />
+			<class name="com.gitblit.LogoServlet" />
+			<class name="com.gitblit.PagesFilter" />
+			<class name="com.gitblit.PagesServlet" />
+			<class name="com.gitblit.RobotsTxtServlet" />
+			<class name="com.gitblit.RpcFilter" />
+			<class name="com.gitblit.RpcServlet" />
+			<class name="com.gitblit.SyndicationFilter" />
+			<class name="com.gitblit.SyndicationServlet" />
+			<class name="com.gitblit.SparkleShareInviteServlet" />
+			<class name="com.gitblit.wicket.GitBlitWebApp" />
+			<!-- Manually include alternative User Services -->
+			<class name="com.gitblit.LdapUserService" />
+			<class name="com.gitblit.RedmineUserService" />
+			<class name="com.gitblit.SalesforceUserService" />
+			<class name="com.gitblit.WindowsUserService" />
+		</mx:genjar>
+
+		<!-- Build Express Zip file -->
+		<mx:zip basedir="${express.dir}" destfile="${project.targetDirectory}/${express.zipfile}">
 			<fileset dir="${basedir}">
 				<include name="LICENSE" />
 				<include name="NOTICE" />
 			</fileset>
-		</copy>
-
-		<!-- Copy gitblit.properties as reference.properties -->
-		<copy tofile="${deployments.root}/WEB-INF/reference.properties" 
-			file="${basedir}/distrib/gitblit.properties"/>
-
-		<!-- Copy users.conf and gitblit.properties -->
-		<mkdir dir="${deployments.root}/WEB-INF/data" />
-		<copy todir="${deployments.root}/WEB-INF/data">
-			<fileset dir="${basedir}/distrib">
-				<include name="users.conf" />
-				<include name="projects.conf" />
-				<include name="gitblit.properties" />
-			</fileset>
-		</copy>
-					
-		<!-- Copy the supported Groovy hook scripts -->
-		<mkdir dir="${deployments.root}/WEB-INF/data/groovy" />
-		<copy todir="${deployments.root}/WEB-INF/data/groovy">
-			<fileset dir="${basedir}/distrib/groovy">
-				<include name="sendmail.groovy" />
-				<include name="sendmail-html.groovy" />
-				<include name="jenkins.groovy" />
-				<include name="protect-refs.groovy" />
-				<include name="fogbugz.groovy" />
-				<include name="thebuggenie.groovy" />
-			</fileset>
-		</copy>
-					
-		<!-- Build the WAR web.xml from the prototype web.xml and gitblit.properties -->
-		<!-- THIS FILE IS NOT OVERRIDDEN ONCE IT IS BUILT!!! -->
-		<java classpath="${project.build.dir}" classname="com.gitblit.build.BuildWebXml">
-			<classpath refid="master-classpath" />
-
-			<arg value="--sourceFile" />
-			<arg value="${basedir}/src/WEB-INF/web.xml" />
-
-			<arg value="--destinationFile" />
-			<arg value="${deployments.root}/WEB-INF/web.xml" />
-
-			<arg value="--propertiesFile" />
-			<arg value="${basedir}/distrib/gitblit.properties" />
-		</java>
-
-		<!-- Gitblit resources -->
-		<copy todir="${deployments.root}">
-			<fileset dir="${project.resources.dir}">
+			<!-- README -->
+			<zipfileset fullpath="README.gitblit" file="${project.siteSourceDirectory}/openshift.mkd" />
+			<!-- resources -->
+			<zipfileset prefix="deployments/ROOT.war" dir="${project.resources.dir}">
 				<exclude name="thumbs.db" />
-			</fileset>
-		</copy>
-
-		<!-- Gitblit library dependencies -->
-		<mkdir dir="${deployments.root}/WEB-INF/lib"/>
-		<copy todir="${deployments.root}/WEB-INF/lib">
-			<fileset dir="${basedir}/ext">
-				<exclude name="src/**" />
-				<exclude name="jcommander*.jar" />
-				<exclude name="jetty*.jar" />
-				<exclude name="junit*.jar" />
-				<exclude name="hamcrest*.jar" />
-				<exclude name="servlet*.jar" />
-				<exclude name="javax.servlet*.jar" />
-				<exclude name="jsslutils*.jar" />
-				<exclude name="jcalendar*.jar" />
-			</fileset>
-		</copy>
-
-		<!-- Gitblit classes -->
-		<jar destfile="${deployments.root}/WEB-INF/lib/gitblit-${gb.version}.jar">
-			<fileset dir="${project.build.dir}">
-				<exclude name="WEB-INF/" />
-				<exclude name="com/gitblit/tests/" />
-				<exclude name="com/gitblit/build/**" />
-				<exclude name="com/gitblit/client/**" />
-				<exclude name="com/gitblit/GitBlitServer*.class" />
-				<exclude name="com/gitblit/Launcher*.class" />
-				<exclude name="com/gitblit/authority/**" />
-			</fileset>
-		</jar>
-
-		<!-- Build Express Zip file -->
-		<zip destfile="${project.target.dir}/${express.zipfile}">
-			<fileset dir="${project.express.dir}" />
-		</zip>
+				<exclude name="*.mkd" />
+			</zipfileset>
+			<!-- include "war" tagged dependencies -->
+			<dependencies prefix="deployments/ROOT.war/WEB-INF/lib" tag="war" />
+		</mx:zip>
 
 	</target>
 
@@ -780,72 +449,69 @@
 		~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 	-->
 	<target name="buildManager" depends="compile" description="Builds the stand-alone Gitblit Manager">
-		<echo>Building Gitblit Manager ${gb.version}</echo>
+		<echo>Building Gitblit Manager ${project.version}</echo>
 
-		<genjar jarfile="${project.target.dir}/manager-${gb.version}.jar">
-			<resource file="${basedir}/src/com/gitblit/client/splash.png" />
-			<resource file="${basedir}/resources/gitblt-favicon.png" />
-			<resource file="${basedir}/resources/gitweb-favicon.png" />
-			<resource file="${basedir}/resources/git-orange-16x16.png" />
-			<resource file="${basedir}/resources/user_16x16.png" />
-			<resource file="${basedir}/resources/users_16x16.png" />
-			<resource file="${basedir}/resources/settings_16x16.png" />
-			<resource file="${basedir}/resources/lock_go_16x16.png" />
-			<resource file="${basedir}/resources/lock_pull_16x16.png" />
-			<resource file="${basedir}/resources/shield_16x16.png" />
-			<resource file="${basedir}/resources/federated_16x16.png" />
-			<resource file="${basedir}/resources/cold_16x16.png" />
-			<resource file="${basedir}/resources/book_16x16.png" />
-			<resource file="${basedir}/resources/bug_16x16.png" />
-			<resource file="${basedir}/resources/health_16x16.png" />
-			<resource file="${basedir}/resources/feed_16x16.png" />
-			<resource file="${basedir}/resources/bullet_feed.png" />
-			<resource file="${basedir}/resources/search-icon.png" />
-			<resource file="${basedir}/resources/commit_changes_16x16.png" />
-			<resource file="${basedir}/resources/commit_merge_16x16.png" />
-			<resource file="${basedir}/resources/commit_divide_16x16.png" />
-			<resource file="${basedir}/resources/star_16x16.png" />
-			<resource file="${basedir}/resources/blank.png" />
-			<resource file="${basedir}/src/com/gitblit/wicket/GitBlitWebApp.properties" />
-			<resource file="${basedir}/src/com/gitblit/wicket/GitBlitWebApp_es.properties" />
-			<resource file="${basedir}/src/com/gitblit/wicket/GitBlitWebApp_ja.properties" />
-			<resource file="${basedir}/src/com/gitblit/wicket/GitBlitWebApp_ko.properties" />
-			<resource file="${basedir}/src/com/gitblit/wicket/GitBlitWebApp_nl.properties" />
-			<resource file="${basedir}/src/com/gitblit/wicket/GitBlitWebApp_pl.properties" />
-			<resource file="${basedir}/src/com/gitblit/wicket/GitBlitWebApp_pt_BR.properties" />
+		<!-- generate jar by traversing the class hierarchy of the specified
+			 classes, exclude any classes in classpath jars -->
+		<mx:genjar tag="" includeResources="false" excludeClasspathJars="true"
+			destfile="${project.targetDirectory}/manager.jar">
+			<resource file="${project.src.dir}/com/gitblit/client/splash.png" />
+			<resource file="${project.resources.dir}/gitblt-favicon.png" />
+			<resource file="${project.resources.dir}/gitweb-favicon.png" />
+			<resource file="${project.resources.dir}/git-orange-16x16.png" />
+			<resource file="${project.resources.dir}/user_16x16.png" />
+			<resource file="${project.resources.dir}/users_16x16.png" />
+			<resource file="${project.resources.dir}/settings_16x16.png" />
+			<resource file="${project.resources.dir}/lock_go_16x16.png" />
+			<resource file="${project.resources.dir}/lock_pull_16x16.png" />
+			<resource file="${project.resources.dir}/shield_16x16.png" />
+			<resource file="${project.resources.dir}/federated_16x16.png" />
+			<resource file="${project.resources.dir}/cold_16x16.png" />
+			<resource file="${project.resources.dir}/book_16x16.png" />
+			<resource file="${project.resources.dir}/bug_16x16.png" />
+			<resource file="${project.resources.dir}/health_16x16.png" />
+			<resource file="${project.resources.dir}/feed_16x16.png" />
+			<resource file="${project.resources.dir}/bullet_feed.png" />
+			<resource file="${project.resources.dir}/search-icon.png" />
+			<resource file="${project.resources.dir}/commit_changes_16x16.png" />
+			<resource file="${project.resources.dir}/commit_merge_16x16.png" />
+			<resource file="${project.resources.dir}/commit_divide_16x16.png" />
+			<resource file="${project.resources.dir}/star_16x16.png" />
+			<resource file="${project.resources.dir}/blank.png" />
+			<resource file="${project.src.dir}/log4j.properties" />
+			<resource>
+				<!-- inlcude all translations -->
+				<fileset dir="${project.src.dir}/com/gitblit/wicket">
+					<include name="*.properties" />
+				</fileset>
+			</resource>
 
-			<class name="com.gitblit.client.GitblitManagerLauncher" />
-			<classfilter>
-				<exclude name="org.apache." />
-				<exclude name="org.bouncycastle." />
-				<exclude name="org.eclipse." />
-				<exclude name="org.slf4j." />
-				<exclude name="com.beust." />
-				<exclude name="com.google." />
-				<exclude name="com.unboundid." />
-			</classfilter>
-			<classpath refid="master-classpath" />
+			<mainclass name="com.gitblit.client.GitblitManagerLauncher" />
+			<class name="com.gitblit.Keys" />
+			<class name="com.gitblit.client.GitblitClient" />
+			<class name="com.gitblit.models.FederationModel" />
+			<class name="com.gitblit.models.FederationProposal" />
+			<class name="com.gitblit.models.FederationSet" />			
 			<manifest>
-				<attribute name="Main-Class" value="com.gitblit.client.GitblitManagerLauncher" />
 				<attribute name="SplashScreen-Image" value="splash.png" />
-				<attribute name="Specification-Version" value="${gb.version}" />
-				<attribute name="Release-Date" value="${gb.versionDate}" />
 			</manifest>
-		</genjar>
+		</mx:genjar>
 
 		<!-- Build Manager Zip file -->
-		<zip destfile="${project.target.dir}/${manager.zipfile}">
+		<mx:zip destfile="${project.targetDirectory}/${manager.zipfile}">
 			<fileset dir="${basedir}">
 				<include name="LICENSE" />
 				<include name="NOTICE" />
 			</fileset>
-			<fileset dir="${project.target.dir}">
-				<include name="manager-${gb.version}.jar" />
+			<fileset dir="${project.targetDirectory}">
+				<include name="manager.jar" />
 			</fileset>
-		</zip>
+			<!-- include "manager" tagged dependencies -->
+			<dependencies prefix="ext" tag="manager" />
+		</mx:zip>
 		
 		<!-- Cleanup -->
-		<delete file="${project.target.dir}/manager-${gb.version}.jar" />
+		<delete file="${project.targetDirectory}/manager.jar" />
 	</target>
 	
 	
@@ -855,69 +521,67 @@
 		~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 	-->
 	<target name="buildAuthority" depends="compile" description="Builds the stand-alone Gitblit Authority">
-		<echo>Building Gitblit Authority ${gb.version}</echo>
+		<echo>Building Gitblit Authority ${project.version}</echo>
 
-		<genjar jarfile="${project.target.dir}/authority-${gb.version}.jar">
-			<resource file="${basedir}/src/com/gitblit/client/splash.png" />
-			<resource file="${basedir}/resources/gitblt-favicon.png" />
-			<resource file="${basedir}/resources/user_16x16.png" />
-			<resource file="${basedir}/resources/users_16x16.png" />
-			<resource file="${basedir}/resources/rosette_16x16.png" />
-			<resource file="${basedir}/resources/rosette_32x32.png" />
-			<resource file="${basedir}/resources/vcard_16x16.png" />
-			<resource file="${basedir}/resources/settings_16x16.png" />
-			<resource file="${basedir}/resources/settings_32x32.png" />
-			<resource file="${basedir}/resources/search-icon.png" />
-			<resource file="${basedir}/resources/mail_16x16.png" />
-			<resource file="${basedir}/resources/script_16x16.png" />
-			<resource file="${basedir}/resources/blank.png" />
-			<resource file="${basedir}/resources/bullet_green.png" />
-			<resource file="${basedir}/resources/bullet_orange.png" />
-			<resource file="${basedir}/resources/bullet_red.png" />
-			<resource file="${basedir}/resources/bullet_white.png" />
-			<resource file="${basedir}/resources/bullet_delete.png" />
-			<resource file="${basedir}/resources/bullet_key.png" />
-			<resource file="${basedir}/src/log4j.properties" />
-			<resource file="${basedir}/src/com/gitblit/wicket/GitBlitWebApp.properties" />
-			<resource file="${basedir}/src/com/gitblit/wicket/GitBlitWebApp_es.properties" />
-			<resource file="${basedir}/src/com/gitblit/wicket/GitBlitWebApp_ja.properties" />
-			<resource file="${basedir}/src/com/gitblit/wicket/GitBlitWebApp_ko.properties" />
-			<resource file="${basedir}/src/com/gitblit/wicket/GitBlitWebApp_pl.properties" />
+		<!-- generate jar by traversing the class hierarchy of the specified
+			 classes, exclude any classes in "authority" classpath jars -->
+		<mx:genjar tag="authority" excludeClasspathJars="true" 
+			destfile="${project.targetDirectory}/authority.jar">
+			<resource file="${project.src.dir}/com/gitblit/client/splash.png" />
+			<resource file="${project.resources.dir}/gitblt-favicon.png" />
+			<resource file="${project.resources.dir}/user_16x16.png" />
+			<resource file="${project.resources.dir}/users_16x16.png" />
+			<resource file="${project.resources.dir}/rosette_16x16.png" />
+			<resource file="${project.resources.dir}/rosette_32x32.png" />
+			<resource file="${project.resources.dir}/vcard_16x16.png" />
+			<resource file="${project.resources.dir}/settings_16x16.png" />
+			<resource file="${project.resources.dir}/settings_32x32.png" />
+			<resource file="${project.resources.dir}/search-icon.png" />
+			<resource file="${project.resources.dir}/mail_16x16.png" />
+			<resource file="${project.resources.dir}/script_16x16.png" />
+			<resource file="${project.resources.dir}/blank.png" />
+			<resource file="${project.resources.dir}/bullet_green.png" />
+			<resource file="${project.resources.dir}/bullet_orange.png" />
+			<resource file="${project.resources.dir}/bullet_red.png" />
+			<resource file="${project.resources.dir}/bullet_white.png" />
+			<resource file="${project.resources.dir}/bullet_delete.png" />
+			<resource file="${project.resources.dir}/bullet_key.png" />
+			<resource file="${project.src.dir}/log4j.properties" />
+			<resource>
+				<!-- inlcude all translations -->
+				<fileset dir="${project.src.dir}/com/gitblit/wicket">
+					<include name="*.properties" />
+				</fileset>
+			</resource>
 
-			<class name="com.gitblit.authority.GitblitAuthorityLauncher" />
-			<classfilter>
-				<exclude name="org.apache." />
-				<exclude name="org.bouncycastle." />
-				<exclude name="org.eclipse." />
-				<exclude name="org.slf4j." />
-				<exclude name="com.beust." />
-				<exclude name="com.google." />
-				<exclude name="com.unboundid." />
-			</classfilter>
-			<classpath refid="master-classpath" />
+			<mainclass name="com.gitblit.authority.Launcher" />
+			<class name="com.gitblit.Keys" />
 			<manifest>
-				<attribute name="Main-Class" value="com.gitblit.authority.GitblitAuthorityLauncher" />
 				<attribute name="SplashScreen-Image" value="splash.png" />
-				<attribute name="Specification-Version" value="${gb.version}" />
-				<attribute name="Release-Date" value="${gb.versionDate}" />
 			</manifest>
-		</genjar>
+		</mx:genjar>
 
 		<!-- Build Authority Zip file -->
-		<zip destfile="${project.target.dir}/${authority.zipfile}">
+		<mx:zip destfile="${project.targetDirectory}/${authority.zipfile}">
 			<fileset dir="${basedir}">
 				<include name="LICENSE" />
 				<include name="NOTICE" />
 			</fileset>
-			<fileset dir="${project.target.dir}">
-				<include name="authority-${gb.version}.jar" />
+			<fileset dir="${project.targetDirectory}">
+				<include name="authority.jar" />
 			</fileset>
-			<zipfileset dir="${basedir}/distrib" prefix="data/certs">
-				<include name="authority.conf" />
-				<include name="mail.tmpl" />
-				<include name="instructions.tmpl" />
+			<zipfileset dir="${project.distrib.dir}/data" prefix="data">
+				<include name="users.conf" />
+				<include name="gitblit.properties" />
 			</zipfileset>
-		</zip>
+			<!-- Gitblit Authority data -->
+			<zipfileset dir="${project.distrib.dir}/data/certs" prefix="data/certs" />
+			<!-- include "authority" tagged dependencies -->
+			<dependencies prefix="ext" tag="authority" />
+		</mx:zip>
+				
+		<!-- Cleanup -->
+		<delete file="${project.targetDirectory}/authority.jar" />
 	</target>
 					
 	<!-- 
@@ -926,29 +590,25 @@
 		~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 	-->
 	<target name="buildApiLibrary" depends="compile" description="Builds the Gitblit RPC client library">
-		<echo>Building Gitblit API Library ${gb.version}</echo>
+		<echo>Building Gitblit API Library ${project.version}</echo>
 	
+		<local name="javadoc.dir" />
+		<property name="javadoc.dir" value="${project.outputDirectory}/javadoc" />
+		<delete dir="${javadoc.dir}" />
+
 		<!-- Build API Library jar -->
-		<genjar jarfile="${project.target.dir}/gbapi-${gb.version}.jar">
+		<mx:genjar tag="" includeResources="false" excludeClasspathJars="true"
+			destfile="${project.targetDirectory}/gbapi-${project.version}.jar">
 			<class name="com.gitblit.Keys" />
 			<class name="com.gitblit.client.GitblitClient" />
 			<class name="com.gitblit.models.FederationModel" />
 			<class name="com.gitblit.models.FederationProposal" />
 			<class name="com.gitblit.models.FederationSet" />			
-			<classpath refid="master-classpath" />
-			<classfilter>
-				<exclude name="com.google.gson." />
-				<exclude name="com.sun.syndication." />
-			</classfilter>
-			<manifest>
-				<attribute name="Specification-Version" value="${gb.version}" />
-				<attribute name="Release-Date" value="${gb.versionDate}" />
-			</manifest>
-		</genjar>
+		</mx:genjar>
 		
 		<!-- Build API sources jar -->
-		<zip destfile="${project.target.dir}/gbapi-${gb.version}-sources.jar">
-			<fileset dir="${basedir}/src" defaultexcludes="yes">
+		<zip destfile="${project.targetDirectory}/gbapi-${project.version}-sources.jar">
+			<fileset dir="${project.src.dir}" defaultexcludes="yes">
 				<include name="com/gitblit/Constants.java"/>
 				<include name="com/gitblit/GitBlitException.java"/>
 				<include name="com/gitblit/Keys.java"/>
@@ -959,8 +619,8 @@
 		</zip>
 		
 		<!-- Build API JavaDoc jar -->
-		<javadoc destdir="${project.target.dir}/javadoc">
-			<fileset dir="${basedir}/src" defaultexcludes="yes">
+		<mx:javadoc destdir="${javadoc.dir}" redirect="true">
+			<fileset dir="${project.src.dir}" defaultexcludes="yes">
 				<include name="com/gitblit/Constants.java"/>
 				<include name="com/gitblit/GitBlitException.java"/>
 				<include name="com/gitblit/Keys.java"/>
@@ -968,37 +628,34 @@
 		  		<include name="com/gitblit/models/**/*.java"/>
 		  		<include name="com/gitblit/utils/**/*.java"/>			  		
 			</fileset>
-		</javadoc>
-		<zip destfile="${project.target.dir}/gbapi-${gb.version}-javadoc.jar">
-			<fileset dir="${project.target.dir}/javadoc" />
+		</mx:javadoc>
+		  			
+		<zip destfile="${project.targetDirectory}/gbapi-${project.version}-javadoc.jar">
+			<fileset dir="${javadoc.dir}" />
 		</zip>
 		
 		<!-- Build the API library zip file -->
-		<zip destfile="${project.target.dir}/${gbapi.zipfile}">
+		<mx:zip destfile="${project.targetDirectory}/${gbapi.zipfile}">
 			<fileset dir="${basedir}">
 				<include name="LICENSE" />
 				<include name="NOTICE" />
 			</fileset>
-			<fileset dir="${project.target.dir}">
-				<include name="gbapi-${gb.version}.jar" />
-				<include name="gbapi-${gb.version}-sources.jar" />
-				<include name="gbapi-${gb.version}-javadoc.jar" />
+			<fileset dir="${project.targetDirectory}">
+				<include name="gbapi-${project.version}.jar" />
+				<include name="gbapi-${project.version}-sources.jar" />
+				<include name="gbapi-${project.version}-javadoc.jar" />
 			</fileset>
-			<fileset dir="${basedir}/ext">
-				<exclude name="src/**" />
-				<include name="gson*.jar" />
-				<include name="rome*.jar" />
-				<include name="jdom*.jar" />
-			</fileset>
-		</zip>
+			<!-- include "api" tagged dependencies -->
+			<dependencies prefix="ext" tag="api" />
+		</mx:zip>
 		
 		<!-- Cleanup -->
 		<delete>
-			<fileset dir="${project.target.dir}">
+			<fileset dir="${project.targetDirectory}">
 				<include name="javadoc/**" />
-				<include name="gbapi-${gb.version}.jar" />
-				<include name="gbapi-${gb.version}-sources.jar" />
-				<include name="gbapi-${gb.version}-javadoc.jar" />
+				<include name="gbapi-${project.version}.jar" />
+				<include name="gbapi-${project.version}-sources.jar" />
+				<include name="gbapi-${project.version}-javadoc.jar" />
 		</fileset>
 		</delete>
 	</target>
@@ -1009,162 +666,148 @@
 		Build the Gitblit Website
 		~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 	-->
-	<target name="buildSite" depends="compile" description="Build the Gitblit website">
+	<target name="buildSite" depends="prepare" description="Build the Gitblit website">
 		
-		<echo>Building Gitblit Website ${gb.version}</echo>
+		<echo>Building Gitblit Website ${project.version}</echo>
+
+		<property name="releaselog" value="${basedir}/releases.moxie" />
 
 		<!-- Build Site -->
-		<delete dir="${project.site.dir}" />
-		<mkdir dir="${project.site.dir}" />
-		<copy todir="${project.site.dir}">
-			<!-- Copy selected Gitblit resources -->
-			<fileset dir="${project.resources.dir}">
-				<include name="bootstrap/**/*" />
-				<include name="gitblit.css" />
-				<include name="gitblt_25_white.png" />
-				<include name="gitblt-favicon.png" />
-				<include name="lock_go_16x16.png" />
-				<include name="lock_pull_16x16.png" />
-				<include name="shield_16x16.png" />
-				<include name="cold_16x16.png" />
-				<include name="bug_16x16.png" />
-				<include name="book_16x16.png" />
-				<include name="blank.png" />
-				<include name="federated_16x16.png" />
-				<include name="arrow_page.png" />
-			</fileset>
-
-			<!-- Copy Doc images -->
-			<fileset dir="${basedir}/docs">
-				<include name="*.png" />
-				<include name="*.gif" />
-				<include name="*.js" />
-			</fileset>
-		</copy>
+		<mx:doc	googleplusid="114464678392593421684" googleanalyticsid="UA-24377072-1"
+			googlePlusOne="true" minify="true" customless="custom.less">
+			<structure>
+				<menu name="about">
+					<page name="overview" src="siteindex.mkd" out="index.html" headerLinks="false" />
+					<page name="features" src="features.mkd" />
+					<page name="screenshots" src="screenshots.mkd" />
+				</menu>
+				<menu name="documentation" pager="true" pagerPlacement="bottom" pagerLayout="justified">
+					<menu name="Gitblit GO" pager="true" pagerPlacement="bottom" pagerLayout="justified">
+						<page name="setup GO" src="setup_go.mkd" />
+						<page name="upgrade GO" src="upgrade_go.mkd" />
+					</menu>
+					<divider />
+					<menu name="Gitblit WAR" pager="true" pagerPlacement="bottom" pagerLayout="justified">
+						<page name="setup WAR" src="setup_war.mkd" />
+						<page name="upgrade WAR" src="upgrade_war.mkd" />
+					</menu>
+					<divider />
+					<menu name="Gitblit Express" pager="true" pagerPlacement="bottom" pagerLayout="justified">
+						<page name="setup Express" src="setup_express.mkd" />
+						<page name="upgrade Express" src="upgrade_express.mkd" />
+					</menu>
+					<divider />
+					<page name="administration" src="administration.mkd" />
+					<page name="authentication" src="setup_authentication.mkd" />
+					<page name="push hooks" src="setup_hooks.mkd" />
+					<page name="lucene indexing" src="setup_lucene.mkd" />
+					<page name="reverse proxies" src="setup_proxy.mkd" />
+					<page name="client app menus" src="setup_clientmenus.mkd" />
+					<divider />
+					<page name="Gitblit as a viewer" src="setup_viewer.mkd" />
+					<divider />
+					<page name="git client setup" src="setup_client.mkd" />
+					<divider />
+					<page name="federation" src="federation.mkd" />
+					<divider />
+					<page name="settings" src="properties.mkd" />
+					<page name="faq" src="faq.mkd" />
+					<divider />
+					<page name="design" src="design.mkd" />
+					<page name="rpc" src="rpc.mkd" />
+				</menu>
+				
+				<menu name="releases">
+					<page name="release notes" out="releasenotes.html">
+						<template src="releasecurrent.ftl" data="${releaselog}" />
+					</page>
+					<page name="release history" out="releases.html">
+						<template src="releasehistory.ftl" data="${releaselog}" />
+					</page>
+					<divider />
+					<page name="roadmap" src="roadmap.mkd" />					
+				</menu>
+				
+				<menu name="downloads">
+					<link name="Gitblit GO (Windows)" src="${gc.url}gitblit-${project.releaseVersion}.zip" />
+					<link name="Gitblit GO (Linux/OSX)" src="${gc.url}gitblit-${project.releaseVersion}.tar.gz" />
+					<link name="Gitblit WAR" src="${gc.url}gitblit-${project.releaseVersion}.war" />
+					<link name="Gitblit Express" src="${gc.url}express-${project.releaseVersion}.zip" />
+					<divider />
+					<link name="Gitblit Manager" src="${gc.url}manager-${project.releaseVersion}.zip" />
+					<link name="Federation Client" src="${gc.url}fedclient-${project.releaseVersion}.zip" />
+					<divider />
+					<link name="API Library" src="${gc.url}gbapi-${project.releaseVersion}.zip" />
+				</menu>
+				
+				<menu name="links">
+					<link name="Gitblit Demo (RELEASE)" src="https://demo-gitblit.rhcloud.com" />
+					<link name="Gitblit Next (SNAPSHOT)" src="https://next-gitblit.rhcloud.com" />
+					<divider />
+					<link name="Github" src="${project.scmUrl}" />
+					<link name="Issues" src="${project.issuesUrl}" />
+					<link name="Discussion" src="${project.forumUrl}" />
+					<link name="Google+" src="${project.socialNetworkUrl}" />
+					<link name="Ohloh" src="http://www.ohloh.net/p/gitblit" />
+				</menu>
+				<divider />
+			</structure>
+			
+			<replace token="%GCURL%" value="${gc.url}" />
+			
+			<properties token="%PROPERTIES%" file="${project.distrib.dir}/data/gitblit.properties" />
+			
+			<regex searchPattern="\b(issue)(\s*[#]?|-){0,1}(\d+)\b" replacePattern="&lt;a href='http://code.google.com/p/gitblit/issues/detail?id=$3'&gt;issue $3&lt;/a&gt;" />
+			
+			<!-- Set the logo from the mx:doc resources -->
+			<logo file="${project.resources.dir}/gitblt_25_white.png" />
+			<favicon file="${project.resources.dir}/gitblt-favicon.png" />
+			
+			<resource>
+				<fileset dir="${project.resources.dir}">
+					<include name="lock_go_16x16.png" />
+					<include name="lock_pull_16x16.png" />
+					<include name="shield_16x16.png" />
+					<include name="cold_16x16.png" />
+					<include name="bug_16x16.png" />
+					<include name="book_16x16.png" />
+					<include name="blank.png" />
+					<include name="federated_16x16.png" />
+					<include name="arrow_page.png" />
+				</fileset>
+			</resource>
+		</mx:doc>		
 
 		<!-- Copy Fancybox -->
-		<mkdir dir="${project.site.dir}/fancybox" />
-		<copy todir="${project.site.dir}/fancybox">
-			<fileset dir="${basedir}/docs/fancybox">
-				<exclude name="thumbs.db" />
-			</fileset>
-		</copy>
-
-		<!-- Copy google-code-prettify -->
-		<mkdir dir="${basedir}/src/com/gitblit/wicket/pages/prettify" />
-		<copy todir="${project.site.dir}/prettify">
-			<fileset dir="${basedir}/src/com/gitblit/wicket/pages/prettify">
+		<mkdir dir="${project.siteTargetDirectory}/fancybox" />
+		<copy todir="${project.siteTargetDirectory}/fancybox">
+			<fileset dir="${project.siteSourceDirectory}/fancybox">
 				<exclude name="thumbs.db" />
 			</fileset>
 		</copy>
 
 		<!-- Generate thumbnails of screenshots -->
-		<java classpath="${project.build.dir}" classname="com.gitblit.build.BuildThumbnails">
-			<classpath refid="master-classpath" />
-				
-			<arg value="--sourceFolder" />
-			<arg value="${basedir}/docs/screenshots" />
-		
-			<arg value="--destinationFolder" />
-			<arg value="${project.site.dir}/thumbs" />
-			
-			<arg value="--maximumDimension" />
-			<arg value="250" />
-		</java>
+		<mx:thumbs input="png" output="png" maximumDimension="250" 
+			sourceDir="${project.siteSourceDirectory}/screenshots"
+			destDir="${project.siteTargetDirectory}/thumbs" />
 
 		<!-- Copy screenshots -->
-		<mkdir dir="${project.site.dir}/screenshots" />
-		<copy todir="${project.site.dir}/screenshots">
-			<fileset dir="${basedir}/docs/screenshots">
+		<mkdir dir="${project.siteTargetDirectory}/screenshots" />
+		<copy todir="${project.siteTargetDirectory}/screenshots">
+			<fileset dir="${project.siteSourceDirectory}/screenshots">
 				<include name="*.png" />
 			</fileset>
 		</copy>
 
-		<!-- Build site pages -->
-		<java classpath="${project.build.dir}" classname="com.gitblit.build.BuildSite">
-			<classpath refid="master-classpath" />
-			<arg value="--sourceFolder" />
-			<arg value="${basedir}/docs" />
-
-			<arg value="--outputFolder" />
-			<arg value="${project.site.dir}" />
-
-			<arg value="--pageHeader" />
-			<arg value="${basedir}/docs/site_header.html" />
-			
-			<arg value="--pageFooter" />
-			<arg value="${basedir}/docs/site_footer.html" />
-
-			<arg value="--analyticsSnippet" />
-			<arg value="${basedir}/docs/site_analytics.html" />
-				
-			<arg value="--adSnippet" />
-			<arg value="${basedir}/docs/site_ads.html" />
-
-			<arg value="--alias" />
-			<arg value="index=overview" />
-
-			<arg value="--alias" />
-			<arg value="properties=settings" />
-
-			<arg value="--substitute" />
-			<arg value="%VERSION%=${gb.version}" />
-
-			<arg value="--substitute" />
-			<arg value="%GO%=${distribution.zipfile}" />
-
-			<arg value="--substitute" />
-			<arg value="%WAR%=${distribution.warfile}" />
-
-			<arg value="--substitute" />
-			<arg value="%FEDCLIENT%=${fedclient.zipfile}" />
-
-			<arg value="--substitute" />
-			<arg value="%MANAGER%=${manager.zipfile}" />
-
-			<arg value="--substitute" />
-			<arg value="%API%=${gbapi.zipfile}" />
-
-			<arg value="--substitute" />
-			<arg value="%EXPRESS%=${express.zipfile}" />
-
-			<arg value="--substitute" />
-			<arg value="%BUILDDATE%=${gb.versionDate}" />
-
-			<arg value="--substitute" />
-			<arg value="%JGIT%=${jgit.version}" />
-
-			<arg value="--properties" />
-			<arg value="%PROPERTIES%=${basedir}/distrib/gitblit.properties" />
-			
-			<arg value="--nomarkdown" />
-			<arg value="%BEGINCODE%:%ENDCODE%" />
-
-			<arg value="--substitute" />
-			<arg value="&quot;%BEGINCODE%=&lt;pre class='prettyprint lang-java'&gt;&quot;" />
-
-			<arg value="--substitute" />
-			<arg value="%ENDCODE%=&lt;/pre&gt;" />
-
-			<arg value="--regex" />
-			<arg value="&quot;\b(issue)(\s*[#]?|-){0,1}(\d+)\b!!!&lt;a href='http://code.google.com/p/gitblit/issues/detail?id=$3'&gt;issue $3&lt;/a&gt;&quot;" />
-
-		</java>	
 	</target>
 
 
 	<!--
 		~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
-		Compile from source, publish binaries, and build & deploy site
+		Build all binaries and site
 		~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 	-->
-	<target name="buildAll" depends="buildAuthority,buildGO,buildWAR,buildExpress,buildFederationClient,buildManager,buildApiLibrary,buildSite">		
-		<!-- Cleanup -->
-		<delete dir="${project.build.dir}" />
-		<delete dir="${project.war.dir}" />
-		<delete dir="${project.deploy.dir}" />
-		<delete dir="${project.express.dir}" />
-	</target>
+	<target name="buildAll" depends="buildAuthority,buildGO,buildWAR,buildExpress,buildFederationClient,buildManager,buildApiLibrary,buildSite" />		
 
 	
 	<!--
@@ -1172,18 +815,9 @@
 		Update the gh-pages branch with the current site
 		~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 	-->
-	<target name="updateGhPages" depends="buildSite">
+	<target name="updateGhPages">
 		<!-- Build gh-pages branch -->
-		<java classpath="${project.build.dir}" classname="com.gitblit.build.BuildGhPages">
-			<classpath refid="master-classpath" />
-			<arg value="--sourceFolder" />
-			<arg value="${basedir}/target/site" />
-
-			<arg value="--repository" />
-			<arg value="${basedir}" />
-			
-			<arg value="--obliterate" />
-		</java>
+		<mx:ghpages repositorydir="${basedir}" obliterate="true" />
 	</target>
 	
 
@@ -1192,69 +826,72 @@
 		Publish binaries to Google Code
 		~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 	-->
-	<target name="publishBinaries" depends="buildGO,buildWAR,buildExpress,buildFederationClient,buildManager,buildApiLibrary" description="Publish the Gitblit binaries to Google Code">
+	<target name="publishBinaries" depends="prepare" description="Publish the Gitblit binaries to Google Code">
 
-		<echo>Uploading Gitblit ${gb.version} binaries</echo>
+		<echo>Uploading Gitblit ${project.version} binaries</echo>
 
-		<!-- Upload Gitblit GO ZIP file -->
-		<gcupload 
-			 username="${googlecode.user}" 
-			 password="${googlecode.password}" 
-			 projectname="gitblit" 
-			 filename="${project.target.dir}/${distribution.zipfile}" 
-			 targetfilename="gitblit-${gb.version}.zip"
-			 summary="Gitblit GO v${gb.version} (standalone, integrated Gitblit server)"
-			 labels="Featured, Type-Package, OpSys-All" />
+		<!-- Upload Gitblit GO Windows ZIP file -->
+		<mx:gcupload 
+			username="${googlecode.user}"
+			password="${googlecode.password}"
+			projectname="gitblit"
+			filename="${project.targetDirectory}/${distribution.zipfile}" 
+			targetFilename="gitblit-${project.version}.zip"
+			summary="Gitblit GO v${project.version} (standalone, integrated Gitblit server for Windows)" />
+
+		<!-- Upload Gitblit GO Linux/Unix tar.gz file -->
+		<mx:gcupload
+			username="${googlecode.user}"
+			password="${googlecode.password}"
+			projectname="gitblit"
+			filename="${project.targetDirectory}/${distribution.tgzfile}" 
+			targetFilename="gitblit-${project.version}.tar.gz"
+			summary="Gitblit GO v${project.version} (standalone, integrated Gitblit server for Linux/Unix)" />
 
 		<!-- Upload Gitblit WAR file -->
-		<gcupload 
-		     username="${googlecode.user}" 
-		     password="${googlecode.password}" 
-		     projectname="gitblit" 
-		     filename="${project.target.dir}/${distribution.warfile}" 
-		     targetfilename="gitblit-${gb.version}.war"
-		     summary="Gitblit WAR v${gb.version} (standard WAR webapp for servlet containers)"
-		     labels="Featured, Type-Package, OpSys-All" />
+		<mx:gcupload
+			username="${googlecode.user}"
+			password="${googlecode.password}"
+			projectname="gitblit"
+			filename="${project.targetDirectory}/${distribution.warfile}" 
+			targetFilename="gitblit-${project.version}.war"
+			summary="Gitblit WAR v${project.version} (standard WAR webapp for servlet containers)" />
 
 		<!-- Upload Gitblit FedClient -->
-		<gcupload 
-			username="${googlecode.user}" 
-			password="${googlecode.password}" 
-			projectname="gitblit" 
-			filename="${project.target.dir}/${fedclient.zipfile}" 
-			targetfilename="fedclient-${gb.version}.zip"
-			summary="Gitblit Federation Client v${gb.version} (command-line tool to clone data from federated Gitblit instances)"
-			labels="Featured, Type-Package, OpSys-All" />
+		<mx:gcupload
+			username="${googlecode.user}"
+			password="${googlecode.password}"
+		    projectname="gitblit"
+			filename="${project.targetDirectory}/${fedclient.zipfile}" 
+			targetFilename="fedclient-${project.version}.zip"
+		    summary="Gitblit Federation Client v${project.version} (command-line tool to clone data from federated Gitblit instances)" />
 
 		<!-- Upload Gitblit Manager -->
-		<gcupload 
-			username="${googlecode.user}" 
-			password="${googlecode.password}" 
-			projectname="gitblit" 
-			filename="${project.target.dir}/${manager.zipfile}" 
-			targetfilename="manager-${gb.version}.zip"
-			summary="Gitblit Manager v${gb.version} (Swing tool to remotely administer a Gitblit server)"
-			labels="Featured, Type-Package, OpSys-All" />
+		<mx:gcupload
+			username="${googlecode.user}"
+			password="${googlecode.password}"
+			projectname="gitblit"
+			filename="${project.targetDirectory}/${manager.zipfile}" 
+			targetFilename="manager-${project.version}.zip"
+			summary="Gitblit Manager v${project.version} (Swing tool to remotely administer a Gitblit server)" />
 
 		<!-- Upload Gitblit API Library -->
-		<gcupload 
-			username="${googlecode.user}" 
-			password="${googlecode.password}" 
-			projectname="gitblit" 
-			filename="${project.target.dir}/${gbapi.zipfile}" 
-			targetfilename="gbapi-${gb.version}.zip"
-			summary="Gitblit API Library v${gb.version} (JSON RPC library to integrate with your software)"
-			labels="Featured, Type-Package, OpSys-All" />
+		<mx:gcupload
+			username="${googlecode.user}"
+			password="${googlecode.password}"
+			projectname="gitblit"
+			filename="${project.targetDirectory}/${gbapi.zipfile}" 
+			targetFilename="gbapi-${project.version}.zip"
+			summary="Gitblit API Library v${project.version} (JSON RPC library to integrate with your software)" />
 
 		<!-- Upload Gitblit Express for RedHat OpenShift -->
-		<gcupload 
-			username="${googlecode.user}" 
-			password="${googlecode.password}" 
-			projectname="gitblit" 
-			filename="${project.target.dir}/${express.zipfile}" 
-			targetfilename="express-${gb.version}.zip"
-			summary="Gitblit Express v${gb.version} (run Gitblit on RedHat's OpenShift cloud)"
-			labels="Featured, Type-Package, OpSys-All" />
+		<mx:gcupload
+			username="${googlecode.user}"
+			password="${googlecode.password}"
+			projectname="gitblit"
+			filename="${project.targetDirectory}/${express.zipfile}" 
+			targetFilename="express-${project.version}.zip"
+			summary="Gitblit Express v${project.version} (run Gitblit on RedHat's OpenShift cloud)" />
 
 	</target>
 
@@ -1262,33 +899,262 @@
 	<!--
 		~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
 		Publish site to site hosting service
-		You must add ext/commons-net-1.4.0.jar to your ANT classpath.
 		~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 	-->
-	<target name="publishSite" depends="buildSite,updateGhPages" description="Publish the Gitblit site to a webserver (requires ext/commons-net-1.4.0.jar)" >
+	<target name="publishSite" depends="clean,buildSite,updateGhPages" description="Publish the Gitblit site to a host" >
 
-		<echo>Uploading Gitblit ${gb.version} website</echo>
+		<echo>Uploading Gitblit ${project.version} website</echo>
 
-		<ftp server="${ftp.server}"
+		<mx:ftp server="${ftp.server}"
 			userid="${ftp.user}"
 			password="${ftp.password}"
 			remotedir="${ftp.dir}"
 			passive="true"
 			verbose="yes">
-		<fileset dir="${project.site.dir}" />
-		</ftp>
+			<fileset dir="${project.siteTargetDirectory}" />
+		</mx:ftp>
 	</target>
 
 
 	<!--
 		~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
-		Compile from source, publish binaries, and build & deploy site
+		Tag a new version and prepare for the next development cycle.
+		~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
+	-->
+	<target name="tagRelease" depends="prepare">
+		<!-- release -->
+		<property name="dryrun" value="false" />
+		<mx:version stage="release" dryrun="${dryrun}" />		
+		<property name="project.tag" value="v${project.version}" />
+		<!-- commit build.moxie & releases.moxie (automatic) -->
+		<mx:commit showtitle="no">
+		    <message>Prepare ${project.version} release</message>
+			<tag name="${project.tag}">
+				<message>${project.name} ${project.version} release</message>
+			</tag>
+		</mx:commit>
+
+		<!-- create the release process script -->
+		<mx:if>
+			<os family="windows" />
+			<then>
+				<!-- Windows PowerShell script        -->
+				<!-- set-executionpolicy remotesigned -->
+				<property name="recipe" value="release_${project.version}.ps1" />
+			</then>
+			<else>
+				<!-- Bash script -->
+				<property name="recipe" value="release_${project.version}.sh" />
+			</else>
+		</mx:if>
+		<delete file="${recipe}" failonerror="false" quiet="true" verbose="false" />
+		<!-- Work-around for lack of proper ant property substitution in copy -->
+		<property name="dollar" value="$"/>
+		<copy file="release.template" tofile="${recipe}">
+			<filterset begintoken="${dollar}{" endtoken="}">
+				<filter token="project.version" value="${project.version}" />
+				<filter token="project.commitId" value="${project.commitId}" />
+				<filter token="project.tag" value="${project.tag}" />
+			</filterset>
+		</copy>
+		<chmod file="${recipe}" perm="ugo+rx" />
+
+		<!-- next cycle -->
+		<mx:version stage="snapshot" incrementNumber="incremental" dryrun="${dryrun}" />
+		<mx:commit showtitle="no">
+		    <message>Reset build identifiers for next development cycle</message>
+		</mx:commit>		
+	</target>
+
+		
+	<!--
+		~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+		Build Gitblit Docs
 		~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 	-->
-	<target name="publishAll" depends="publishBinaries,publishSite">
-		<!-- Cleanup -->
-		<delete dir="${project.build.dir}" />
-		<delete dir="${project.war.dir}" />
-		<delete dir="${project.deploy.dir}" />
+	<macrodef name="generateDocs">
+		<attribute name="toDir"/>
+		<sequential>
+			<mx:doc toDir="@{toDir}" minify="true" customless="custom.less">
+				<structure>
+					<menu name="about">
+						<page name="overview" src="siteindex.mkd" out="index.html" headerLinks="false" />
+						<page name="features" src="features.mkd" />
+					</menu>
+					<menu name="documentation">
+						<menu name="Gitblit GO" pager="true" pagerPlacement="bottom" pagerLayout="justified">
+							<page name="setup GO" src="setup_go.mkd" />
+							<page name="upgrade GO" src="upgrade_go.mkd" />
+						</menu>
+						<divider />
+						<menu name="Gitblit WAR" pager="true" pagerPlacement="bottom" pagerLayout="justified">
+							<page name="setup WAR" src="setup_war.mkd" />
+							<page name="upgrade WAR" src="upgrade_war.mkd" />
+						</menu>
+						<divider />
+						<menu name="Gitblit Express" pager="true" pagerPlacement="bottom" pagerLayout="justified">
+							<page name="setup Express" src="setup_express.mkd" />
+							<page name="upgrade Express" src="upgrade_express.mkd" />
+						</menu>
+						<divider />
+						<page name="administration" src="administration.mkd" />
+						<page name="authentication" src="setup_authentication.mkd" />
+						<page name="push hooks" src="setup_hooks.mkd" />
+						<page name="lucene indexing" src="setup_lucene.mkd" />
+						<page name="reverse proxies" src="setup_proxy.mkd" />
+						<page name="client app menus" src="setup_clientmenus.mkd" />
+						<divider />
+						<page name="Gitblit as a viewer" src="setup_viewer.mkd" />
+						<divider />
+						<page name="git client setup" src="setup_client.mkd" />
+						<divider />
+						<page name="federation" src="federation.mkd" />
+						<divider />
+						<page name="settings" src="properties.mkd" />
+						<page name="faq" src="faq.mkd" />
+						<divider />
+						<page name="design" src="design.mkd" />
+						<page name="rpc" src="rpc.mkd" />
+					</menu>
+					<menu name="changelog">
+						<page name="current release" src="releasecurrent.mkd" />
+						<page name="older releases" src="releasehistory.mkd" />
+					</menu>
+					<menu name="links">
+						<link name="Gitblit Demo (RELEASE)" src="https://demo-gitblit.rhcloud.com" />
+						<link name="Gitbilt Next (SNAPSHOT)" src="https://next-gitblit.rhcloud.com" />
+						<divider />
+						<link name="Github" src="${project.scmUrl}" />
+						<link name="Issues" src="${project.issuesUrl}" />
+						<link name="Discussion" src="${project.forumUrl}" />
+						<link name="Google+" src="${project.socialNetworkUrl}" />
+						<link name="Ohloh" src="http://www.ohloh.net/p/gitblit" />
+					</menu>
+				</structure>
+				
+				<properties token="%PROPERTIES%" file="${project.distrib.dir}/data/gitblit.properties" />
+				
+				<regex searchPattern="\b(issue)(\s*[#]?|-){0,1}(\d+)\b" replacePattern="&lt;a href='http://code.google.com/p/gitblit/issues/detail?id=$3'&gt;issue $3&lt;/a&gt;" />
+				
+				<!-- Set the logo from the mx:doc resources -->
+				<logo file="${project.resources.dir}/gitblt_25_white.png" />
+				<favicon file="${project.resources.dir}/gitblt-favicon.png" />
+				
+				<resource>
+					<fileset dir="${project.resources.dir}">
+						<include name="lock_go_16x16.png" />
+						<include name="lock_pull_16x16.png" />
+						<include name="shield_16x16.png" />
+						<include name="cold_16x16.png" />
+						<include name="bug_16x16.png" />
+						<include name="book_16x16.png" />
+						<include name="blank.png" />
+						<include name="federated_16x16.png" />
+						<include name="arrow_page.png" />
+					</fileset>
+				</resource>
+			</mx:doc>
+	    </sequential>
+	</macrodef>
+	
+	<!--
+		~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
+		Macro to create a pristine data directory for the target build
+		~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+	-->
+	<macrodef name="prepareDataDirectory">
+		<attribute name="toDir"/>
+		<sequential>
+			<mkdir dir="@{toDir}" />
+			<copy todir="@{toDir}" overwrite="false">
+				<fileset dir="${project.distrib.dir}/data">
+					<include name="users.conf" />
+					<include name="projects.conf" />
+					<include name="gitblit.properties" />					
+				</fileset>
+			</copy>
+			<mkdir dir="@{toDir}/git" />
+			<copy todir="@{toDir}/git" overwrite="false">
+				<fileset dir="${project.distrib.dir}/data/git">
+					<include name="project.mkd" />
+				</fileset>
+			</copy>
+			<mkdir dir="@{toDir}/groovy" />
+			<copy todir="@{toDir}/groovy">
+				<fileset dir="${project.distrib.dir}/data/groovy">					
+					<include name="sendmail.groovy" />
+					<include name="sendmail-html.groovy" />
+					<include name="jenkins.groovy" />
+					<include name="protect-refs.groovy" />
+					<include name="fogbugz.groovy" />
+					<include name="thebuggenie.groovy" />
+				</fileset>
+			</copy>
+      </sequential>
+	</macrodef>
+	
+	<!--
+		~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
+		Macro to upload binaries to GoogleCode
+		~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+	-->
+	<macrodef name="googleUpload">
+		<attribute name="sourceFile"/>
+		<attribute name="targetFile"/>
+		<attribute name="description"/>
+		<sequential>
+			<gcupload 
+				username="${googlecode.user}" 
+				password="${googlecode.password}" 
+				projectname="gitblit" 
+				filename="${project.targetDirectory}/@{sourceFile}" 
+				targetfilename="@{targetFile}"
+				summary="@{description}"
+				labels="Featured, Type-Package, OpSys-All" />		
+	     </sequential>
+	</macrodef>
+
+
+	<!--
+		~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+		Install Gitblit JAR for usage as Maven module
+		~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+	-->
+	<target name="installMaven" depends="compile" description="Install Gitblit JAR as Maven module">
+		<local name="project.jar" />
+		<property name="project.jar" value="${project.outputDirectory}/gitblit.jar" />
+		<property name="resourceFolderPrefix" value="" />
+		<mx:jar destfile="${project.jar}" includeresources="true" resourceFolderPrefix="${resourceFolderPrefix}" />
+
+		<exec executable="mvn">
+			<arg value="install:install-file" />
+			<arg value="-Dfile=${project.jar}" />
+			<arg value="-DpomFile=${basedir}/pom.xml" />
+			<arg value="-DcreateChecksum=true" />
+		</exec>
 	</target>
+
+	<!--
+    	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+    	Upload Gitblit JAR to remote Maven repository
+    	
+    	build.properties:
+    	   project.maven.repo.url = http://whatever.com/maven2
+    	   project.maven.repo.id = whateverId
+    	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+    -->
+	<target name="uploadMaven" depends="compile" description="Upload Gitblit JAR to remote Maven repository">
+		<local name="project.jar" />
+		<property name="project.jar" value="${project.outputDirectory}/gitblit.jar" />
+		<mx:jar destfile="${project.jar}" includeresources="true" />
+
+		<exec executable="mvn">
+			<arg value="deploy:deploy-file" />
+			<arg value="-Dfile=${project.jar}" />
+			<arg value="-DpomFile=${basedir}/pom.xml" />
+			<arg value="-Durl=${project.maven.repo.url}" />
+			<arg value="-DrepositoryId=${project.maven.repo.id}" />
+			<arg value="-DcreateChecksum=true" />
+		</exec>
+	</target>					
 </project>
diff --git a/distrib/add-indexed-branch.cmd b/distrib/add-indexed-branch.cmd
deleted file mode 100644
index e167639..0000000
--- a/distrib/add-indexed-branch.cmd
+++ /dev/null
@@ -1,20 +0,0 @@
-@REM --------------------------------------------------------------------------
-@REM This is for Lucene search integration.
-@REM
-@REM Allows you to add an indexed branch specification to the repository config
-@REM for all matching repositories in the specified folder.
-@REM
-@REM All repositories are included unless excluded using a --skip parameter.
-@REM --skip supports simple wildcard fuzzy matching however only 1 asterisk is
-@REM allowed per parameter.
-@REM
-@REM Always use forward-slashes for the path separator in your parameters!!
-@REM
-@REM Set FOLDER to the server's git.repositoriesFolder
-@REM Set BRANCH ("default" or fully qualified ref - i.e. refs/heads/master)
-@REM Set EXCLUSIONS for any repositories that you do not want to change
-@REM --------------------------------------------------------------------------
-@SET FOLDER=c:/gitblit/git
-@SET EXCLUSIONS=--skip test.git --skip group/test*
-@SET BRANCH=default
-@java -cp gitblit.jar;"%CD%\ext\*" com.gitblit.AddIndexedBranch --repositoriesFolder %FOLDER% --branch %BRANCH% %EXCLUSIONS%
diff --git a/distrib/authority.cmd b/distrib/authority.cmd
deleted file mode 100644
index 75cb0cf..0000000
--- a/distrib/authority.cmd
+++ /dev/null
@@ -1 +0,0 @@
-@java -jar authority.jar --baseFolder data
diff --git a/distrib/federation.properties b/distrib/federation.properties
deleted file mode 100644
index c762b45..0000000
--- a/distrib/federation.properties
+++ /dev/null
@@ -1,83 +0,0 @@
-#
-# Git Repository Settings
-#
-
-# Base folder for repositories
-# Use forward slashes even on Windows!!
-# e.g. c:/gitrepos
-#
-# SINCE 0.5.0
-# RESTART REQUIRED
-git.repositoriesFolder = git
-
-# Search the repositories folder subfolders for other repositories.
-# Repositories MAY NOT be nested (i.e. one repository within another)
-# but they may be grouped together in subfolders.
-# e.g. c:/gitrepos/libraries/mylibrary.git
-#      c:/gitrepos/libraries/myotherlibrary.git
-#
-# SINCE 0.5.0
-git.searchRepositoriesSubfolders = true
-
-# Your federation name is used for federation status acknowledgments.  If it is
-# unset, and you elect to send a status acknowledgment, your Gitblit instance
-# will be identified by its hostname, if available, else your internal ip address.
-# The source Gitblit instance will also append your external IP address to your
-# identification to differentiate multiple pulling systems behind a single proxy.
-#
-# SINCE 0.6.0
-federation.name =
-
-# Federation pull registrations
-# Registrations are read once, at startup.
-#
-# RESTART REQUIRED
-#
-# frequency:
-#   The shortest frequency allowed is every 5 minutes
-#   Decimal frequency values are cast to integers
-#   Frequency values may be specified in mins, hours, or days
-#   Values that can not be parsed or are unspecified default to *federation.defaultFrequency*
-#
-# folder:
-#   if unspecified, the folder is *git.repositoriesFolder*
-#   if specified, the folder is relative to *git.repositoriesFolder*
-#
-# bare:
-#   if true, each repository will be created as a *bare* repository and will not
-#   have a working directory.
-#
-#   if false, each repository will be created as a normal repository suitable
-#   for local work.
-#
-# mirror:
-#   if true, each repository HEAD is reset to *origin/master* after each pull.
-#   The repository will be flagged *isFrozen* after the initial clone.
-#
-#   if false, each repository HEAD will point to the FETCH_HEAD of the initial
-#   clone from the origin until pushed to or otherwise manipulated.
-#
-# mergeAccounts:
-#   if true, remote accounts and their permissions are merged into your 
-#   users.properties file 
-#
-# notifyOnError:
-#   if true and the mail configuration is properly set, administrators will be
-#   notified by email of pull failures
-#
-# include and exclude:
-#   Space-delimited list of repositories to include or exclude from pull
-#   may be * wildcard to include or exclude all
-#   may use fuzzy match (e.g. org.eclipse.*)
-
-#
-# (Nearly) Perfect Mirror example
-#
-
-#federation.example1.url = https://go.gitblit.com
-#federation.example1.token = 6f3b8a24bf970f17289b234284c94f43eb42f0e4
-#federation.example1.frequency = 120 mins
-#federation.example1.folder =
-#federation.example1.bare = true 
-#federation.example1.mirror = true 
-#federation.example1.mergeAccounts = true
diff --git a/distrib/gitblit b/distrib/gitblit
deleted file mode 100644
index 6c74d54..0000000
--- a/distrib/gitblit
+++ /dev/null
@@ -1,37 +0,0 @@
-#!/bin/sh
-
-set -e
-
-GITBLIT_PATH=/opt/gitblit
-GITBLIT_BASE_FOLDER=/opt/gitblit/data
-GITBLIT_HTTP_PORT=0
-GITBLIT_HTTPS_PORT=8443
-source ${GITBLIT_PATH}/java-proxy-config.sh
-JAVA="java -server -Xmx1024M ${JAVA_PROXY_CONFIG} -Djava.awt.headless=true -jar"
-
-. /lib/lsb/init-functions
-
-case "$1" in
-  start)
-        log_action_begin_msg "Starting gitblit server"
-        cd $GITBLIT_PATH
-        $JAVA $GITBLIT_PATH/gitblit.jar --httpsPort $GITBLIT_HTTPS_PORT --httpPort $GITBLIT_HTTP_PORT --baseFolder $GITBLIT_BASE_FOLDER > /dev/null &
-        log_action_end_msg $?
-        ;;
-  stop)
-        log_action_begin_msg "Stopping gitblit server"
-        cd $GITBLIT_PATH
-        $JAVA $GITBLIT_PATH/gitblit.jar --baseFolder $GITBLIT_BASE_FOLDER --stop > /dev/null &
-        log_action_end_msg $?
-        ;;
-  force-reload|restart)
-        $0 stop
-        $0 start
-        ;;
-  *)
-        echo "Usage: /etc/init.d/gitblit {start|stop|restart|force-reload}"
-        exit 1
-        ;;
-esac
-
-exit 0
diff --git a/distrib/gitblit-stop.cmd b/distrib/gitblit-stop.cmd
deleted file mode 100644
index 5820c49..0000000
--- a/distrib/gitblit-stop.cmd
+++ /dev/null
@@ -1 +0,0 @@
-@java -jar gitblit.jar --stop --baseFolder data
diff --git a/distrib/gitblit.cmd b/distrib/gitblit.cmd
deleted file mode 100644
index 3006a68..0000000
--- a/distrib/gitblit.cmd
+++ /dev/null
@@ -1 +0,0 @@
-@java -jar gitblit.jar --baseFolder data
diff --git a/distrib/gitblit.properties b/distrib/gitblit.properties
deleted file mode 100644
index f5cc19b..0000000
--- a/distrib/gitblit.properties
+++ /dev/null
@@ -1,1277 +0,0 @@
-#
-# Gitblit Settings
-#
-
-# This settings file supports parameterization from the command-line for the
-# following command-line parameters:
-#
-#   --baseFolder    ${baseFolder}    SINCE 1.2.1
-#
-# Settings that support ${baseFolder} parameter substitution are indicated with the
-# BASEFOLDER attribute.  If the --baseFolder argument is unspecified, ${baseFolder}
-# and it's trailing / will be discarded from the setting value leaving a relative
-# path that is equivalent to pre-1.2.1 releases.
-#
-# e.g. "${baseFolder}/git" becomes "git", if --baseFolder is unspecified 
-#
-# Git Servlet Settings
-#
-
-# Base folder for repositories.
-# This folder may contain bare and non-bare repositories but Gitblit will only
-# allow you to push to bare repositories.
-# Use forward slashes even on Windows!!
-# e.g. c:/gitrepos
-#
-# SINCE 0.5.0
-# RESTART REQUIRED
-# BASEFOLDER
-git.repositoriesFolder = ${baseFolder}/git
-
-# Build the available repository list at startup and cache this list for reuse.
-# This reduces disk io when presenting the repositories page, responding to rpcs,
-# etc, but it means that  Gitblit will not automatically identify repositories
-# added or deleted by external tools.
-#
-# For this case you can use curl, wget, etc to issue an rpc request to clear the
-# cache (e.g. https://localhost/rpc?req=CLEAR_REPOSITORY_CACHE)
-#
-# SINCE 1.1.0
-git.cacheRepositoryList = true
-
-# Search the repositories folder subfolders for other repositories.
-# Repositories MAY NOT be nested (i.e. one repository within another)
-# but they may be grouped together in subfolders.
-# e.g. c:/gitrepos/libraries/mylibrary.git
-#      c:/gitrepos/libraries/myotherlibrary.git
-#
-# SINCE 0.5.0
-git.searchRepositoriesSubfolders = true
-
-# Maximum number of folders to recurse into when searching for repositories.
-# The default value, -1, disables depth limits.
-#
-# SINCE 1.1.0
-git.searchRecursionDepth = -1
-
-# List of regex exclusion patterns to match against folders found in
-# *git.repositoriesFolder*.
-# Use forward slashes even on Windows!!
-# e.g. test/jgit\.git
-#
-# SPACE-DELIMITED
-# CASE-SENSITIVE
-# SINCE 1.1.0
-git.searchExclusions =
-
-# List of regex url patterns for extracting a repository name when locating
-# submodules.
-#   e.g. git.submoduleUrlPatterns = .*?://github.com/(.*) will extract
-#   *gitblit/gitblit.git* from *git://github.com/gitblit/gitblit.git*
-# If no matches are found then the submodule repository name is assumed to be
-# whatever trails the last / character. (e.g. gitblit.git).
-#
-# SPACE-DELIMITED
-# CASE-SENSITIVE
-# SINCE 1.1.0
-git.submoduleUrlPatterns = .*?://github.com/(.*)
-
-# Allow push/pull over http/https with JGit servlet.
-# If you do NOT want to allow Git clients to clone/push to Gitblit set this
-# to false.  You might want to do this if you are only using ssh:// or git://.
-# If you set this false, consider changing the *web.otherUrls* setting to
-# indicate your clone/push urls.
-#
-# SINCE 0.5.0
-git.enableGitServlet = true
-
-# If you want to restrict all git servlet access to those with valid X509 client
-# certificates then set this value to true.
-#
-# SINCE 1.2.0
-git.requiresClientCertificate = false
-
-# Enforce date checks on client certificates to ensure that they are not being
-# used prematurely and that they have not expired.
-#
-# SINCE 1.2.0
-git.enforceCertificateValidity = true
-
-# List of OIDs to extract from a client certificate DN to map a certificate to
-# an account username.
-#
-# e.g. git.certificateUsernameOIDs = CN
-# e.g. git.certificateUsernameOIDs = FirstName LastName
-#
-# SPACE-DELIMITED
-# SINCE 1.2.0
-git.certificateUsernameOIDs = CN
-
-# Only serve/display bare repositories.
-# If there are non-bare repositories in git.repositoriesFolder and this setting
-# is true, they will be excluded from the ui. 
-#
-# SINCE 0.9.0
-git.onlyAccessBareRepositories = false
-
-# Allow an authenticated user to create a destination repository on a push if
-# the repository does not already exist.
-#
-# Administrator accounts can create a repository in any project.
-# These repositories are created with the default access restriction and authorization
-# control values.  The pushing account is set as the owner.
-#
-# Non-administrator accounts with the CREATE role may create personal repositories.
-# These repositories are created as VIEW restricted for NAMED users.
-# The pushing account is set as the owner.
-#
-# SINCE 1.2.0
-git.allowCreateOnPush = true
-
-# The default access restriction for new repositories.
-# Valid values are NONE, PUSH, CLONE, VIEW
-#  NONE = anonymous view, clone, & push
-#  PUSH = anonymous view & clone and authenticated push
-#  CLONE = anonymous view, authenticated clone & push
-#  VIEW = authenticated view, clone, & push
-#
-# SINCE 1.0.0
-git.defaultAccessRestriction = NONE
-
-# The default authorization control for new repositories.
-# Valid values are AUTHENTICATED and NAMED
-#  AUTHENTICATED = any authenticated user is granted restricted access
-#  NAMED = only named users/teams are granted restricted access
-#
-# SINCE 1.1.0
-git.defaultAuthorizationControl = NAMED
-
-# Enable JGit-based garbage collection. (!!EXPERIMENTAL!!)
-#
-# USE AT YOUR OWN RISK!
-#
-# If enabled, the garbage collection executor scans all repositories once a day
-# at the hour of your choosing.  The GC executor will take each repository "offline",
-# one-at-a-time, to check if the repository satisfies it's GC trigger requirements.
-#
-# While the repository is offline it will be inaccessible from the web UI or from
-# any of the other services (git, rpc, rss, etc).
-#
-# Gitblit's GC Executor MAY NOT PLAY NICE with the other Git kids on the block,
-# especially on Windows systems, so if you are using other tools please coordinate
-# their usage with your GC Executor schedule or do not use this feature.
-#
-# The GC algorithm complex and the JGit team advises caution when using their
-# young implementation of GC.
-#
-# http://wiki.eclipse.org/EGit/New_and_Noteworthy/2.1#Garbage_Collector_and_Repository_Storage_Statistics
-#
-# EXPERIMENTAL
-# SINCE 1.2.0
-# RESTART REQUIRED
-git.enableGarbageCollection = false
-
-# Hour of the day for the GC Executor to scan repositories.
-# This value is in 24-hour time.
-#
-# SINCE 1.2.0
-git.garbageCollectionHour = 0
-
-# The default minimum total filesize of loose objects to trigger early garbage
-# collection.
-#
-# You may specify a custom threshold for a repository in the repository's settings.
-# Common unit suffixes of k, m, or g are supported.
-#
-# SINCE 1.2.0
-git.defaultGarbageCollectionThreshold = 500k
-
-# The default period, in days, between GCs for a repository.  If the total filesize
-# of the loose object exceeds *git.garbageCollectionThreshold* or the repository's
-# custom threshold, this period will be short-circuited. 
-#
-# e.g. if a repository collects 100KB of loose objects every day with a 500KB
-# threshold and a period of 7 days, it will take 5 days for the loose objects to
-# be collected, packed, and pruned.
-#
-# OR
-#
-# if a repository collects 10KB of loose objects every day with a 500KB threshold
-# and a period of 7 days, it will take the full 7 days for the loose objects to be
-# collected, packed, and pruned.
-#
-# You may specify a custom period for a repository in the repository's settings.
-#
-# The minimum value is 1 day since the GC Executor only runs once a day.
-#
-# SINCE 1.2.0
-git.defaultGarbageCollectionPeriod = 7
-
-# Number of bytes of a pack file to load into memory in a single read operation.
-# This is the "page size" of the JGit buffer cache, used for all pack access
-# operations. All disk IO occurs as single window reads. Setting this too large
-# may cause the process to load more data than is required; setting this too small
-# may increase the frequency of read() system calls.
-#
-# Default on JGit is 8 KiB on all platforms.
-#
-# Common unit suffixes of k, m, or g are supported.
-# Documentation courtesy of the Gerrit project.
-#
-# SINCE 1.0.0
-# RESTART REQUIRED
-git.packedGitWindowSize = 8k
-
-# Maximum number of bytes to load and cache in memory from pack files. If JGit
-# needs to access more than this many bytes it will unload less frequently used
-# windows to reclaim memory space within the process. As this buffer must be shared
-# with the rest of the JVM heap, it should be a fraction of the total memory available.
-#
-# The JGit team recommends setting this value larger than the size of your biggest
-# repository. This ensures you can serve most requests from memory.
-#
-# Default on JGit is 10 MiB on all platforms.
-#
-# Common unit suffixes of k, m, or g are supported.
-# Documentation courtesy of the Gerrit project.
-#
-# SINCE 1.0.0
-# RESTART REQUIRED
-git.packedGitLimit = 10m
-
-# Maximum number of bytes to reserve for caching base objects that multiple deltafied
-# objects reference. By storing the entire decompressed base object in a cache Git
-# is able to avoid unpacking and decompressing frequently used base objects multiple times.
-#
-# Default on JGit is 10 MiB on all platforms. You probably do not need to adjust
-# this value.
-#
-# Common unit suffixes of k, m, or g are supported.
-# Documentation courtesy of the Gerrit project.
-#
-# SINCE 1.0.0
-# RESTART REQUIRED
-git.deltaBaseCacheLimit = 10m
-
-# Maximum number of pack files to have open at once. A pack file must be opened
-# in order for any of its data to be available in a cached window.
-#
-# If you increase this to a larger setting you may need to also adjust the ulimit
-# on file descriptors for the host JVM, as Gitblit needs additional file descriptors
-# available for network sockets and other repository data manipulation.
-#
-# Default on JGit is 128 file descriptors on all platforms.
-# Documentation courtesy of the Gerrit project.
-#
-# SINCE 1.0.0
-# RESTART REQUIRED
-git.packedGitOpenFiles = 128
-
-# Largest object size, in bytes, that JGit will allocate as a contiguous byte
-# array. Any file revision larger than this threshold will have to be streamed,
-# typically requiring the use of temporary files under $GIT_DIR/objects to implement
-# psuedo-random access during delta decompression.
-#
-# Servers with very high traffic should set this to be larger than the size of
-# their common big files. For example a server managing the Android platform
-# typically has to deal with ~10-12 MiB XML files, so 15 m would be a reasonable
-# setting in that environment. Setting this too high may cause the JVM to run out
-# of heap space when handling very big binary files, such as device firmware or
-# CD-ROM ISO images. Make sure to adjust your JVM heap accordingly. 
-#
-# Default is 50 MiB on all platforms.
-#
-# Common unit suffixes of k, m, or g are supported.
-# Documentation courtesy of the Gerrit project.
-#
-# SINCE 1.0.0
-# RESTART REQUIRED
-git.streamFileThreshold = 50m
-
-# When true, JGit will use mmap() rather than malloc()+read() to load data from
-# pack files.  The use of mmap can be problematic on some JVMs as the garbage
-# collector must deduce that a memory mapped segment is no longer in use before
-# a call to munmap() can be made by the JVM native code.
-#
-# In server applications (such as Gitblit) that need to access many pack files,
-# setting this to true risks artificially running out of virtual address space, 
-# as the garbage collector cannot reclaim unused mapped spaces fast enough.
-#
-# Default on JGit is false. Although potentially slower, it yields much more
-# predictable behavior.
-# Documentation courtesy of the Gerrit project.
-#
-# SINCE 1.0.0
-# RESTART REQUIRED
-git.packedGitMmap = false
-
-#
-# Groovy Integration
-#
-
-# Location of Groovy scripts to use for Pre and Post receive hooks.
-# Use forward slashes even on Windows!!
-# e.g. c:/groovy
-#
-# RESTART REQUIRED
-# SINCE 0.8.0
-# BASEFOLDER
-groovy.scriptsFolder = ${baseFolder}/groovy
-
-# Specify the directory Grape uses for downloading libraries.
-# http://groovy.codehaus.org/Grape
-#
-# RESTART REQUIRED
-# SINCE 1.0.0
-# BASEFOLDER
-groovy.grapeFolder = ${baseFolder}/groovy/grape
-
-# Scripts to execute on Pre-Receive.
-#
-# These scripts execute after an incoming push has been parsed and validated
-# but BEFORE the changes are applied to the repository.  You might reject a
-# push in this script based on the repository and branch the push is attempting
-# to change.
-#
-# Script names are case-sensitive on case-sensitive file systems.  You may omit
-# the traditional ".groovy" from this list if your file extension is ".groovy" 
-#
-# NOTE:
-# These scripts are only executed when pushing to *Gitblit*, not to other Git
-# tooling you may be using.  Also note that these scripts are shared between
-# repositories. These are NOT repository-specific scripts!  Within the script
-# you may customize the control-flow for a specific repository by checking the
-# *repository* variable.
-#
-# SPACE-DELIMITED
-# CASE-SENSITIVE
-# SINCE 0.8.0
-groovy.preReceiveScripts =
-
-# Scripts to execute on Post-Receive.
-#
-# These scripts execute AFTER an incoming push has been applied to a repository.
-# You might trigger a continuous-integration build here or send a notification.
-#
-# Script names are case-sensitive on case-sensitive file systems.  You may omit
-# the traditional ".groovy" from this list if your file extension is ".groovy" 
-#
-# NOTE:
-# These scripts are only executed when pushing to *Gitblit*, not to other Git
-# tooling you may be using.  Also note that these scripts are shared between
-# repositories. These are NOT repository-specific scripts!  Within the script
-# you may customize the control-flow for a specific repository by checking the
-# *repository* variable.
-# 
-# SPACE-DELIMITED
-# CASE-SENSITIVE
-# SINCE 0.8.0
-groovy.postReceiveScripts =
-
-# Repository custom fields for Groovy Hook mechanism
-#
-# List of key=label pairs of custom fields to prompt for in the Edit Repository
-# page.  These keys are stored in the repository's git config file in the 
-# section [gitblit "customFields"].  Key names are alphanumeric only.  These
-# fields are intended to be used for the Groovy hook mechanism where a script
-# can adjust it's execution based on the custom fields stored in the repository
-# config.
-#
-# e.g. "commitMsgRegex=Commit Message Regular Expression" anotherProperty=Another
-#
-# SPACE-DELIMITED
-# SINCE 1.0.0
-groovy.customFields = 
-
-#
-# Fanout Settings
-#
-
-# Fanout is a PubSub notification service that can be used by Sparkleshare
-# to eliminate repository change polling.  The fanout service runs in a separate
-# thread on a separate port from the Gitblit http/https application.
-# This service is provided so that Sparkleshare may be used with Gitblit in
-# firewalled environments or where reliance on Sparkleshare's default notifications
-# server (notifications.sparkleshare.org) is unwanted.
-#
-# This service maintains an open socket connection from the client to the
-# Fanout PubSub service. This service may not work properly behind a proxy server.  
-
-# Specify the interface for Fanout to bind it's service.
-# You may specify an ip or an empty value to bind to all interfaces.
-# Specifying localhost will result in Gitblit ONLY listening to requests to
-# localhost.
-#
-# SINCE 1.2.1
-# RESTART REQUIRED
-fanout.bindInterface = localhost
-
-# port for serving the Fanout PubSub service.  <= 0 disables this service.
-# On Unix/Linux systems, ports < 1024 require root permissions.
-# Recommended value: 17000
-#
-# SINCE 1.2.1
-# RESTART REQUIRED
-fanout.port = 0
-
-# Use Fanout NIO service.  If false, a multi-threaded socket service will be used.
-# Be advised, the socket implementation spawns a thread per connection plus the
-# connection acceptor thread.  The NIO implementation is completely single-threaded.
-#
-# SINCE 1.2.1
-# RESTART REQUIRED
-fanout.useNio = true
-
-# Concurrent connection limit.  <= 0 disables concurrent connection throttling.
-# If > 0, only the specified number of concurrent connections will be allowed
-# and all other connections will be rejected.
-#
-# SINCE 1.2.1
-# RESTART REQUIRED
-fanout.connectionLimit = 0
-
-#
-# Authentication Settings
-#
-
-# Require authentication to see everything but the admin pages
-#
-# SINCE 0.5.0
-# RESTART REQUIRED
-web.authenticateViewPages = false
-
-# Require admin authentication for the admin functions and pages
-#
-# SINCE 0.5.0
-# RESTART REQUIRED
-web.authenticateAdminPages = true
-
-# Allow Gitblit to store a cookie in the user's browser for automatic
-# authentication.  The cookie is generated by the user service.
-#
-# SINCE 0.5.0
-web.allowCookieAuthentication = true
-
-# Config file for storing project metadata
-#
-# SINCE 1.2.0
-# BASEFOLDER
-web.projectsFile = ${baseFolder}/projects.conf
-
-# Either the full path to a user config file (users.conf)
-# OR the full path to a simple user properties file (users.properties)
-# OR a fully qualified class name that implements the IUserService interface.
-#
-# Alternative user services:
-#    com.gitblit.LdapUserService
-#    com.gitblit.RedmineUserService
-#
-# Any custom user service implementation must have a public default constructor.
-#
-# SINCE 0.5.0
-# RESTART REQUIRED
-# BASEFOLDER
-realm.userService = ${baseFolder}/users.conf
-
-# How to store passwords.
-# Valid values are plain, md5, or combined-md5.  md5 is the hash of password.
-# combined-md5 is the hash of username.toLowerCase()+password.
-# Default is md5.
-#
-# SINCE 0.5.0 
-realm.passwordStorage = md5
-
-# Minimum valid length for a plain text password.
-# Default value is 5.  Absolute minimum is 4.
-#
-# SINCE 0.5.0 
-realm.minPasswordLength = 5
-
-#
-# Gitblit Web Settings
-#
-# If blank Gitblit is displayed.
-#
-# SINCE 0.5.0
-web.siteName =
-
-# If *web.authenticateAdminPages*=true, users with "admin" role can create
-# repositories, create users, and edit repository metadata.
-#
-# If *web.authenticateAdminPages*=false, any user can execute the aforementioned
-# functions. 
-#
-# SINCE 0.5.0 
-web.allowAdministration = true
-
-# Allows rpc clients to list repositories and possibly manage or administer the 
-# Gitblit server, if the authenticated account has administrator permissions.
-# See *web.enableRpcManagement* and *web.enableRpcAdministration*.
-#
-# SINCE 0.7.0 
-web.enableRpcServlet = true
-
-# Allows rpc clients to manage repositories and users of the Gitblit instance,
-# if the authenticated account has administrator permissions.
-# Requires *web.enableRpcServlet=true*.
-#
-# SINCE 0.7.0 
-web.enableRpcManagement = false
-
-# Allows rpc clients to control the server settings and monitor the health of this
-# this Gitblit instance, if the authenticated account has administrator permissions.
-# Requires *web.enableRpcServlet=true* and *web.enableRpcManagement*.
-#
-# SINCE 0.7.0 
-web.enableRpcAdministration = false
-
-# Full path to a configurable robots.txt file.  With this file you can control
-# what parts of your Gitblit server respectable robots are allowed to traverse.
-# http://googlewebmastercentral.blogspot.com/2008/06/improving-on-robots-exclusion-protocol.html
-#
-# SINCE 1.0.0
-# BASEFOLDER
-web.robots.txt = ${baseFolder}/robots.txt
-
-# If true, the web ui layout will respond and adapt to the browser's dimensions.
-# if false, the web ui will use a 940px fixed-width layout.
-# http://twitter.github.com/bootstrap/scaffolding.html#responsive
-#
-# SINCE 1.0.0
-web.useResponsiveLayout = true
-
-# Allow Gravatar images to be displayed in Gitblit pages.
-#
-# SINCE 0.8.0
-web.allowGravatar = true
-
-# Allow dynamic zip downloads.
-#
-# SINCE 0.5.0   
-web.allowZipDownloads = true
-
-# If *web.allowZipDownloads=true* the following formats will be displayed for
-# download compressed archive links:
-#
-# zip   = standard .zip
-# tar   = standard tar format (preserves *nix permissions and symlinks)
-# gz    = gz-compressed tar
-# xz    = xz-compressed tar
-# bzip2 = bzip2-compressed tar
-#
-# SPACE-DELIMITED
-# SINCE 1.2.0
-web.compressedDownloads = zip gz
-
-# Allow optional Lucene integration. Lucene indexing is an opt-in feature.
-# A repository may specify branches to index with Lucene instead of using Git
-# commit traversal. There are scenarios where you may want to completely disable
-# Lucene indexing despite a repository specifying indexed branches.  One such
-# scenario is on a resource-constrained federated Gitblit mirror.
-#
-# SINCE 0.9.0
-web.allowLuceneIndexing = true
-
-# Allows an authenticated user to create forks of a repository
-#
-# set this to false if you want to disable all fork controls on the web site
-#
-web.allowForking = true
-
-# Controls the length of shortened commit hash ids
-#
-# SINCE 1.2.0
-web.shortCommitIdLength = 6
-
-# Use Clippy (Flash solution) to provide a copy-to-clipboard button.
-# If false, a button with a more primitive JavaScript-based prompt box will
-# offer a 3-step (click, ctrl+c, enter) copy-to-clipboard alternative.
-#
-# SINCE 0.8.0
-web.allowFlashCopyToClipboard = true
-
-# Default maximum number of commits that a repository may contribute to the
-# activity page, regardless of the selected duration.  This setting may be valuable
-# for an extremely busy server.  This value may also be configed per-repository
-# in Edit Repository. 0 disables this throttle.
-#
-# SINCE 1.2.0
-web.maxActivityCommits = 0
-
-# Default number of entries to include in RSS Syndication links
-#
-# SINCE 0.5.0
-web.syndicationEntries = 25
-
-# Show the size of each repository on the repositories page.
-# This requires recursive traversal of each repository folder.  This may be
-# non-performant on some operating systems and/or filesystems. 
-#
-# SINCE 0.5.2
-web.showRepositorySizes = true
-
-# List of custom regex expressions that can be displayed in the Filters menu
-# of the Repositories and Activity pages.  Keep them very simple because you
-# are likely to run into encoding issues if they are too complex.
-#
-# Use !!! to separate the filters 
-#
-# SINCE 0.8.0
-web.customFilters =
-
-# Show federation registrations (without token) and the current pull status
-# to non-administrator users. 
-#
-# SINCE 0.6.0
-web.showFederationRegistrations = false
-
-# This is the message displayed when *web.authenticateViewPages=true*.
-# This can point to a file with Markdown content.
-# Specifying "gitblit" uses the internal login message.
-#
-# SINCE 0.7.0
-# BASEFOLDER
-web.loginMessage = gitblit
-
-# This is the message displayed above the repositories table.
-# This can point to a file with Markdown content.
-# Specifying "gitblit" uses the internal welcome message.
-#
-# SINCE 0.5.0
-# BASEFOLDER
-web.repositoriesMessage = gitblit
-
-# Ordered list of charsets/encodings to use when trying to display a blob.
-# If empty, UTF-8 and ISO-8859-1 are used.  The server's default charset
-# is always appended to the encoding list.  If all encodings fail to cleanly
-# decode the blob content, UTF-8 will be used with the standard malformed
-# input/unmappable character replacement strings.
-# 
-# SPACE-DELIMITED
-# SINCE 1.0.0
-web.blobEncodings = UTF-8 ISO-8859-1
-
-# Manually set the default timezone to be used by Gitblit for display in the 
-# web ui.  This value is independent of the JVM timezone.  Specifying a blank
-# value will default to the JVM timezone.
-# e.g. America/New_York, US/Pacific, UTC, Europe/Berlin
-#
-# SINCE 0.9.0
-# RESTART REQUIRED
-web.timezone =
-
-# Use the client timezone when formatting dates.
-# This uses AJAX to determine the browser's timezone and may require more
-# server overhead because a Wicket session is created.  All Gitblit pages
-# attempt to be stateless, if possible.
-#
-# SINCE 0.5.0
-# RESTART REQUIRED
-web.useClientTimezone = false
-
-# Time format
-# <http://download.oracle.com/javase/6/docs/api/java/text/SimpleDateFormat.html>
-#
-# SINCE 0.8.0
-web.timeFormat = HH:mm
-
-# Short date format
-# <http://download.oracle.com/javase/6/docs/api/java/text/SimpleDateFormat.html>
-#
-# SINCE 0.5.0
-web.datestampShortFormat = yyyy-MM-dd
-
-# Long date format
-#
-# SINCE 0.8.0
-web.datestampLongFormat = EEEE, MMMM d, yyyy
-
-# Long timestamp format
-# <http://download.oracle.com/javase/6/docs/api/java/text/SimpleDateFormat.html>
-#
-# SINCE 0.5.0
-web.datetimestampLongFormat = EEEE, MMMM d, yyyy HH:mm Z
-
-# Mount URL parameters
-# This setting controls if pretty or parameter URLs are used.
-# i.e.
-# if true:
-#     http://localhost/commit/myrepo/abcdef
-# if false:
-#     http://localhost/commit/?r=myrepo&h=abcdef
-#
-# SINCE 0.5.0
-# RESTART REQUIRED
-web.mountParameters = true
-
-# Some servlet containers (e.g. Tomcat >= 6.0.10) disallow '/' (%2F) encoding
-# in URLs as a security precaution for proxies.  This setting tells Gitblit
-# to preemptively replace '/' with '*' or '!' for url string parameters.
-#
-# <https://issues.apache.org/jira/browse/WICKET-1303>
-# <http://tomcat.apache.org/security-6.html#Fixed_in_Apache_Tomcat_6.0.10>
-# Add *-Dorg.apache.tomcat.util.buf.UDecoder.ALLOW_ENCODED_SLASH=true* to your
-# *CATALINA_OPTS* or to your JVM launch parameters
-#
-# SINCE 0.5.2
-web.forwardSlashCharacter = /
-
-# Show other URLs on the summary page for accessing your git repositories
-# Use spaces to separate urls. {0} is the token for the repository name.
-# e.g.
-# web.otherUrls = ssh://localhost/git/{0} git://localhost/git/{0}
-#
-# SPACE-DELIMITED
-# SINCE 0.5.0
-web.otherUrls = 
-
-# Choose how to present the repositories list.
-#   grouped = group nested/subfolder repositories together (no sorting)
-#   flat = flat list of repositories (sorting allowed)
-#
-# SINCE 0.5.0
-web.repositoryListType = grouped
-
-# If using a grouped repository list and there are repositories at the
-# root level of your repositories folder, you may specify the displayed
-# group name with this setting.  This value is only used for web presentation.
-#
-# SINCE 0.5.0
-web.repositoryRootGroupName = main
-
-# Display the repository swatch color next to the repository name link in the 
-# repositories list. 
-#
-# SINCE 0.8.0
-web.repositoryListSwatches = true
-
-# Choose the diff presentation style: gitblt, gitweb, or plain
-#
-# SINCE 0.5.0
-web.diffStyle = gitblit
-
-# Control if email addresses are shown in web ui
-#
-# SINCE 0.5.0
-web.showEmailAddresses = true
-
-# Shows a combobox in the page links header with commit, committer, and author
-# search selection.  Default search is commit.
-#
-# SINCE 0.5.0
-web.showSearchTypeSelection = false
-
-# Generates a line graph of repository activity over time on the Summary page.
-# This uses the Google Charts API.
-#
-# SINCE 0.5.0 
-web.generateActivityGraph = true
-
-# The number of days to show on the activity page.
-# Value must exceed 0 else default of 14 is used
-#
-# SINCE 0.8.0
-web.activityDuration = 14
-
-# The number of commits to display on the summary page
-# Value must exceed 0 else default of 20 is used
-#
-# SINCE 0.5.0
-web.summaryCommitCount = 16
-
-# The number of tags/branches to display on the summary page.
-# -1 = all tags/branches
-# 0 = hide tags/branches
-# N = N tags/branches
-#
-# SINCE 0.5.0
-web.summaryRefsCount = 5
-
-# The number of items to show on a page before showing the first, prev, next
-# pagination links.  A default if 50 is used for any invalid value.
-#
-# SINCE 0.5.0
-web.itemsPerPage = 50
-
-# Registered file extensions to ignore during Lucene indexing
-#
-# SPACE-DELIMITED
-# SINCE 0.9.0
-web.luceneIgnoreExtensions = 7z arc arj bin bmp dll doc docx exe gif gz jar jpg lib lzh odg odf odt pdf ppt png so swf xcf xls xlsx zip
-
-# Registered extensions for google-code-prettify
-#
-# SPACE-DELIMITED
-# SINCE 0.5.0
-web.prettyPrintExtensions = c cpp cs css frm groovy htm html java js php pl prefs properties py rb scala sh sql xml vb
-
-# Registered extensions for markdown transformation
-#
-# SPACE-DELIMITED
-# CASE-SENSITIVE
-# SINCE 0.5.0
-web.markdownExtensions = md mkd markdown MD MKD
-
-# Image extensions
-#
-# SPACE-DELIMITED
-# SINCE 0.5.0
-web.imageExtensions = bmp jpg gif png 
-
-# Registered extensions for binary blobs
-#
-# SPACE-DELIMITED
-# SINCE 0.5.0
-web.binaryExtensions = jar pdf tar.gz zip
-
-# Aggressive heap management will run the garbage collector on every generated
-# page.  This slows down page generation a little but improves heap consumption. 
-#
-# SINCE 0.5.0
-web.aggressiveHeapManagement = false
-
-# Run the webapp in debug mode
-#
-# SINCE 0.5.0
-# RESTART REQUIRED
-web.debugMode = false
-
-# Enable/disable global regex substitutions (i.e. shared across repositories)
-#
-# SINCE 0.5.0
-regex.global = true
-
-# Example global regex substitutions
-# Use !!! to separate the search pattern and the replace pattern
-# searchpattern!!!replacepattern
-# SINCE 0.5.0
-regex.global.bug = \\b(Bug:)(\\s*[#]?|-){0,1}(\\d+)\\b!!!<a href="http://somehost/bug/$3">Bug-Id: $3</a>
-# SINCE 0.5.0
-regex.global.changeid = \\b(Change-Id:\\s*)([A-Za-z0-9]*)\\b!!!<a href="http://somehost/changeid/$2">Change-Id: $2</a>
-
-# Example per-repository regex substitutions overrides global
-# SINCE 0.5.0
-regex.myrepository.bug = \\b(Bug:)(\\s*[#]?|-){0,1}(\\d+)\\b!!!<a href="http://elsewhere/bug/$3">Bug-Id: $3</a>
-
-#
-# Mail Settings
-# SINCE 0.6.0
-#
-# Mail settings are used to notify administrators of received federation proposals
-#
-
-# ip or hostname of smtp server
-#
-# SINCE 0.6.0
-mail.server =
-
-# port to use for smtp requests
-#
-# SINCE 0.6.0
-mail.port = 25
-
-# debug the mail executor
-#
-# SINCE 0.6.0
-mail.debug = false
-
-# if your smtp server requires authentication, supply the credentials here
-#
-# SINCE 0.6.0
-mail.username =
-# SINCE 0.6.0
-mail.password =
-
-# from address for generated emails
-#
-# SINCE 0.6.0
-mail.fromAddress = 
-
-# List of email addresses for the Gitblit administrators
-#
-# SPACE-DELIMITED
-# SINCE 0.6.0
-mail.adminAddresses = 
-
-# List of email addresses for sending push email notifications.
-#
-# This key currently requires use of the sendemail.groovy hook script.
-# If you set sendemail.groovy in *groovy.postReceiveScripts* then email
-# notifications for all repositories (regardless of access restrictions!)
-# will be sent to these addresses.
-#
-# SPACE-DELIMITED
-# SINCE 0.8.0
-mail.mailingLists =
-
-#
-# Federation Settings
-# SINCE 0.6.0
-#
-# A Gitblit federation is a way to backup one Gitblit instance to another.
-#
-# *git.enableGitServlet* must be true to use this feature.
-
-# Your federation name is used for federation status acknowledgments.  If it is
-# unset, and you elect to send a status acknowledgment, your Gitblit instance
-# will be identified by its hostname, if available, else your internal ip address.
-# The source Gitblit instance will also append your external IP address to your
-# identification to differentiate multiple pulling systems behind a single proxy.
-#
-# SINCE 0.6.0
-federation.name =
-
-# Specify the passphrase of this Gitblit instance.
-#
-# An unspecified (empty) passphrase disables processing federation requests.
-#
-# This value can be anything you want: an integer, a sentence, an haiku, etc.
-# Keep the value simple, though, to avoid Java properties file encoding issues.
-#
-# Changing your passphrase will break any registrations you have established with other
-# Gitblit instances.
-#
-# CASE-SENSITIVE
-# SINCE 0.6.0
-# RESTART REQUIRED *(only to enable or disable federation)*
-federation.passphrase =
-
-# Control whether or not this Gitblit instance can receive federation proposals
-# from another Gitblit instance.  Registering a federated Gitblit is a manual
-# process.  Proposals help to simplify that process by allowing a remote Gitblit
-# instance to send your Gitblit instance the federation pull data.
-#
-# SINCE 0.6.0
-federation.allowProposals = false
-
-# The destination folder for cached federation proposals.
-# Use forward slashes even on Windows!!
-#
-# SINCE 0.6.0
-# BASEFOLDER
-federation.proposalsFolder = ${baseFolder}/proposals
-
-# The default pull frequency if frequency is unspecified on a registration
-#
-# SINCE 0.6.0
-federation.defaultFrequency = 60 mins
-
-# Federation Sets are named groups of repositories.  The Federation Sets are 
-# available for selection in the repository settings page.  You can assign a
-# repository to one or more sets and then distribute the token for the set.
-# This allows you to grant federation pull access to a subset of your available
-# repositories.  Tokens for federation sets only grant repository pull access.
-#
-# SPACE-DELIMITED
-# CASE-SENSITIVE
-# SINCE 0.6.0
-federation.sets = 
-
-# Federation pull registrations
-# Registrations are read once, at startup.
-#
-# RESTART REQUIRED
-#
-# frequency:
-#   The shortest frequency allowed is every 5 minutes
-#   Decimal frequency values are cast to integers
-#   Frequency values may be specified in mins, hours, or days
-#   Values that can not be parsed or are unspecified default to *federation.defaultFrequency*
-#
-# folder:
-#   if unspecified, the folder is *git.repositoriesFolder*
-#   if specified, the folder is relative to *git.repositoriesFolder*
-#
-# bare:
-#   if true, each repository will be created as a *bare* repository and will not
-#   have a working directory.
-#
-#   if false, each repository will be created as a normal repository suitable
-#   for local work.
-#
-# mirror:
-#   if true, each repository HEAD is reset to *origin/master* after each pull.
-#   The repository will be flagged *isFrozen* after the initial clone.
-#
-#   if false, each repository HEAD will point to the FETCH_HEAD of the initial
-#   clone from the origin until pushed to or otherwise manipulated.
-#
-# mergeAccounts:
-#   if true, remote accounts and their permissions are merged into your 
-#   users.properties file 
-#
-# notifyOnError:
-#   if true and the mail configuration is properly set, administrators will be
-#   notified by email of pull failures
-#
-# include and exclude:
-#   Space-delimited list of repositories to include or exclude from pull
-#   may be * wildcard to include or exclude all
-#   may use fuzzy match (e.g. org.eclipse.*)
-
-#
-# (Nearly) Perfect Mirror example
-#
-
-#federation.example1.url = https://go.gitblit.com
-#federation.example1.token = 6f3b8a24bf970f17289b234284c94f43eb42f0e4
-#federation.example1.frequency = 120 mins
-#federation.example1.folder =
-#federation.example1.bare = true 
-#federation.example1.mirror = true 
-#federation.example1.mergeAccounts = true
-
-#
-# Advanced Realm Settings
-#
-
-# URL of the LDAP server.
-# To use encrypted transport, use either ldaps:// URL for SSL or ldap+tls:// to
-# send StartTLS command.
-#
-# SINCE 1.0.0
-realm.ldap.server = ldap://localhost
-
-# Login username for LDAP searches.
-# If this value is unspecified, anonymous LDAP login will be used.
-# 
-# e.g. mydomain\\username
-#
-# SINCE 1.0.0
-realm.ldap.username = cn=Directory Manager
-
-# Login password for LDAP searches.
-#
-# SINCE 1.0.0
-realm.ldap.password = password
-
-# The LdapUserService must be backed by another user service for standard user
-# and team management.
-# default: users.conf
-#
-# SINCE 1.0.0
-# RESTART REQUIRED
-# BASEFOLDER
-realm.ldap.backingUserService = ${baseFolder}/users.conf
-
-# Delegate team membership control to LDAP.
-#
-# If true, team user memberships will be specified by LDAP groups.  This will
-# disable team selection in Edit User and user selection in Edit Team.
-#
-# If false, LDAP will only be used for authentication and Gitblit will maintain
-# team memberships with the *realm.ldap.backingUserService*.
-#
-# SINCE 1.0.0
-realm.ldap.maintainTeams = false
-
-# Root node for all LDAP users
-#
-# This is the root node from which subtree user searches will begin.
-# If blank, Gitblit will search ALL nodes.
-#
-# SINCE 1.0.0
-realm.ldap.accountBase = OU=Users,OU=UserControl,OU=MyOrganization,DC=MyDomain
-
-# Filter criteria for LDAP users
-#
-# Query pattern to use when searching for a user account. This may be any valid 
-# LDAP query expression, including the standard (&) and (|) operators.
-#
-# Variables may be injected via the ${variableName} syntax.
-# Recognized variables are:
-#    ${username} - The text entered as the user name
-#
-# SINCE 1.0.0
-realm.ldap.accountPattern = (&(objectClass=person)(sAMAccountName=${username}))
-
-# Root node for all LDAP groups to be used as Gitblit Teams
-#
-# This is the root node from which subtree team searches will begin.
-# If blank, Gitblit will search ALL nodes.  
-#
-# SINCE 1.0.0
-realm.ldap.groupBase = OU=Groups,OU=UserControl,OU=MyOrganization,DC=MyDomain
-
-# Filter criteria for LDAP groups
-#
-# Query pattern to use when searching for a team. This may be any valid 
-# LDAP query expression, including the standard (&) and (|) operators.
-#
-# Variables may be injected via the ${variableName} syntax.
-# Recognized variables are:
-#    ${username} - The text entered as the user name
-#    ${dn} - The Distinguished Name of the user logged in
-#
-# All attributes from the LDAP User record are available. For example, if a user
-# has an attribute "fullName" set to "John", "(fn=${fullName})" will be 
-# translated to "(fn=John)".
-#
-# SINCE 1.0.0
-realm.ldap.groupMemberPattern = (&(objectClass=group)(member=${dn}))
-
-# LDAP users or groups that should be given administrator privileges.
-#
-# Teams are specified with a leading '@' character.  Groups with spaces in the
-# name can be entered as "@team name".
-#
-# e.g. realm.ldap.admins = john @git_admins "@git admins"
-#
-# SPACE-DELIMITED
-# SINCE 1.0.0
-realm.ldap.admins = @Git_Admins
-
-# Attribute(s) on the USER record that indicate their display (or full) name.
-# Leave blank for no mapping available in LDAP.
-#
-# This may be a single attribute, or a string of multiple attributes.  Examples:
-#  displayName - Uses the attribute 'displayName' on the user record
-#  ${personalTitle}. ${givenName} ${surname} - Will concatenate the 3 
-#       attributes together, with a '.' after personalTitle
-#
-# SINCE 1.0.0
-realm.ldap.displayName = displayName
-
-# Attribute(s) on the USER record that indicate their email address.
-# Leave blank for no mapping available in LDAP.
-#
-# This may be a single attribute, or a string of multiple attributes.  Examples:
-#  email - Uses the attribute 'email' on the user record
-#  ${givenName}.${surname}@gitblit.com -Will concatenate the 2 attributes
-#       together with a '.' and '@' creating something like first.last@gitblit.com 
-#
-# SINCE 1.0.0
-realm.ldap.email = email
-
-# The RedmineUserService must be backed by another user service for standard user
-# and team management.
-# default: users.conf
-#
-# RESTART REQUIRED
-# BASEFOLDER
-realm.redmine.backingUserService = ${baseFolder}/users.conf
-
-# URL of the Redmine.
-realm.redmine.url = http://example.com/redmine
-
-#
-# Server Settings
-#
-
-# The temporary folder to decompress the embedded gitblit webapp. 
-#
-# SINCE 0.5.0
-# RESTART REQUIRED
-# BASEFOLDER
-server.tempFolder = ${baseFolder}/temp
-
-# Use Jetty NIO connectors.  If false, Jetty Socket connectors will be used.
-#
-# SINCE 0.5.0
-# RESTART REQUIRED
-server.useNio = true
-
-# Context path for the GO application.  You might want to change the context
-# path if running Gitblit behind a proxy layer such as mod_proxy.
-#
-# SINCE 0.7.0
-# RESTART REQUIRED
-server.contextPath = /
-
-# Standard http port to serve.  <= 0 disables this connector.
-# On Unix/Linux systems, ports < 1024 require root permissions.
-# Recommended value: 80 or 8080
-#
-# SINCE 0.5.0
-# RESTART REQUIRED
-server.httpPort = 0
-
-# Secure/SSL https port to serve. <= 0 disables this connector.
-# On Unix/Linux systems, ports < 1024 require root permissions.
-# Recommended value: 443 or 8443
-#
-# SINCE 0.5.0
-# RESTART REQUIRED
-server.httpsPort = 8443
-
-# Port for serving an Apache JServ Protocol (AJP) 1.3 connector for integrating
-# Gitblit GO into an Apache HTTP server setup.  <= 0 disables this connector.
-# Recommended value: 8009
-#
-# SINCE 0.9.0
-# RESTART REQUIRED
-server.ajpPort = 0
-
-# Specify the interface for Jetty to bind the standard connector.
-# You may specify an ip or an empty value to bind to all interfaces.
-# Specifying localhost will result in Gitblit ONLY listening to requests to
-# localhost.
-#
-# SINCE 0.5.0
-# RESTART REQUIRED
-server.httpBindInterface = localhost
-
-# Specify the interface for Jetty to bind the secure connector.
-# You may specify an ip or an empty value to bind to all interfaces.
-# Specifying localhost will result in Gitblit ONLY listening to requests to
-# localhost.
-#
-# SINCE 0.5.0
-# RESTART REQUIRED
-server.httpsBindInterface = localhost
-
-# Specify the interface for Jetty to bind the AJP connector.
-# You may specify an ip or an empty value to bind to all interfaces.
-# Specifying localhost will result in Gitblit ONLY listening to requests to
-# localhost.
-#
-# SINCE 0.9.0
-# RESTART REQUIRED
-server.ajpBindInterface = localhost
-
-# Alias of certificate to use for https/SSL serving.  If blank the first
-# certificate found in the keystore will be used. 
-#
-# SINCE 1.2.0
-# RESTART REQUIRED
-server.certificateAlias = localhost
-
-# Password for SSL keystore.
-# Keystore password and certificate password must match.
-# This is provided for convenience, its probably more secure to set this value
-# using the --storePassword command line parameter.
-#
-# If you are using the official JRE or JDK from Oracle you may not have the
-# JCE Unlimited Strength Jurisdiction Policy files bundled with your JVM.  Because
-# of this, your store/key password can not exceed 7 characters.  If you require
-# longer passwords you may need to install the JCE Unlimited Strength Jurisdiction
-# Policy files from Oracle.
-#
-# http://www.oracle.com/technetwork/java/javase/downloads/index.html
-#
-# Gitblit and the Gitblit Certificate Authority will both indicate if Unlimited
-# Strength encryption is available.
-#
-# SINCE 0.5.0
-# RESTART REQUIRED
-server.storePassword = gitblit
-
-# If serving over https (recommended) you might consider requiring clients to
-# authenticate with ssl certificates.  If enabled, only https clients with the
-# a valid client certificate will be able to access Gitblit.
-#
-# If disabled, client certificate authentication is optional and will be tried
-# first before falling-back to form authentication or basic authentication.
-#
-# Requiring client certificates to access any of Gitblit may be too extreme,
-# consider this carefully.
-#
-# SINCE 1.2.0
-# RESTART REQUIRED
-server.requireClientCertificates = false
-
-# Port for shutdown monitor to listen on.
-#
-# SINCE 0.5.0
-# RESTART REQUIRED
-server.shutdownPort = 8081
diff --git a/distrib/groovy/sendmail-html.groovy b/distrib/groovy/sendmail-html.groovy
deleted file mode 100644
index 1692073..0000000
--- a/distrib/groovy/sendmail-html.groovy
+++ /dev/null
@@ -1,516 +0,0 @@
-/*
- * 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.
- */
-import com.gitblit.GitBlit
-import com.gitblit.Keys
-import com.gitblit.models.RepositoryModel
-import com.gitblit.models.TeamModel
-import com.gitblit.models.UserModel
-import com.gitblit.utils.JGitUtils
-import java.text.SimpleDateFormat
-
-import org.eclipse.jgit.api.Status;
-import org.eclipse.jgit.api.errors.JGitInternalException;
-import org.eclipse.jgit.diff.DiffEntry;
-import org.eclipse.jgit.diff.DiffFormatter;
-import org.eclipse.jgit.diff.RawTextComparator;
-import org.eclipse.jgit.diff.DiffEntry.ChangeType;
-import org.eclipse.jgit.lib.Constants;
-import org.eclipse.jgit.lib.IndexDiff;
-import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.Repository
-import org.eclipse.jgit.lib.Config
-import org.eclipse.jgit.patch.FileHeader;
-import org.eclipse.jgit.revwalk.RevCommit
-import org.eclipse.jgit.revwalk.RevWalk;
-import org.eclipse.jgit.transport.ReceiveCommand
-import org.eclipse.jgit.transport.ReceiveCommand.Result
-import org.eclipse.jgit.treewalk.FileTreeIterator;
-import org.eclipse.jgit.treewalk.EmptyTreeIterator;
-import org.eclipse.jgit.treewalk.CanonicalTreeParser;
-import org.eclipse.jgit.util.io.DisabledOutputStream;
-import org.slf4j.Logger
-import groovy.xml.MarkupBuilder
-
-import java.io.IOException;
-import java.security.MessageDigest
-
-
-/**
- * Sample Gitblit Post-Receive Hook: sendmail-html
- *
- * The Post-Receive hook is executed AFTER the pushed commits have been applied
- * to the Git repository.  This is the appropriate point to trigger an
- * integration build or to send a notification.
- * 
- * This script is only executed when pushing to *Gitblit*, not to other Git
- * tooling you may be using.
- * 
- * If this script is specified in *groovy.postReceiveScripts* of gitblit.properties
- * or web.xml then it will be executed by any repository when it receives a
- * push.  If you choose to share your script then you may have to consider
- * tailoring control-flow based on repository access restrictions.
- *
- * Scripts may also be specified per-repository in the repository settings page.
- * Shared scripts will be excluded from this list of available scripts.
- * 
- * This script is dynamically reloaded and it is executed within it's own
- * exception handler so it will not crash another script nor crash Gitblit.
- *
- * If you want this hook script to fail and abort all subsequent scripts in the
- * chain, "return false" at the appropriate failure points.
- * 
- * Bound Variables:
- *  gitblit         Gitblit Server               com.gitblit.GitBlit
- *  repository      Gitblit Repository           com.gitblit.models.RepositoryModel
- *  user            Gitblit User                 com.gitblit.models.UserModel
- *  commands        JGit commands                Collection<org.eclipse.jgit.transport.ReceiveCommand>
- *  url             Base url for Gitblit         java.lang.String
- *  logger          Logs messages to Gitblit     org.slf4j.Logger
- *  clientLogger    Logs messages to Git client  com.gitblit.utils.ClientLogger
- *
- * Accessing Gitblit Custom Fields:
- *   def myCustomField = repository.customFields.myCustomField
- *  
- */
-
-com.gitblit.models.UserModel userModel = user
-
-// Indicate we have started the script
-logger.info("sendmail-html hook triggered by ${user.username} for ${repository.name}")
-
-/*
- * Primitive email notification.
- * This requires the mail settings to be properly configured in Gitblit.
- */
-
-Repository r = gitblit.getRepository(repository.name)
-
-// reuse existing repository config settings, if available
-Config config = r.getConfig()
-def mailinglist = config.getString('hooks', null, 'mailinglist')
-def emailprefix = config.getString('hooks', null, 'emailprefix')
-
-// set default values
-def toAddresses = []
-if (emailprefix == null) {
-    emailprefix = '[Gitblit]'
-}
-
-if (mailinglist != null) {
-    def addrs = mailinglist.split(/(,|\s)/)
-    toAddresses.addAll(addrs)
-}
-
-// add all mailing lists defined in gitblit.properties or web.xml
-toAddresses.addAll(GitBlit.getStrings(Keys.mail.mailingLists))
-
-// add all team mailing lists
-def teams = gitblit.getRepositoryTeams(repository)
-for (team in teams) {
-    TeamModel model = gitblit.getTeamModel(team)
-    if (model.mailingLists) {
-        toAddresses.addAll(model.mailingLists)
-    }
-}
-
-// add all mailing lists for the repository
-toAddresses.addAll(repository.mailingLists)
-
-// define the summary and commit urls
-def repo = repository.name
-def summaryUrl = url + "/summary?r=$repo"
-def baseCommitUrl = url + "/commit?r=$repo&h="
-def baseBlobDiffUrl = url + "/blobdiff/?r=$repo&h="
-def baseCommitDiffUrl = url + "/commitdiff/?r=$repo&h="
-def forwardSlashChar = gitblit.getString(Keys.web.forwardSlashCharacter, '/')
-
-if (gitblit.getBoolean(Keys.web.mountParameters, true)) {
-    repo = repo.replace('/', forwardSlashChar).replace('/', '%2F')
-    summaryUrl = url + "/summary/$repo"
-    baseCommitUrl = url + "/commit/$repo/"
-    baseBlobDiffUrl = url + "/blobdiff/$repo/"
-    baseCommitDiffUrl = url + "/commitdiff/$repo/"
-}
-
-class HtmlMailWriter {
-    Repository repository
-    def url
-    def baseCommitUrl
-    def baseCommitDiffUrl
-    def baseBlobDiffUrl
-    def mountParameters
-	def forwardSlashChar
-	def includeGravatar
-	def shortCommitIdLength
-    def commitCount = 0
-    def commands
-    def writer = new StringWriter();
-    def builder = new MarkupBuilder(writer)
-
-    def writeStyle() {
-        builder.style(type:"text/css", '''
-    .table td {
-        vertical-align: middle;
-    }
-    tr.noborder td {
-        border: none;
-        padding-top: 0px;
-    }
-    .gravatar-column {
-        width: 5%; 
-    }
-    .author-column {
-        width: 20%; 
-    }
-    .commit-column {
-        width: 5%; 
-    }
-    .status-column {
-        width: 10%;
-    }
-    .table-disable-hover.table tbody tr:hover td,
-    .table-disable-hover.table tbody tr:hover th {
-        background-color: inherit;
-    }
-    .table-disable-hover.table-striped tbody tr:nth-child(odd):hover td,
-    .table-disable-hover.table-striped tbody tr:nth-child(odd):hover th {
-      background-color: #f9f9f9;
-    }
-    ''')
-    }
-
-    def writeBranchTitle(type, name, action, number) {
-        builder.div('class' : 'pageTitle') {
-			builder.span('class':'project') {
-				mkp.yield "$type "
-				span('class': 'repository', name )
-				if (number > 0) {
-					mkp.yield " $action ($number commits)"
-				} else {
-					mkp.yield " $action"
-				}
-			}
-        }
-    }
-
-    def writeBranchDeletedTitle(type, name) {
-		builder.div('class' : 'pageTitle', 'style':'color:red') {
-			builder.span('class':'project') {
-				mkp.yield "$type "
-				span('class': 'repository', name )
-				mkp.yield " deleted"
-			}
-		}
-    }
-
-    def commitUrl(RevCommit commit) {
-        "${baseCommitUrl}$commit.id.name"
-    }
-
-    def commitDiffUrl(RevCommit commit) {
-        "${baseCommitDiffUrl}$commit.id.name"
-    }
-
-    def encoded(String path) {
-        path.replace('/', forwardSlashChar).replace('/', '%2F')
-    }
-
-    def blobDiffUrl(objectId, path) {
-        if (mountParameters) {
-            // REST style
-            "${baseBlobDiffUrl}${objectId.name()}/${encoded(path)}"
-        } else {
-            "${baseBlobDiffUrl}${objectId.name()}&f=${path}"
-        }
-
-    }
-
-    def writeCommitTable(commits, includeChangedPaths=true) {
-        // Write commits table
-        builder.table('class':"table table-disable-hover") {
-            thead {
-                tr {
-					th(colspan: includeGravatar ? 2 : 1, "Author")
-                    th( "Commit" )
-                    th( "Message" )
-                }
-            }
-            tbody() {
-
-                // Write all the commits
-                for (commit in commits) {
-                    writeCommit(commit)
-
-					if (includeChangedPaths) {
-						// Write detail on that particular commit
-						tr('class' : 'noborder') {
-							td (colspan: includeGravatar ? 3 : 2)
-							td (colspan:2) { writeStatusTable(commit) }
-						}
-					}
-                }
-            }
-        }
-    }
-
-    def writeCommit(commit) {
-        def abbreviated = repository.newObjectReader().abbreviate(commit.id, shortCommitIdLength).name()
-        def author = commit.authorIdent.name
-        def email = commit.authorIdent.emailAddress
-        def message = commit.shortMessage
-        builder.tr {
-			if (includeGravatar) {
-				td('class':"gravatar-column") {
-					img(src:gravatarUrl(email), 'class':"gravatar")
-				}
-			}
-            td('class':"author-column", author)
-            td('class':"commit-column") {
-                a(href:commitUrl(commit)) {
-                    span('class':"label label-info",  abbreviated )
-                }
-            }
-            td {
-                mkp.yield message
-                a('class':'link', href:commitDiffUrl(commit), " [commitdiff]" )
-            }
-        }
-    }
-
-    def writeStatusLabel(style, tooltip) {
-        builder.span('class' : style,  'title' : tooltip )
-    }
-
-    def writeAddStatusLine(ObjectId id, FileHeader header) {		
-        builder.td('class':'changeType') {
-            writeStatusLabel("addition", "addition")
-        }
-        builder.td {
-            a(href:blobDiffUrl(id, header.newPath), header.newPath)
-        }
-    }
-
-    def writeCopyStatusLine(ObjectId id, FileHeader header) {
-        builder.td('class':'changeType') {
-            writeStatusLabel("rename", "rename")
-        }
-        builder.td() {
-            a(href:blobDiffUrl(id, header.newPath), header.oldPath + " copied to " + header.newPath)
-        }
-    }
-
-    def writeDeleteStatusLine(ObjectId id, FileHeader header) {
-        builder.td('class':'changeType') {
-            writeStatusLabel("deletion", "deletion")
-        }
-        builder.td() {
-            a(href:blobDiffUrl(id, header.oldPath), header.oldPath)
-        }
-    }
-
-    def writeModifyStatusLine(ObjectId id, FileHeader header) {
-        builder.td('class':'changeType') {
-			writeStatusLabel("modification", "modification")
-        }
-        builder.td() {
-            a(href:blobDiffUrl(id, header.oldPath), header.oldPath)
-        }
-    }
-
-    def writeRenameStatusLine(ObjectId id, FileHeader header) {
-        builder.td('class':'changeType') {
-             writeStatusLabel("rename", "rename")
-        }
-        builder.td() {
-            mkp.yield header.oldPath
-			mkp.yieldUnescaped "<b> -&rt; </b>"
-			a(href:blobDiffUrl(id, header.newPath),  header.newPath)
-        }
-    }
-
-    def writeStatusLine(ObjectId id, FileHeader header) {
-        builder.tr {
-            switch (header.changeType) {
-                case ChangeType.ADD:
-                    writeAddStatusLine(id, header)
-                    break;
-                case ChangeType.COPY:
-                    writeCopyStatusLine(id, header)
-                    break;
-                case ChangeType.DELETE:
-                    writeDeleteStatusLine(id, header)
-                    break;
-                case ChangeType.MODIFY:
-                    writeModifyStatusLine(id, header)
-                    break;
-                case ChangeType.RENAME:
-                    writeRenameStatusLine(id, header)
-                    break;
-            }
-        }
-    }
-
-    def writeStatusTable(RevCommit commit) {
-        DiffFormatter formatter = new DiffFormatter(DisabledOutputStream.INSTANCE)
-        formatter.setRepository(repository)
-        formatter.setDetectRenames(true)
-        formatter.setDiffComparator(RawTextComparator.DEFAULT);
-
-        def diffs
-		RevWalk rw = new RevWalk(repository)
-        if (commit.parentCount > 0) {
-			RevCommit parent = rw.parseCommit(commit.parents[0].id)
-            diffs = formatter.scan(parent.tree, commit.tree)
-        } else {
-            diffs = formatter.scan(new EmptyTreeIterator(),
-                                   new CanonicalTreeParser(null, rw.objectReader, commit.tree))
-        }
-		rw.dispose()
-        // Write status table
-        builder.table('class':"plain") {
-            tbody() {
-                for (DiffEntry entry in diffs) {
-                    FileHeader header = formatter.toFileHeader(entry)
-                    writeStatusLine(commit.id, header)
-                }
-            }
-        }
-    }
-
-
-    def md5(text) {
-
-        def digest = MessageDigest.getInstance("MD5")
-
-        //Quick MD5 of text
-        def hash = new BigInteger(1, digest.digest(text.getBytes()))
-                         .toString(16)
-                         .padLeft(32, "0")
-        hash.toString()
-    }
-
-    def gravatarUrl(email) {
-        def cleaned = email.trim().toLowerCase()
-        "http://www.gravatar.com/avatar/${md5(cleaned)}?s=30"
-    }
-
-    def writeNavbar() {
-        builder.div('class':"navbar navbar-fixed-top") {
-            div('class':"navbar-inner") {
-                div('class':"container") {
-                    a('class':"brand", href:"${url}", title:"GitBlit") {
-                        img(src:"${url}/gitblt_25_white.png",
-                            width:"79",
-                            height:"25",
-                            'class':"logo")
-                    }
-                }
-            }
-        }
-    }
-
-    def write() {
-        builder.html {
-            head {
-                link(rel:"stylesheet", href:"${url}/bootstrap/css/bootstrap.css")
-                link(rel:"stylesheet", href:"${url}/gitblit.css")
-				link(rel:"stylesheet", href:"${url}/bootstrap/css/bootstrap-responsive.css")
-                writeStyle()
-            }
-            body {
-
-                writeNavbar()
-
-				div('class':"container") {
-
-                for (command in commands) {
-                    def ref = command.refName
-                    def refType = 'Branch'
-                    if (ref.startsWith('refs/heads/')) {
-                        ref  = command.refName.substring('refs/heads/'.length())
-                    } else if (ref.startsWith('refs/tags/')) {
-                        ref  = command.refName.substring('refs/tags/'.length())
-                        refType = 'Tag'
-                    }
-
-                    switch (command.type) {
-                        case ReceiveCommand.Type.CREATE:
-							def commits = JGitUtils.getRevLog(repository, command.oldId.name, command.newId.name).reverse()
-							commitCount += commits.size()
-							if (refType == 'Branch') {
-								// new branch
-								writeBranchTitle(refType, ref, "created", commits.size())
-								writeCommitTable(commits, true)
-							} else {
-								// new tag
-								writeBranchTitle(refType, ref, "created", 0)
-								writeCommitTable(commits, false)
-							}
-                            break
-                        case ReceiveCommand.Type.UPDATE:
-                            def commits = JGitUtils.getRevLog(repository, command.oldId.name, command.newId.name).reverse()
-                            commitCount += commits.size()
-                            // fast-forward branch commits table
-                            // Write header
-                            writeBranchTitle(refType, ref, "updated", commits.size())
-                            writeCommitTable(commits)
-                            break
-                        case ReceiveCommand.Type.UPDATE_NONFASTFORWARD:
-                            def commits = JGitUtils.getRevLog(repository, command.oldId.name, command.newId.name).reverse()
-                            commitCount += commits.size()
-                            // non-fast-forward branch commits table
-                            // Write header
-                            writeBranchTitle(refType, ref, "updated [NON fast-forward]", commits.size())
-                            writeCommitTable(commits)
-                            break
-                        case ReceiveCommand.Type.DELETE:
-                            // deleted branch/tag
-                            writeBranchDeletedTitle(refType, ref)
-                            break
-                        default:
-                            break
-                    }
-                }
-                }
-            }
-        }
-        writer.toString()
-    }
-
-}
-
-def mailWriter = new HtmlMailWriter()
-mailWriter.repository = r
-mailWriter.baseCommitUrl = baseCommitUrl
-mailWriter.baseBlobDiffUrl = baseBlobDiffUrl
-mailWriter.baseCommitDiffUrl = baseCommitDiffUrl
-mailWriter.forwardSlashChar = forwardSlashChar
-mailWriter.commands = commands
-mailWriter.url = url
-mailWriter.mountParameters = GitBlit.getBoolean(Keys.web.mountParameters, true)
-mailWriter.includeGravatar = GitBlit.getBoolean(Keys.web.allowGravatar, true)
-mailWriter.shortCommitIdLength = GitBlit.getInteger(Keys.web.shortCommitIdLength, 8)
-
-def content = mailWriter.write()
-
-// close the repository reference
-r.close()
-
-// tell Gitblit to send the message (Gitblit filters duplicate addresses)
-def repositoryName = repository.name.substring(0, repository.name.length() - 4)
-gitblit.sendHtmlMail("${emailprefix} ${userModel.displayName} pushed ${mailWriter.commitCount} commits => $repositoryName",
-                     content,
-                     toAddresses)
diff --git a/distrib/install-service-centos.sh b/distrib/install-service-centos.sh
deleted file mode 100644
index 5e2e592..0000000
--- a/distrib/install-service-centos.sh
+++ /dev/null
@@ -1,3 +0,0 @@
-cp gitblit-centos /etc/init.d/gitblit
-chmod +x /etc/init.d/gitblit
-sudo chkconfig --add gitblit
diff --git a/distrib/install-service.sh b/distrib/install-service.sh
deleted file mode 100644
index 75d7fcc..0000000
--- a/distrib/install-service.sh
+++ /dev/null
@@ -1,3 +0,0 @@
-cp gitblit /etc/init.d/
-chmod +x /etc/init.d/gitblit
-sudo update-rc.d gitblit defaults
\ No newline at end of file
diff --git a/distrib/installService.cmd b/distrib/installService.cmd
deleted file mode 100644
index d254d67..0000000
--- a/distrib/installService.cmd
+++ /dev/null
@@ -1,38 +0,0 @@
-@REM Install Gitblit as a Windows service.
-
-@REM gitblitw.exe (prunmgr.exe) is a GUI application for monitoring 
-@REM and configuring the Gitblit procrun service.
-@REM
-@REM By default this tool launches the service properties dialog
-@REM but it also has some other very useful functionality.
-@REM
-@REM http://commons.apache.org/daemon/procrun.html
-
-@REM arch = x86, amd64, or ia32
-SET ARCH=amd64
-
-@REM Be careful not to introduce trailing whitespace after the ^ characters.
-@REM Use ; or # to separate values in the --StartParams parameter.
-"%CD%\%ARCH%\gitblit.exe"  //IS//gitblit ^
-		 --DisplayName="gitblit" ^
-		 --Description="a pure Java Git solution" ^
-		 --Startup=auto ^
-		 --LogPath="%CD%\logs" ^
-		 --LogLevel=INFO ^
-		 --LogPrefix=gitblit ^
-		 --StdOutput=auto ^
-		 --StdError=auto ^
-		 --StartPath="%CD%" ^
-		 --StartClass=com.gitblit.Launcher ^
-		 --StartMethod=main ^
-		 --StartParams="--storePassword;gitblit;--baseFolder;%CD%\data" ^
-		 --StartMode=jvm ^
-		 --StopPath="%CD%" ^
-		 --StopClass=com.gitblit.Launcher ^
-		 --StopMethod=main ^
-		 --StopParams="--stop;--baseFolder;%CD%\data" ^
-		 --StopMode=jvm ^
-		 --Classpath="%CD%\gitblit.jar" ^
-		 --Jvm=auto ^
-		 --JvmMx=1024
-		 
\ No newline at end of file
diff --git a/docs/00_index.mkd b/docs/00_index.mkd
deleted file mode 100644
index 0812c3e..0000000
--- a/docs/00_index.mkd
+++ /dev/null
@@ -1,86 +0,0 @@
-## What is Gitblit?
-<div class="well" style="margin-left:5px;float:right;width:275px;padding: 10px 10px;">
-<b>Current Release %VERSION% (%BUILDDATE%)</b> <a href="releases.html">changelog</a>
-<div style="padding:5px;"><a style="width:175px;text-decoration:none;" class="btn btn-success" href="http://code.google.com/p/gitblit/downloads/detail?name=%GO%">Download Gitblit GO</a></div>
-<div style="padding:5px;"><a style="width:175px;text-decoration:none;" class="btn btn-danger" href="http://code.google.com/p/gitblit/downloads/detail?name=%WAR%">Download Gitblit WAR</a></div>
-<div style="padding:5px;"><a style="width:175px;text-decoration:none;" class="btn btn-info" href="http://code.google.com/p/gitblit/downloads/detail?name=%EXPRESS%">Download Gitblit Express</a></div>
-<div style="padding:5px;"><a style="width:175px;text-decoration:none;" class="btn btn-primary" href="http://code.google.com/p/gitblit/downloads/detail?name=%MANAGER%">Download Gitblit Manager</a></div>
-	<div style="text-align:center">
-		<a href="http://code.google.com/p/gitblit/downloads/detail?name=%API%">Gitblit API</a> | <a href="http://code.google.com/p/gitblit/downloads/detail?name=%FEDCLIENT%">Gitblit Federation Client</a>
-		<br/>
-		<a href="screenshots.html" title="Screenshots"><img style="margin-top:5px;border:1px solid #ccc;" src="thumbs/00.png" alt="Screenshots" /></a>
-	</div>
-
-	<div style="padding-top:5px;">
-	<table class="table condensed-table">
-		<tbody>
-		<tr><th>License</th><td><a href="http://www.apache.org/licenses/LICENSE-2.0">Apache License, version 2.0</a></td></tr>
-		<tr><th>Sources</th><td><a href="http://github.com/gitblit">GitHub</a> &amp; <a href="http://code.google.com/p/gitblit/source/list">GoogleCode</a></td></tr>		
-		<tr><th>Issues</th><td><a href="http://code.google.com/p/gitblit/issues/list">GoogleCode</a></td></tr>
-		<tr><th>Discussion</th><td><a href="http://groups.google.com/group/gitblit">Gitblit Group</a></td></tr>
-		<tr><th>Google+</th><td><a href="https://plus.google.com/114464678392593421684">Gitblit+</a></td></tr>
-		<tr><th>Ohloh</th><td><a target="_top" href="http://www.ohloh.net/p/gitblit"><img border="0" width="100" height="16" src="http://www.ohloh.net/p/gitblit/widgets/project_thin_badge.gif" alt="Ohloh project report for Gitblit" /></a></td></tr>
-		<tr><th>Donations</th><td>If you enjoy Gitblit and want to support its development, please consider making a donation to <a href="http://www.helpmegivetostjude.org/gitblit">St. Jude Children's Research Hospital</a>.
-		<a href="http://www.helpmegivetostjude.org/gitblit" alt="St. Jude Children's Research Hospital"><img style="padding-top:10px;" src="stjude_150x150.gif"/></a></td></tr>
-		</tbody>
-		</table>
-	</div>
-</div>
-
-Gitblit is an open-source, pure Java stack for managing, viewing, and serving [Git][git] repositories.  
-It's designed primarily as a tool for small workgroups who want to host centralized repositories.
-
-You can browse a live demo [here](https://demo-gitblit.rhcloud.com) hosted on [RedHat's OpenShift][rhcloud] cloud service.
-
-### GO: Single-Stack Solution
-
-*Gitblit GO* is an integrated, single-stack solution based on Jetty.
-
-You do not need Apache httpd, Perl, Git, or Gitweb.  Should you want to use some or all of those, you still can; Gitblit plays nice with the other kids on the block.
-
-This is what you should download if you want to go from zero to Git in less than 5 mins.
-
-All dependencies are bundled.
-
-### WAR: For Your Servlet Container
-*Gitblit WAR* is what you should download if you already have a servlet container available that you wish to use.  Jetty 6/7/8 and Tomcat 6/7 are known to work.  Generally, any Servlet 2.5 or Servlet 3.0 container should work.
-
-All dependencies are bundled.
-
-### Express: For the Cloud
-*Gitblit Express* is a prepared distribution for [RedHat's OpenShift][rhcloud] cloud service.
-
-All dependencies are bundled.
-
-### You decide how to use Gitblit
-
-Gitblit can be used as a dumb repository viewer with no administrative controls or user accounts.  
-Gitblit can be used as a complete Git stack for cloning, pushing, and repository access control.  
-Gitblit can be used without any other Git tooling (including actual Git) or it can cooperate with your established tools.
-
-### Easy Remote Management
-
-Administrators can create and manage all repositories, user accounts, and teams from the *Web UI*.  
-Administrators can create and manage all repositories, user accounts, and teams from the *JSON RPC interface* using the [Gitblit Manager](http://code.google.com/p/gitblit/downloads/detail?name=%MANAGER%) or your own custom tooling. 
-
-### Integration with Your Infrastructure
-
-- Groovy push hook scripts
-- Pluggable user service mechanism
-    - LDAP authentication with optional LDAP-controlled Team memberships
-    - Custom authentication, authorization, and user management
-- Rich RSS feeds
-- JSON-based RPC mechanism
-- [Java Client RSS/JSON API library](http://code.google.com/p/gitblit/downloads/detail?name=%API%) for custom integration
-
-### Backup Strategy
-
-Gitblit includes a backup mechanism (*federation*) which can be used to backup repositories and, optionally, user accounts, team definitions, server settings, & Groovy push hook scripts from your Gitblit instance to another Gitblit instance or to a [Gitblit Federation Client](http://code.google.com/p/gitblit/downloads/detail?name=%FEDCLIENT%).  Similarly, you can use the federation mechanism to aggregate individual workspace Gitblit instances to a common, centralized server.
-
-### Java Runtime Requirement
-
-Gitblit requires a Java 6 Runtime Environment (JRE) or a Java 6 Development Kit (JDK).
-
-[jgit]: http://eclipse.org/jgit "Eclipse JGit Site"
-[git]: http://git-scm.com "Official Git Site"
-[rhcloud]: https://openshift.redhat.com/app "RedHat OpenShift"
diff --git a/docs/01_features.mkd b/docs/01_features.mkd
deleted file mode 100644
index b99aa52..0000000
--- a/docs/01_features.mkd
+++ /dev/null
@@ -1,78 +0,0 @@
-## Standard Features (GO/WAR)
-- JGit SmartHTTP servlet
-- Browser and git client authentication
-- Four *per-repository* access restriction configurations with a Read-Only control flag
-    - ![anonymous](blank.png) *Anonymous View, Clone & Push*
-    - ![push](lock_go_16x16.png) *Authenticated Push*
-    - ![clone](lock_pull_16x16.png) *Authenticated Clone & Push*
-    - ![view](shield_16x16.png) *Authenticated View, Clone & Push*
-    - ![freeze](cold_16x16.png) Freeze repository (i.e. deny push, make read-only)
-- Six *per-user/team* repository access permissions
-    - **V** (view in web ui, RSS feeds, download zip)
-    - **R** (clone)
-    - **RW** (clone and push)
-    - **RWC** (clone and push with ref creation)
-    - **RWD** (clone and push with ref creation, deletion)
-    - **RW+** (clone and push with ref creation, deletion, rewind)
-- Optional feature to allow users to create personal repositories
-- Optional feature to fork a repository to a personal repository
-- Optional feature to create a repository on push
-- *Experimental* built-in Garbage Collection
-- Ability to federate with one or more other Gitblit instances
-- RSS/JSON RPC interface
-- Java/Swing Gitblit Manager tool
-- Gitweb inspired web UI
-- Responsive web UI that subtracts elements to be usable on phones, tablets, and desktop browsers
-- Groovy pre- and post- push hook scripts, per-repository or globally for all repositories
-- Email push notifications *(via sendmail.groovy push script)*
-- Lucene indexing of specified repository branches
-- Administrators may create, edit, rename, or delete repositories through the web UI or RPC interface
-- Administrators may create, edit, rename, or delete users through the web UI or RPC interface
-- Administrators may create, edit, rename, or delete teams through the web UI or RPC interface
-- Repository Owners may edit repositories through the web UI
-- Administrators and Repository Owners may set the default branch through the web UI or RPC interface
-- LDAP authentication and optional LDAP-controlled Team memberships
-- Redmine authentication
-- Gravatar integration
-- Git-notes display support
-- Submodule support
-- Push log based on a hidden, orphan branch refs/gitblit/pushes
-- Fanout PubSub notifications service for self-hosted [Sparkleshare](http://sparkleshare.org) use
-- gh-pages display support (Jekyll is not supported)
-- Branch metrics (uses Google Charts)
-- HEAD and Branch RSS feeds
-- Blame annotations view
-- Dates can optionally be displayed using the browser's reported timezone
-- Display of Author and Committer email addresses can be disabled
-- Case-insensitive searching of commit messages, authors, or committers
-- Dynamic zip downloads feature
-- Markdown file view support
-- Syntax highlighting for popular source code types
-- Customizable regular expression substitution for commit messages (i.e. bug or code review link integration)
-- Single text file for users configuration
-- Optional utility pages
-    - ![docs](book_16x16.png) Docs page which enumerates all Markdown files within a repository
-    - ![tickets](bug_16x16.png) **readonly** Ticgit ticket pages *(based on last MIT release bf57b032 2009-01-27)*
-- Translations
-    - English
-    - Japanese
-    - Spanish
-    - Polish
-    - Korean
-	- Brazilian Portuguese
-	- Dutch
-	- Chinese (zh_CN)
-
-## Gitblit GO Features
-- Out-of-the-box integrated stack requiring minimal configuration
-- Automatic generation of ssl certificate for https communications
-- Integrated GUI tool to facilitate x509 PKI including ssl and client certificate generation, client certificate revocation, and client certificate distribution
-- Single text file for configuring server and gitblit
-- A Windows service installation script and configuration tool
-- Built-in AJP connector for Apache httpd
-
-## Limitations
-- HTTP/HTTPS are the only supported Git protocols
-- Built-in access controls are not path-based, they are repository-based.
-
-[jgit]: http://eclipse.org/jgit "Eclipse JGit Site"
diff --git a/docs/01_screenshots.mkd b/docs/01_screenshots.mkd
deleted file mode 100644
index 74f6742..0000000
--- a/docs/01_screenshots.mkd
+++ /dev/null
@@ -1,138 +0,0 @@
-## Screenshots
-
-### Gitblit GO/WAR
-
-<ul class="thumbnails">
-<li class="span3">
-	<a class="thumbnail" rel="screenshots_group" href="screenshots/00.png" title="Repository List"><img alt="Repositories" src="thumbs/00.png" /></a>
-	<h5>Repository List</h5>
-</li>
-<li class="span3">
-	<a class="thumbnail" rel="screenshots_group" href="screenshots/00b.png" title="Repository List (Admin)"><img alt="Repositories (Admin)" src="thumbs/00b.png" /></a>
-	<h5>Repository List (Admin)</h5>
-</li>
-<li class="span3">
-	<a class="thumbnail" rel="screenshots_group" href="screenshots/00c.png" title="Activity"><img alt="Activity" src="thumbs/00c.png" /></a>
-	<h5>Activity</h5>
-</li>
-<li class="span3">
-	<a class="thumbnail" rel="screenshots_group" href="screenshots/00d.png" title="Lucene Search"><img alt="Lucene Search" src="thumbs/00d.png" /></a>
-	<h5>Lucene Search</h5>
-</li>
-<li class="span3">
-	<a class="thumbnail" rel="screenshots_group" href="screenshots/01c.png" title="Users &amp; Teams"><img alt="Users &amp; Teams" src="thumbs/01c.png" /></a>
-	<h5>Users &amp; Teams</h5>
-</li>
-<li class="span3">
-	<a class="thumbnail" rel="screenshots_group" href="screenshots/01.png" title="New User"><img alt="New User" src="thumbs/01.png" /></a>
-	<h5>New User</h5>
-</li>
-<li class="span3">
-	<a class="thumbnail" rel="screenshots_group" href="screenshots/01b.png" title="New Team"><img alt="New Team" src="thumbs/01b.png" /></a>
-	<h5>New Team</h5>
-</li>
-<li class="span3">
-	<a class="thumbnail" rel="screenshots_group" href="screenshots/02.png" title="Edit Repository"><img alt="Edit Repository" src="thumbs/02.png" /></a>
-	<h5>Edit Repository</h5>
-</li>
-<li class="span3">
-	<a class="thumbnail" rel="screenshots_group" href="screenshots/03.png" title="Repository Summary"><img alt="Summary" src="thumbs/03.png" /></a>
-	<h5>Repository Summary</h5>
-</li>
-<li class="span3">
-	<a class="thumbnail" rel="screenshots_group" href="screenshots/04.png" title="Repository Log"><img alt="Log" src="thumbs/04.png" /></a>
-	<h5>Repository Log</h5>
-</li>
-<li class="span3">
-	<a class="thumbnail" rel="screenshots_group" href="screenshots/05.png" title="Repository Tree"><img alt="Tree" src="thumbs/05.png" /></a>
-	<h5>Repository Tree</h5>
-</li>
-<li class="span3">
-	<a class="thumbnail" rel="screenshots_group" href="screenshots/06.png" title="Commit Page"><img alt="Commit Page" src="thumbs/06.png" /></a>
-	<h5>Commit Page</h5>
-</li>
-<li class="span3">
-	<a class="thumbnail" rel="screenshots_group" href="screenshots/07.png" title="Commit Diff"><img alt="Commit Diff" src="thumbs/07.png" /></a>
-	<h5>Commit Diff</h5>
-</li>
-<li class="span3">
-	<a class="thumbnail" rel="screenshots_group" href="screenshots/09.png" title="Branch Metrics"><img alt="Metrics" src="thumbs/09.png" /></a>
-	<h5>Branch Metrics</h5>
-</li>
-<li class="span3">
-	<a class="thumbnail" rel="screenshots_group" href="screenshots/08.png" title="Blob View with Syntax Highlighting"><img alt="Blob" src="thumbs/08.png" /></a>
-	<h5>Blob View with Syntax Highlighting</h5>
-</li>
-<li class="span3">
-	<a class="thumbnail" rel="screenshots_group" href="screenshots/11.png" title="Blame"><img alt="Blame" src="thumbs/11.png" /></a>
-	<h5>Blame</h5>
-</li>
-<li class="span3">
-	<a class="thumbnail" rel="screenshots_group" href="screenshots/12.png" title="Federation Panels"><img alt="Federation Panels" src="thumbs/12.png" /></a>
-	<h5>Federation Panels</h5>
-</li>
-<li class="span3">
-	<a class="thumbnail" rel="screenshots_group" href="screenshots/13.png" title="Detailed Status of a Registration"><img alt="Registration Status" src="thumbs/13.png" /></a>
-	<h5>Detailed Status of a Registration</h5>
-</li>
-<li class="span3">
-	<a class="thumbnail" rel="screenshots_group" href="screenshots/14.png" title="Send Proposal"><img alt="Propose" src="thumbs/14.png" /></a>
-	<h5>Send Proposal</h5>
-</li>
-<li class="span3">
-	<a class="thumbnail" rel="screenshots_group" href="screenshots/15.png" title="Empty Repository"><img alt="Empty Repository" src="thumbs/15.png" /></a>
-	<h5>Empty Repository</h5>
-</li>
-</ul>
-
-### Gitblit Manager
-
-<ul class="thumbnails">
-<li class="span3">
-	<a class="thumbnail" rel="screenshots_group" href="screenshots/m00.png" title="Repositories Panel"><img alt="Repositories Panel" src="thumbs/m00.png" /></a>
-	<h5>Repositories Panel</h5>
-</li>
-<li class="span3">
-	<a class="thumbnail" rel="screenshots_group" href="screenshots/m01.png" title="Search Dialog"><img alt="Search Dialog" src="thumbs/m01.png" /></a>
-	<h5>Search Dialog</h5>
-</li>
-<li class="span3">
-	<a class="thumbnail" rel="screenshots_group" href="screenshots/m02.png" title="Activity Panel"><img alt="Activity Panel" src="thumbs/m02.png" /></a>
-	<h5>Activity Panel</h5>
-</li>
-<li class="span3">
-	<a class="thumbnail" rel="screenshots_group" href="screenshots/m03.png" title="Subscriptions Dialog"><img alt="Subscriptions Dialog" src="thumbs/m03.png" /></a>
-	<h5>Subscriptions Dialog</h5>
-</li>
-<li class="span3">
-	<a class="thumbnail" rel="screenshots_group" href="screenshots/m04.png" title="Users Panel"><img alt="Users Panels" src="thumbs/m04.png" /></a>
-	<h5>Users Panel</h5>
-</li>
-<li class="span3">
-	<a rel="screenshots_group" href="screenshots/m05.png" title="Settings Panel"><img alt="Settings Panel" src="thumbs/m05.png" /></a>
-	<h5>Settings Panel</h5>
-</li>
-<li class="span3">
-	<a class="thumbnail" rel="screenshots_group" href="screenshots/m06.png" title="Status Panel"><img alt="Status Panel" src="thumbs/m06.png" /></a>
-	<h5>Status Panel</h5>
-</li>
-</ul>
-
-<ul class="thumbnails">
-<li class="span3">
-	<a class="thumbnail" rel="screenshots_group" href="screenshots/m07.png" title="Edit Repository Settings"><img alt="Repository Settings" src="thumbs/m07.png" /></a>
-	<h5>Edit Repository Settings</h5>
-</li>
-<li class="span3">
-	<a class="thumbnail" rel="screenshots_group" href="screenshots/m08.png" title="Edit Repository Access Restrictions"><img alt="Access Restrictions" src="thumbs/m08.png" /></a>
-	<h5>Edit Repository Access Restriction</h5>
-</li>
-<li class="span3">
-	<a class="thumbnail" rel="screenshots_group" href="screenshots/m09.png" title="Edit Repository Federation Settings"><img alt="Federation Settings" src="thumbs/m09.png" /></a>
-	<h5>Edit Repository Federation Settings</h5>
-</li>
-<li class="span3">
-	<a class="thumbnail" rel="screenshots_group" href="screenshots/m10.png" title="Edit User Dialog"><img alt="Edit User Dialog" src="thumbs/m10.png" /></a>
-	<h5>Edit User Dialog</h5>
-</li>
-</ul>
\ No newline at end of file
diff --git a/docs/01_setup.mkd b/docs/01_setup.mkd
deleted file mode 100644
index a7f7526..0000000
--- a/docs/01_setup.mkd
+++ /dev/null
@@ -1,767 +0,0 @@
-## Gitblit WAR Installation & Setup
-
-1. Download [Gitblit WAR %VERSION%](http://code.google.com/p/gitblit/downloads/detail?name=%WAR%) to the webapps folder of your servlet container.  
-2. You may have to manually extract the WAR (zip file) to a folder within your webapps folder.
-3. By default, the Gitblit webapp is configured through `WEB-INF/data/gitblit.properties`.<br/>
-Open `WEB-INF/data/gitblit.properties` in your favorite text editor and make sure to review and set:
-    - &lt;context-parameter&gt; *git.packedGitLimit* (set larger than the size of your largest repository)
-    - &lt;context-parameter&gt; *git.streamFileThreshold* (set larger than the size of your largest committed file)
-4. You may have to restart your servlet container. 
-5. Open your browser to <http://localhost/gitblit> or whatever the url should be.
-6. Enter the default administrator credentials: **admin / admin** and click the *Login* button  
-    **NOTE:** Make sure to change the administrator username and/or password!! 
-
-### WAR Data Location
-By default, Gitblit WAR stores all data (users, settings, repositories, etc) in `${contextFolder}/WEB-INF/data`.  This is fine for a quick setup, but there are many reasons why you don't want to keep your data within the webapps folder of your servlet container.  You may specify an external location for your data by editing `WEB-INF/web.xml` and manipulating the *baseFolder* context parameter.  Choose a location that is writeable by your servlet container.  Your servlet container may be smart enough to recognize the change and to restart Gitblit.
-
-On the next restart of Gitblit, Gitblit will copy the contents of the `WEB-INF/data` folder to your specified *baseFolder* **IF** the file `${baseFolder}/gitblit.properties` does not already exist.  This allows you to get going with minimal fuss.
-
-Specifying an alternate *baseFolder* also allows for simpler upgrades in the future.
-
-## Gitblit GO Installation & Setup
-
-1. Download and unzip [Gitblit GO %VERSION%](http://code.google.com/p/gitblit/downloads/detail?name=%GO%).  
-*Its best to eliminate spaces in the path name.* 
-2. The server itself is configured through a simple text file.<br/>
-Open `data/gitblit.properties` in your favorite text editor and make sure to review and set:
-    - *server.httpPort* and *server.httpsPort*
-    - *server.httpBindInterface* and *server.httpsBindInterface*  
-	- *server.storePassword*
-    **https** is strongly recommended because passwords are insecurely transmitted form your browser/git client using Basic authentication!
-    - *git.packedGitLimit* (set larger than the size of your largest repository)
-    - *git.streamFileThreshold* (set larger than the size of your largest committed file)
-3. Execute `authority.cmd` or `java -jar authority.jar --baseFolder data` from a command-line
-    1. fill out the fields in the *new certificate defaults* dialog
-	2. enter the store password used in *server.storePassword* when prompted.  This generates an SSL certificate for **localhost**.
-	3. you may want to generate an SSL certificate for the hostname or ip address hostnames you are serving from<br/>**NOTE:** You can only have **one** SSL certificate specified for a port.
-	5. exit the authority app
-4. Execute `gitblit.cmd` or `java -jar gitblit.jar --baseFolder data` from a command-line
-5. Open your browser to <http://localhost:8080> or <https://localhost:8443> depending on your chosen configuration.
-6. Enter the default administrator credentials: **admin / admin** and click the *Login* button    
-    **NOTE:** Make sure to change the administrator username and/or password!! 
-
-### GO Data Location
-
-By default, Gitblit GO stores all data (users, settings, repositories, etc) in the `data` subfolder of your GO installation.  You may specify an external location for your data on the command-line by setting the *--baseFolder* argument.  If you relocate the data folder then you must supply the *--baseFolder* argument to both GO and the Certificate Authority.
-
-If you are deploying Gitblit to a *nix platform, you might consider moving the data folder out of the GO installation folder and then creating a symlink named "data" that points to your moved folder.
-
-### Creating your own Self-Signed SSL Certificate
-Gitblit GO (and Gitblit Certificate Authority) automatically generates a Certificate Authority (CA) certificate and an ssl certificate signed by this CA certificate that is bound to *localhost*.
-
-Remote Eclipse/EGit/JGit clients (<= 2.2.0) will fail to communicate using this certificate because JGit always verifies the hostname of the certificate, regardless of the *http.sslVerify=false* client-side setting.
-
-The EGit failure message is something like:
-
-	Cannot get remote repository refs.
-	Reason: https:/myserver.com/git/myrepo.git: cannot open git-upload-pack
-
-If you want to serve your repositories to another machine over https then you will want to generate a new certificate for the hostname or ip address you are serving from.
-
-1. `authority.cmd` or `java -jar authority.jar --baseFolder data`
-2. Click the *new ssl certificate* button (red rosette in the toolbar in upper left of window)
-3. Enter the hostname or ip address
-4. Make sure the checkbox *serve https with this certificate* is checked
-5. In the keystore password prompt, enter the *server.storePassword* password
- 
-If you decide to change the value of *server.storePassword* (recommended) <u>after</u> you have already started Gitblit or Gitblit Certificate Authority, then you will have to delete the following files and then restart the Gitblit Certificate Authority app:
-
-1. data/serverKeyStore.jks
-2. data/serverTrustStore.jks
-3. data/certs/caKeyStore.jks
-4. data/certs/ca.crt
-5. data/certs/caRevocationList.crl (optional)
-
-### Client SSL Certificates
-SINCE 1.2.0
-
-Gitblit supports X509 certificate authentication.  This authentication method relies on your servlet container to validate/verify/trust your client certificate and can be used by your browser and your git client.
-
-All X509 certificates have a *distinguished name (DN)* which is a signature of several fields like:
-
-    C=US,O=Gitblit,OU=Gitblit,CN=james
-	
-Gitblit must be able to map the DN of the certificate to an *existing* account username.  The default mapping is to extract the *common name (CN)* value from the DN and use that as the account name.  If the CN is a valid account, then the user is authenticated.  The servlet container which runs Gitblit validates, verifies, and trusts the certificate passed to Gitblit.  If you need to specify an alternative DN mapping you may do so with the *git.certificateUsernameOIDs* setting, but this mapping must be matched to the user account name.
-
-How do you make your servlet container trust a client certificate?
-
-In the WAR variant, you will have to manually setup your servlet container to:
-
-1. want/need client certificates
-2. trust a CA certificate used to sign your client certificates
-3. generate client certificates signed by your CA certificate
-
-Alternatively, Gitblit GO is designed to facilitate use of client certificate authentication.  Gitblit GO ships with a tool that simplifies creation and management of client certificates, Gitblit Certificate Authority.
-
-#### Creating SSL Certificates with Gitblit Certificate Authority
-
-When you generate a new client certificate, a zip file bundle is created which includes a P12 keystore for browsers and a PEM keystore for Git.  Both of these are password-protected.  Additionally, a personalized README file is generated with setup instructions for popular browsers and Git.  The README is generated from `data\certs\instructions.tmpl` and can be modified to suit your needs.
-
-1. `authority.cmd` or `java -jar authority.jar --baseFolder data`
-2. Select the user for which to generate the certificate
-3. Click the *new certificate* button and enter the expiration date of the certificate.  You must also enter a password for the generated keystore.  This password is *not* the same as the user's login password.  This password is used to protect the privatekey and public certificate you will generate for the selected user.  You must also enter a password hint for the user.
-4. If your mail server settings are properly configured you will have a *send email* checkbox which you can use to immediately send the generated certificate bundle to the user.
-
-#### Certificate Inspection and Advanced Troubleshooting
-
-X509 certificates can be confusing and tricky even with the simplified Gitblit Certificate Authority tool.  If you find you need more tooling to understand your keystores, certificates, and certificate revocation lists (CRLs), I highly recommend [Portecle](http://portecle.sourceforge.net) which can be conveniently launched as a [Java Web Start app](http://portecle.sourceforge.net/webstart/portecle.jnlp).
-
-### Running as a Windows Service
-Gitblit uses [Apache Commons Daemon](http://commons.apache.org/daemon) to install and configure its Windows service.
-
-1. Review the contents of the `installService.cmd`
-2. Set the *ARCH* value as appropriate for your installed Java Virtual Machine.
-3. Add any necessary *--StartParams* as enumerated below in **Command-Line Parameters**.
-4. Execute the script.
-
-After service installation you can use the `gitblitw.exe` utility to control and modify the runtime settings of the service.<br/>
-Additional service definition options and runtime capabilities of `gitblitw.exe` (prunmgr.exe) are documented [here](http://commons.apache.org/daemon/procrun.html).
-
-**NOTE:**<br/>
-If you change the name of the service from *gitblit* you must also change the name of `gitblitw.exe` to match the new service name otherwise the connection between the service and the utility is lost, at least to double-click execution. 
-
-#### VM Considerations
-By default, the service installation script configures your Windows service to use your default JVM.  This setup usually defaults to a client VM.<br/>
-If you have installed a JDK, you might consider using the `gitblitw.exe` utility to manually specify the *server* VM.
-
-1. Execute `gitblitw.exe`
-2. On the *Java* tab uncheck *Use default*.
-3. Manually navigate your filesystem and specify the server VM with the `...` button<br/><pre>
-Java Virtual Machine:
-C:\Program Files\Java\jre6\bin\server\jvm.dll</pre>
-
-#### Command-Line Parameters
-Command-Line parameters override the values in `gitblit.properties` at runtime.
-
-    --baseFolder           The default base folder for all relative file reference settings
-	--repositoriesFolder   Git Repositories Folder
-    --userService          Authentication and Authorization Service (filename or fully qualified classname)
-    --useNio               Use NIO Connector else use Socket Connector.
-    --httpPort             HTTP port for to serve. (port <= 0 will disable this connector)
-    --httpsPort            HTTPS port to serve.  (port <= 0 will disable this connector)
-    --ajpPort              AJP port to serve.  (port <= 0 will disable this connector)
-	--alias                Alias in keystore of SSL cert to use for https serving
-    --storePassword        Password for SSL (https) keystore.
-    --shutdownPort         Port for Shutdown Monitor to listen on. (port <= 0 will disable this monitor)
-    --tempFolder           Folder for server to extract built-in webapp
-    
-**Example**
-
-    java -jar gitblit.jar --userService c:/myrealm.config --storePassword something --baseFolder c:/data
-
-#### Overriding Gitblit GO's Log4j Configuration
-
-You can override Gitblit GO's default Log4j configuration with a command-line parameter to the JVM.
-
-    java -Dlog4j.configuration=file:///home/james/log4j.properties -jar gitblit.jar <optional_gitblit_args>
-    
-For reference, here is [Gitblit's default Log4j configuration](https://github.com/gitblit/gitblit/blob/master/src/log4j.properties).  It includes some file appenders that are disabled by default. 
-    
-## Running Gitblit behind Apache
-
-Gitblit runs fine behind Apache.  You may use either *mod_proxy* (GO or WAR) or *mod_proxy_ajp* (GO).
-
-Each Linux distribution may vary on the exact configuration of Apache 2.2.  
-Here is a sample configuration that works on Debian 7.0 (Wheezy), your distribution may be different.
-
-1. First we need to make sure we have Apache's proxy modules available.  
-<pre>
-sudo su
-cd /etc/apache2/mods-enabled
-ln -s ../mods-available/proxy.load proxy.load
-ln -s ../mods-available/proxy_balancer.load proxy_balancer.load
-ln -s ../mods-available/proxy_http.load proxy_http.load
-ln -s ../mods-available/proxy_ajp.load proxy_ajp.load
-</pre>
-2. Then we need to make sure we are configuring Apache to use the proxy modules and to setup the proxied connection from Apache to Gitblit GO or from Apache to your chosen servlet container.  The following snippet is stored as `/etc/apache2/conf.d/gitblit`.  
-%BEGINCODE%
-# Turn off support for true Proxy behaviour as we are acting as 
-# a transparent proxy
-ProxyRequests Off
-
-# Turn off VIA header as we know where the requests are proxied
-ProxyVia Off
- 
-# Turn on Host header preservation so that the servlet container
-# can write links with the correct host and rewriting can be avoided.
-#
-# This is important for all git push/pull/clone operations.
-ProxyPreserveHost On
- 
-# Set the permissions for the proxy
-&lt;Proxy *&gt;
-	AddDefaultCharset off
-	Order deny,allow
-	Allow from all
-&lt;/Proxy&gt;
- 
-# The proxy context path must match the Gitblit context path.
-# For Gitblit GO, see server.contextPath in gitblit.properties.
-
-#ProxyPass /gitblit http://localhost:8080/gitblit
-#ProxyPassreverse /gitblit http://localhost:8080/gitblit
-
-# If your httpd frontend is https but you are proxying http Gitblit WAR or GO
-#Header edit Location &#94;http://([&#94;&#8260;]+)/gitblit/ https://&#36;1/gitblit/
-
-# Additionally you will want to tell Gitblit the original scheme and port
-#RequestHeader set X-Forwarded-Proto https
-#RequestHeader set X-Forwarded-Port 443
-
-# If you are using subdomain proxying then you will want to tell Gitblit the appropriate
-# context path for your repository url.
-# If you are not using subdomain proxying, then ignore this setting.
-#RequestHeader set X-Forwarded-Context /
-
-#ProxyPass /gitblit ajp://localhost:8009/gitblit
-%ENDCODE%  
-**Please** make sure to:  
-    1. Review the security of these settings as appropriate for your deployment
-    2. Uncomment the *ProxyPass* setting for whichever connection you prefer (http/ajp)
-    3. Correctly set the ports and context paths both in the *ProxyPass* definition and your Gitblit installation  
-    If you are using Gitblit GO you can easily configure the AJP connector by specifying a non-zero AJP port.  
-    Please remember that on Linux/UNIX, ports < 1024 require root permissions to open.
-    4. Set *web.mountParameters=false* in `gitblit.properties` or `web.xml` this will use parameterized URLs.  
-    Alternatively, you can respecify *web.forwardSlashCharacter*.
-
-## Upgrading Gitblit
-
-Any important changes to the setting keys or default values will always be mentioned in the [release log](releases.html).
-
-Gitblit v0.8.0 introduced a new default user service implementation which serializes and deserializes user objects into `users.conf`.  A `users.conf` file will be automatically created from an existing `users.properties` file on the first launch after an upgrade.  To use the `users.conf` service, *realm.userService=users.conf* must be set.  This revised user service allows for more sophisticated Gitblit user objects and will facilitate the development of more advanced features without adding the complexity of an embedded SQL database.
-
-`users.properties` and its user service implementation are deprecated as of v0.8.0.
-
-### Upgrading Gitblit WAR (1.2.1+)
-1. Make sure your `WEB-INF/web.xml` *baseFolder* context parameter is not `${contextFolder}/WEB-INF/data`!<br/>
-If it is, move your `WEB-INF/data` folder to a location writeable by your servlet container.
-2. Deploy new WAR
-3. Edit the new WAR's `WEB-INF/web.xml` file and set the *baseFolder* context parameter to your external baseFolder.
-4. Review and optionally apply any new settings as indicated in the [release log](releases.html) to `${baseFolder}/gitblit.properties`. 
- 
-### Upgrading Gitblit GO (1.2.1+)
- 
-1. Unzip Gitblit GO to a new folder
-2. Copy your `data` folder from your current Gitblit installation to the new folder and overwrite any conflicts
-3. Review and optionally apply any new settings as indicated in the [release log](releases.html) to `data/gitblit.properties`.
-
-In *nix systems, there are other tricks you can play like symlinking the `data` folder or symlinking the GO folder.
-All platforms support the *--baseFolder* command-line argument.
-
-### Upgrading Gitblit WAR (pre-1.2.1)
-
-1. Create a `data` as outlined in step 1 of *Upgrading Gitblit GO (pre-1.2.1)*
-2. Copy your existing web.xml to your data folder
-3. Deploy new WAR
-4. Copy the new WAR's `WEB-INF/data/gitblit.properties` file to your data folder
-5. Manually apply any changes you made to your original web.xml file to the gitblit.properties file you copied to your data folder
-6. Edit the new WAR's `WEB-INF/web.xml` file and set the *baseFolder* context parameter to your external baseFolder.
-
-### Upgrading Gitblit GO (pre-1.2.1)
-1. Create a `data` folder and copy the following files and folders to it:
-    - **users.conf*
-	- **projects.conf** *(if you have one)*
-	- **gitblit.properties**
-	- **serverKeystore.jks**
-	- **serverTrustStore.jks**
-	- *certs** folder
-	- **git** folder
-	- **groovy** folder
-	- **proposals** folder
-    - and any other custom files (robots.txt, welcome/login markdown files, etc)
-	- then edit your `gitblit.properties` file and adjust the following settings:
-        - *git.repositoriesFolder* = ${baseFolder}/git
-        - *groovy.scriptsFolder* = ${baseFolder}/groovy
-        - *groovy.grapeFolder* = ${baseFolder}/groovy/grape
-        - *web.projectsFile* = ${baseFolder}/projects.conf
-        - *realm.userService* = ${baseFolder}/users.conf
-        - *web.robots.txt* = ${baseFolder}/robots.txt
-        - *federation.proposalsFolder* = ${baseFolder}/proposals
-        - *realm.ldap.backingUserService* = ${baseFolder}/users.conf
-        - *realm.redmine.backingUserService* = ${baseFolder}/users.conf
-        - *server.tempFolder* = ${baseFolder}/temp
-
-2. Unzip Gitblit GO to a new folder
-3. Copy your `data` folder and overwrite the folder of the same name in the just-unzipped version
-4. Review and optionally apply any new settings as indicated in the [release log](releases.html) to `data/gitblit.properties`.
-
-**NOTE:** You may need to adjust your service definitions to include the `--baseFolder data` argument.
-
-#### Upgrading Windows Service
-You may need to delete your old service definition and install a new one depending on what has changed in the release.
-
-## Gitblit Configuration
-
-### Administering Repositories
-Repositories can be created, edited, renamed, and deleted through the web UI.  They may also be created, edited, and deleted from the command-line using real [Git](http://git-scm.com) or your favorite file manager and text editor.
-
-All repository settings are stored within the repository `.git/config` file under the *gitblit* section.
-
-    [gitblit]
-	    description = master repository
-	    owner = james
-	    useTickets = false
-	    useDocs = true
-	    showRemoteBranches = false
-	    accessRestriction = clone
-	    isFrozen = false
-	    showReadme = false
-	    federationStrategy = FEDERATE_THIS
-	    isFederated = false
-	    skipSizeCalculation = false
-	    federationSets = 
-
-#### Repository Names
-Repository names must be case-insensitive-unique but are CASE-SENSITIVE ON CASE-SENSITIVE FILESYSTEMS.  The name must be composed of letters, digits, or `/ _ - . ~`<br/>
-Whitespace is illegal.
-
-Repositories can be grouped within subfolders.  e.g. *libraries/mycoollib.git* and *libraries/myotherlib.git*
-
-All repositories created with Gitblit are *bare* and will automatically have *.git* appended to the name at creation time, if not already specified. 
-
-#### Repository Owner
-The *Repository Owner* has the special permission of being able to edit a repository through the web UI.  The Repository Owner is not permitted to rename the repository, delete the repository, or reassign ownership to another user.
-
-### Access Restrictions and Access Permissions
-![permissions matrix](permissions_matrix.png "Permissions and Restrictions")
-
-#### Discrete Permissions (Gitblit v1.2.0+)
-
-Since v1.2.0, Gitblit supports more discrete permissions.  While Gitblit does not offer a built-in solution for branch-based permissions like Gitolite, it does allow for the following repository access permissions:
-
-- **V** (view in web ui, RSS feeds, download zip)
-- **R** (clone)
-- **RW** (clone and push)
-- **RWC** (clone and push with ref creation)
-- **RWD** (clone and push with ref creation, deletion)
-- **RW+** (clone and push with ref creation, deletion, rewind)
-
-These permission codes are combined with the repository path to create a user permission:
-
-    RW:mygroup/myrepo.git
-
-#### Discrete Permissions with Regex Matching (Gitblit v1.2.0+)
-
-Gitblit also supports *case-insensitive* regex matching for repository permissions.  The following permission grants push privileges to all repositories in the *mygroup* folder.
-
-    RW:mygroup/.*
-
-##### Exclusions
-
-When using regex matching it may also be useful to exclude specific repositories or to exclude regex repository matches.  You may specify the **X** permission for exclusion.  The following example grants clone permission to all repositories except the repositories in mygroup.  The user/team will have no access whatsoever to these repositories.
-
-    X:mygroup/.*
-    R:.*
-
-##### Order is Important
-
-The preceding example should suggest that order of permissions is important with regex matching.  Here are the rules for determining the permission that is applied to a repository request:
-
-1. If the user is an admin or repository owner, then RW+
-2. Else if user has an explicit permission, use that
-3. Else check for the first regex match in user permissions
-4. Else check for the HIGHEST permission from team memberships
-    1. If the team is an admin team, then RW+
-    2. Else if a team has an explicit permission, use that
-    3. Else check for the first regex match in team permissions
-
-#### No-So-Discrete Permissions (Gitblit <= v1.1.0)
-
-Prior to v1.2.0, Gitblit has two main access permission groupings:  
-
-1. what you are permitted to do as an anonymous user
-2. **RW+** for any permitted user
-
-#### Committer Verification
-
-You may optionally enable committer verification which requires that each commit be committed by the authenticated user pushing the commits.  i.e. If Bob is pushing the commits, Bob **must** be the committer of those commits.
-
-**How is this enforced?**
-
-Bob must set his *user.name* and *user.email* values for the repository to match his Gitblit user account **BEFORE** committing to his repository.
-<pre>
-[user "bob"]
-    displayName = Bob Jones
-    emailAddress = bob@somewhere.com
-</pre>
-<pre>
-    git config user.name "Bob Jones"
-    git config user.email bob@somewhere.com	
-</pre>
-or
-
-    git config user.name bob
-    git config user.email bob@somewhere.com	
-
-If the Gitblit account does not specify an email address, then the committer email address is ignored.  However, if the account does specify an address it must match the committer's email address.  Display name or username can be used as the committer name.
-
-All checks are case-insensitive.
-
-**What about merges?**
-
-You can not use fast-forward merges on your client when using committer verification.  You must specify *--no-ff* to ensure that a merge commit is created with your identity as the committer.  Only the first parent chain is traversed when verifying commits.
-
-#### Push Log
-
-Gitblit v1.2.1 introduces an incomplete push mechanism.  All pushes are logged since 1.2.1, but the log has not yet been exposed through the web ui.  This will be a feature of an upcoming release.
-
-### Teams
-
-Since v0.8.0, Gitblit supports *teams* for the original `users.properties` user service and the current default user service `users.conf`.  Teams have assigned users and assigned repositories.  A user can be a member of multiple teams and a repository may belong to multiple teams.  This allows the administrator to quickly add a user to a team without having to keep track of all the appropriate repositories. 
-
-### Administering Users (users.conf, Gitblit v0.8.0+)
-All users are stored in the `users.conf` file or in the file you specified in `gitblit.properties`. Your file extension must be *.conf* in order to use this user service.
-
-The `users.conf` file uses a Git-style configuration format:
-
-    [user "admin"]
-	    password = admin
-	    role = "#admin"
-	    role = "#notfederated"
-	    repository = RW+:repo1.git
-	    repository = RW+:repo2.git
-	    
-	[user "hannibal"]
-		password = bossman
-		repository = RWD:topsecret.git
-		repository = RW+:ateam/[A-Za-z0-9-~_\\./]+
-
-	[user "faceman"]
-		password = vanity
-
-	[user "murdock"]
-		password = crazy		
-	    
-	[user "babaracus"]
-		password = grrrr
-	    
-	[team "ateam"]
-		user = hannibal
-		user = faceman
-		user = murdock
-		user = babaracus
-		repository = RW:topsecret.git
-		mailingList = list@ateam.org
-		postReceiveScript = sendmail
-
-The `users.conf` file allows flexibility for adding new fields to a UserModel object that the original `users.properties` file does not afford without imposing the complexity of relying on an embedded SQL database. 
-
-### Administering Users (users.properties, Gitblit v0.5.0 - v0.7.0)
-All users are stored in the `users.properties` file or in the file you specified in `gitblit.properties`. Your file extension must be *.properties* in order to use this user service.
-
-The format of `users.properties` loosely follows Jetty's convention for HashRealms:
-
-    username=password,role1,role2,role3...
-    @teamname=&mailinglist,!username1,!username2,!username3,repository1,repository2,repository3...
-
-### Usernames
-Usernames must be unique and are case-insensitive.  
-Whitespace is illegal.
-
-### Passwords
-User passwords are CASE-SENSITIVE and may be *plain*, *md5*, or *combined-md5* formatted (see `gitblit.properties` -> *realm.passwordStorage*).
-
-### User Roles
-There are four actual *roles* in Gitblit:
-
-- *#admin*, which grants administrative powers to that user
-- *#notfederated*, which prevents an account from being pulled by another Gitblit instance
-- *#create*, which allows the user the power to create personal repositories
-- *#fork*, which allows the user to create a personal fork of an existing Gitblit-hosted repository
-
-Administrators automatically have access to all repositories.  All other *roles* are repository permissions.  If a repository is access-restricted, the user must have the repository's name within his/her roles to bypass the access restriction.  This is how users are granted access to a restricted repository.
-
-**NOTE:**  
-The following roles are equivalent:
-
-- myrepo.git
-- RW+:myrepo.git
-
-This is to preserve backwards-compatibility with Gitblit <= 1.1.0 which granted rewind power to all access-permitted users.
-
-### Personal Repositories & Forks
-
-Personal Repositories and Forks are related but are controlled individually.
-
-#### Creating a Personal Repository
-A user may be granted the power to create personal repositories by specifying the *#create* role through the web ui or through the RPC mechanism via the Gitblit Manager.  Personal repositories are exactly like common/shared repositories except that the owner has a few additional administrative powers for that repository, like rename and delete.
-
-#### Creating a Fork
-A user may also be granted the power to fork an existing repository hosted on your Gitblit server to their own personal clone by specifying the *#fork* role through the web ui or via the Gitblit Manager.
-
-Forks are mostly likely personal repositories or common/shared repositories except for two important differences:
-
-1. Forks inherit a view/clone access list from the origin repository.  
-i.e. if Team A has clone access to the origin repository, then by default Team A also has clone access to the fork.  This is to facilitate collaboration.
-2. Forks are always listed in the fork network, regardless of any access restriction set on the fork.  
-In other words, if you fork *RepoA.git* to *~me/RepoA.git* and then set the access restriction of *~me/RepoA.git* to *Authenticated View, Clone, & Push* your fork will still be listed in the fork network for *RepoA.git*.
-
-If you really must have an invisible fork, the clone it locally, create a new personal repository for your invisible fork, and push it back to that personal repository.
-
-## Alternative Authentication and Authorization
-
-### LDAP Authentication
-*SINCE 1.0.0*
-
-LDAP can be used to authenticate Users and optionally control Team memberships.  When properly configured, Gitblit will delegate authentication to your LDAP server and will cache some user information in the usual users file (.conf or .properties).
-
-When using the LDAP User Service, new user accounts can not be manually created from Gitblit.  Gitblit user accounts are automatically created for new users on their first succesful authentication through Gitblit against the LDAP server.  It is also important to note that the LDAP User Service does not retrieve or store user passwords nor does it implement any LDAP-write functionality.
-
-To use the *LdapUserService* set *realm.userService=com.gitblit.LdapUserService* in your `gitblit.properties` file or your `web.xml` file and then configure the *realm.ldap* settings appropriately for your LDAP environment.
-
-#### Example LDAP Layout
-![block diagram](ldapSample.png "LDAP Sample")
-
-Please see [ldapUserServiceSampleData.ldif](https://github.com/gitblit/gitblit/blob/master/tests/com/gitblit/tests/resources/ldapUserServiceSampleData.ldif) to see the data in LDAP that reflects the above picture.
-
-#### Gitblit Settings for Example LDAP Layout
-The following are the settings required to configure Gitblit to authenticate against the example LDAP server with LDAP-controlled team memberships.
-
-<table class="table">
-<thead>
-<tr><th>parameter</th><th>value</th><th>description</th></tr>
-</thead>
-<tbody>
-<tr>
-  <th>realm.ldap.server</th><td>ldap://localhost:389</td>
-  <td>Tells Gitblit to connect to the LDAP server on localhost port 389.  The URL Must be of form ldap(s)://&lt;server&gt;:&lt;port&gt; with port being optional (389 for ldap, 636 for ldaps).</td>
-</tr>
-<tr>
-  <th>realm.ldap.username</th><td>cn=Directory Manager</td>
-  <td>The credentials that will log into the LDAP server</td>
-</tr>
-<tr>
-  <th>realm.ldap.password</th><td>password</td>
-  <td>The credentials that will log into the LDAP server</td>
-</tr>
-<tr>
-  <th>realm.ldap.backingUserService</th><td>users.conf</td>
-  <td>Where to store all information that is used by Gitblit.  All information will be synced here upon user login.</td>
-</tr>
-<tr>
-  <th>realm.ldap.maintainTeams</th><td>true</td>
-  <td>Are team memberships maintained in LDAP (<em>true</em>) or manually in Gitblit (<em>false</em>).</td>
-</tr>
-<tr>
-  <th>realm.ldap.accountBase</th><td>OU=Users,OU=UserControl,OU=MyOrganization,DC=MyDomain</td>
-  <td>What is the root node for all users in this LDAP system.  Subtree searches will start from this node.</td>
-</tr>
-<tr>
-  <th>realm.ldap.accountPattern</th><td>(&(objectClass=person)(sAMAccountName=${username}))</td><td>The LDAP search filter that will match a particular user in LDAP.  ${username} will be replaced with whatever the user enters as their username in the Gitblit login panel.</td>
-</tr>
-<tr>
-  <th>realm.ldap.groupBase</th><td>OU=Groups,OU=UserControl,OU=MyOrganization,DC=MyDomain</td>
-  <td>What is the root node for all teams in this LDAP system.  Subtree searches will start from this node.</td>
-</tr>
-<tr>
-  <th>realm.ldap.groupMemberPattern</th><td>(&(objectClass=group)(member=${dn}))</td><td>The LDAP search filter that will match all teams for the authenticating user.  ${username} will be replaced with whatever the user enters as their username in the Gitblit login panel.  Anything else in ${} will be replaced by Attributes from the User node.</td>
-</tr>
-<tr>
-  <th>realm.ldap.admins</th><td>@Git_Admins</td><td>A space-delimited list of usernames and/or teams that indicate admin status in Gitblit.  Teams are referenced with a leading <em>@</em> character.</td>
-</tr>
-</tbody>
-</table>
-
-#### LDAP In-Memory Server
-
-You can start Gitblit GO with an in-memory LDAP server by specifying the *--ldapLdifFile* command-line argument.  The LDAP server will listen on localhost of the port specified in *realm.ldap.url* of `gitblit.properties`.  Additionally, a root user record is automatically created for *realm.ldap.username* and *realm.ldap.password*.  Please note that the ldaps:// protocol is not supported for the in-memory server.
-
-### Custom Authentication
-This is the simplest choice where you implement custom authentication and delegate all other standard user and team operations to one of Gitblit's user service implementations.  This choice insulates your customization from changes in User and Team model classes and additional API that may be added to IUserService.
-
-Please subclass [com.gitblit.GitblitUserService](https://github.com/gitblit/gitblit/blob/master/src/com/gitblit/GitblitUserService.java) and override the *setup()* and *authenticate()* methods.  
-Make sure to set the *serviceImpl* field in your *setup()* method.
-
-You may use your subclass by specifying its fully qualified classname in the *realm.userService* setting.
-
-Your subclass must be on Gitblit's classpath and must have a public default constructor.  
-
-### Custom Everything
-Instead of maintaining a `users.conf` or `users.properties` file, you may want to integrate Gitblit into an existing environment.
-
-You may use your own custom *com.gitblit.IUserService* implementation by specifying its fully qualified classname in the *realm.userService* setting.
-
-Your user service class must be on Gitblit's classpath and must have a public default constructor.  
-Please see the following interface definition [com.gitblit.IUserService](https://github.com/gitblit/gitblit/blob/master/src/com/gitblit/IUserService.java).
-
-## Groovy Hook Scripts
-
-*SINCE 0.8.0*
-
-Gitblit uses Groovy for its push hook mechanism.  This mechanism only executes when pushing to Gitblit, not when pushing to some other Git tooling in your stack.
-
-The Groovy hook mechanism allows for dynamic extension of Gitblit to execute custom tasks on receiving and processing push events.  The scripts run within the context of your Gitblit instance and therefore have access to Gitblit's internals at runtime.
-
-### Rules, Requirements, & Behaviors
-1. Your Groovy scripts must be stored in the *groovy.scriptsFolder* as specified in `gitblit.properties` or `web.xml`.
-2. All script files must have the *.groovy* extension. Because of this you may omit the extension when specifying the script.
-3. Script filenames must not have spaces!
-4. Scripts must be explicitly specified to be executed, no scripts are *automatically* executed by name or extension.
-5. A script can be specified to run on *all repositories* by adding the script file name to *groovy.preReceiveScripts* or *groovy.postReceiveScripts* in `gitblit.properties` or `web.xml`.
-6. Scripts can be specified for a team.
-7. Scripts may also be specified per-repository in the repository's settings.
-8. Globally-specified scripts and team-specified scripts are excluded from the list of available scripts in a repository's settings 
-9. Globally-specified scripts are executed first, in their listed order; followed by team-specified scripts in their listed order by alphabetical team order; followed by per-repository scripts, in their listed order.
-10. A script may only be defined once in a pre-receive chain and once in a post-receive chain.  
-You may execute the same script on pre-receive and post-receive, just not multiple times within a pre-receive or post-receive event.
-11. Gitblit does not differentiate between what can be a pre-receive script and what can be a post-receive script.
-12. If a script *returns false* then the hook chain is aborted and none of the subsequent scripts will execute.
-
-Some sample scripts are included in the GO and WAR distributions to show you how you can tap into Gitblit with the provided bound variables.  Additional implementation details may be specified in the header comment of these examples.
-
-Hook contributions and improvements are welcome.
-
-### Grapes
-
-*SINCE 1.0.0*
-
-[Grape](http://groovy.codehaus.org/Grape) lets you quickly add maven repository dependencies to your Groovy hook script.  
-
-<blockquote>Grape (The Groovy Adaptable Packaging Engine or Groovy Advanced Packaging Engine) is the infrastructure enabling the grab() calls in Groovy, a set of classes leveraging <a href="http://ant.apache.org/ivy">Ivy</a> to allow for a repository driven module system for Groovy. This allows a developer to write a script with an essentially arbitrary library requirement, and ship just the script. Grape will, at runtime, download as needed and link the named libraries and all dependencies forming a transitive closure when the script is run from existing repositories such as Ibiblio, Codehaus, and java.net.</blockquote>
-
-%BEGINCODE%
-// create and use a primitive array
-import org.apache.commons.collections.primitives.ArrayIntList
-
-@Grab(group='commons-primitives', module='commons-primitives', version='1.0')
-def createEmptyInts() { new ArrayIntList() }
-
-def ints = createEmptyInts()
-ints.add(0, 42)
-assert ints.size() == 1
-assert ints.get(0) == 42
-%ENDCODE%
-
-### Custom Fields
-
-*SINCE 1.0.0*
-
-Gitblit allows custom repository string fields to be defined in `gitblit.properties` or `web.xml`.  Entry textfields are automatically created for these fields in the Edit Repository page of Gitblit and the Edit Repository dialog of the Gitblit Manager.  These fields are accessible from your Groovy hook scripts as
-
-    repository.customFields.myField
-
-This feature allows you to customize the behavior of your hook scripts without hard-coding values in the hook scripts themselves.
-
-### Pre-Receive
-
-Pre-Receive scripts execute after the pushed objects have all been written to the Git repository but before the refs have been updated to point to these new objects.
-
-This is the appropriate point to block a push and is how many Git tools implement branch-write permissions.
-
-### Post-Receive
-
-Post-Receive scripts execute after all refs have been updated.
-
-This is the appropriate point to trigger continuous integration builds or send email notifications, etc.
-
-## Push Email Notifications
-
-Gitblit implements email notifications in *sendmail.groovy* which uses the Groovy Hook Script mechanism.  This allows for dynamic customization of the notification process at the installation site and serves as an example push script.
-
-### Enabling Push Notifications
-
-In order to send email notifications on a push to Gitblit, this script must be specified somewhere in the *post-receive* script chain.  
-You may specify *sendmail* in one of three places:
-
-1. *groovy.postReceiveScripts* in `gitblit.properties` or `web.xml`, globally applied to all repositories
-2. post-receive scripts of a Team definition
-3. post-receive scripts of a Repository definition
-
-### Destination Addresses
-
-Gitblit does not currently support individual subscriptions to repositories; i.e. a *user* can not subscribe or unsubscribe from push notifications.
-
-However, Repository Managers and Administrators can specify subscribed email addresses in one of three places:
-
-1. *mail.mailingLists* in `gitblit.properties` or `web.xml`, globally applied to all push-notified repositories
-2. mailing lists in a Team definition, applied to all repositories that are part of the team definition
-3. mailing lists in a Repository definition
-
-All three sources are checked and merged into a unique list of destination addresses for push notifications.
-
-**NOTE:**  
-Care should be taken when devising your notification scheme as it relates to any VIEW restricted repositories you might have.  Setting a global mailing list and activating push notifications for a VIEW restricted repository may send unwanted emails.
-
-## Lucene Search Integration
-
-*SINCE 0.9.0*
-
-Repositories may optionally be indexed using the Lucene search engine.  The Lucene search offers several advantages over commit-traversal search:
-
-1. very fast commit and blob searches
-2. multi-term searches
-3. term-highlighted and syntax-highlighted fragment matches
-4. multi-repository searches
-
-### How do I use it?
-
-First you must ensure that *web.allowLuceneIndexing* is set *true* in `gitblit.properties` or `web.xml`.  Then you must understand that Lucene indexing is an opt-in feature which means that no repositories are automatically indexed.  
-Like anything else, this design has pros and cons.
-
-#### Pros
-1. no wasted cycles indexing repositories you will never search
-2. you specify exactly what branches are indexed; experimental/dead/personal branches can be ignored
-
-#### Cons
-1. you specify exactly what branches are indexed
-
-#### I have 300 repositories and you want me to specify indexed branches on each one??
-
-Yeah, I agree that is inconvenient.
-
-If you are using Gitblit GO there is a utility script `add-indexed-branch.cmd` which allows you to specify an indexed branch for many repositories in one step.
-
-If you are using Gitblit WAR then, at present, you are out of luck unless you write your own script to traverse your repositories and use native Git to manipulate each repository config.
-
-    git config --add gitblit.indexBranch "default"
-    git config --add gitblit.indexBranch "refs/heads/master"
-
-#### Indexing Branches
-You may specify which branches should be indexed per-repository in the *Edit Repository* page.  New/empty repositories may only specify the *default* branch which will resolve to whatever commit HEAD points to or the most recently updated branch if HEAD is unresolvable.
-
-Indexes are built and incrementally updated on a 2 minute cycle so you may have to wait a few minutes before your index is built or before your latest pushes get indexed.
-
-**NOTE:**  
-After specifying branches, only the content from those branches can be searched via Gitblit.  Gitblit will automatically redirect any queries entered on a repository's search box to the Lucene search page. Repositories that do not specify any indexed branches will use the traditional commit-traversal search.
-
-#### Adequate Heap
-
-The initial indexing of an existing repository can potentially exhaust the memory allocated to your Java instance and may throw OutOfMemory exceptions.  Be sure to provide your Gitblit server adequate heap space to index your repositories.  The heap is set using the *-Xmx* JVM parameter in your Gitblit launch command (e.g. -Xmx1024M).
-
-#### Why does Gitblit check every 2 mins for repository/branch changes?
-
-Gitblit has to balance its design as a complete, integrated Git server and its utility as a repository viewer in an existing Git setup.
-
-Gitblit could build indexes immediately on *edit repository* or on *receiving pushes*, but that design would not work if someone is pushing via ssh://, git://, or file:// (i.e. not pushing to Gitblit http(s)://).  For this reason Gitblit has a polling mechanism to check for ref changes every 2 mins.  This design works well for all use cases, aside from adding a little lag in updating the index.
-
-## Client Setup and Configuration
-### Https with Self-Signed Certificates
-You must tell Git/JGit not to verify the self-signed certificate in order to perform any remote Git operations.
-
-**NOTE:**  
-The default self-signed certificate generated by Gitlbit GO is bound to *localhost*.  
-If you are using Eclipse/EGit/JGit clients, you will have to generate your own certificate that specifies the exact hostname used in your clone/push url.  
-You must do this because Eclipse/EGit/JGit (<= 2.1.0) always verifies certificate hostnames, regardless of the *http.sslVerify=false* client-side setting. 
- 
-- **Eclipse/EGit/JGit**
-    1. Window->Preferences->Team->Git->Configuration
-    2. Click the *New Entry* button
-    3. <pre>Key = <em>http.sslVerify</em>
-Value = <em>false</em></pre>
-- **Command-line Git** ([Git-Config Manual Page](http://www.kernel.org/pub/software/scm/git/docs/git-config.html))  
-<pre>git config --global --bool --add http.sslVerify false</pre>
-
-### Http Post Buffer Size
-You may find the default post buffer of your git client is too small to push large deltas to Gitblit.  Sometimes this can be observed on your client as *hanging* during a push.  Other times it can be observed by git erroring out with a message like: error: RPC failed; result=52, HTTP code = 0.
-
-This can be adjusted on your client by changing the default post buffer size:
-<pre>git config --global http.postBuffer 524288000</pre>
-
-### Cloning an Access Restricted Repository 
-- **Eclipse/EGit/JGit**  
-Nothing special to configure, EGit figures out everything.
-<pre>https://yourserver/git/your/repository</pre>
-- **Command-line Git**  
-My testing indicates that your username must be embedded in the url.  YMMV.  
-<pre>https://username@yourserver/git/your/repository</pre>
-
diff --git a/docs/02_federation.mkd b/docs/02_federation.mkd
deleted file mode 100644
index d83a9e3..0000000
--- a/docs/02_federation.mkd
+++ /dev/null
@@ -1,339 +0,0 @@
-## Federating Gitblit
-
-*SINCE 0.6.0*
-
-A Gitblit federation is a mechanism to clone repositories and keep them in sync from one Gitblit instance to another.  Federation can be used to maintain a mirror of your Gitblit instance, to aggregate repositories from developer workstations, or to initially clone groups of repositories to developer workstations.  If you are/were a Subversion user you might think of this as [svn-sync](http://svnbook.red-bean.com/en/1.5/svn.ref.svnsync.html), but better.
-
-If your Gitblit instance allows federation and it is properly registered with another Gitblit instance, each of the *non-excluded* repositories of your Gitblit instance can be mirrored, in their entirety, to the pulling Gitblit instance.  You may optionally allow pulling of user accounts and backup of server settings.
-
-The federation feature should be considered a security backdoor and enabled or disabled as appropriate for your installation.<br/>
-Please review all the documentation to understand how it works and its limitations.
-
-![block diagram](fed_aggregation.png "Gitblit Federation Aggregation")
-
-### Important Changes to Note
-
-The *Gitblit 0.8.0* federation protocol adds retrieval of teams and referenced push scripts.  Older clients will not know to request team or push script information. 
-
-The *Gitblit 0.7.0* federation protocol is incompatible with the 0.6.0 federation protocol because of a change in the way timestamps are formatted.
-
-Gitblit 0.6.0 uses the default [google-gson](http://google-gson.googlecode.com) timestamp serializer which generates locally formatted timestamps.  Unfortunately, this creates problems for distributed repositories and distributed developers.  Gitblit 0.7.0 corrects this error by serializing dates to the [iso8601](http://en.wikipedia.org/wiki/ISO_8601) standard.  As a result 0.7.0 is not compatible with 0.6.0.  A partial backwards-compatibility fallback was considered but it would only work one direction and since the federation mechanism is bidirectional it was not implemented.
-
-### Origin Gitblit Instance Requirements
-
-- *git.enableGitServlet* must be true, all Git clone and pull requests are handled through Gitblit's JGit servlet
-- *federation.passphrase* must be non-empty
-- The Gitblit origin instance must be http/https accessible by the pulling Gitblit instance.<br/>That may require configuring port-forwarding on your router and/or opening ports on your firewall.
-
-#### federation.passphrase
-
-The passphrase is used to generate permission tokens that can be shared with other Gitblit instances.
-
-The passphrase value never needs to be shared, although if you give another Gitblit instance the *ALL* federation token then your passphrase will be transferred/backed-up along with all other server settings.
-
-This value can be anything you want: an integer, a sentence, an haiku, etc.  You should probably keep the passphrase simple and use standard Latin characters to prevent Java properties file encoding errors.  The tokens generated from this value are affected by case, so consider this value CASE-SENSITIVE.
-
-The federation feature is completely disabled if your passphrase value is empty.
-
-**NOTE**:  
-Changing your *federation.passphrase* will break any registrations you have established with other Gitblit instances.
-
-### Pulling Gitblit Instance Requirements
-
- - consider setting *federation.allowProposals=true* to facilitate the registration process from origin Gitblit instances
- - properly registered Gitblit instance including, at a minimum, url, *federation token*, and update frequency
-
-### Controlling What Gets Pulled
-
-If you want your repositories (and optionally users accounts and settings) to be pulled by another Gitblit instance, you need to register your origin Gitblit instance with a pulling Gitblit instance by providing the url of your Gitblit instance and a federation token.
-
-Gitblit generates the following standard federation tokens:
-%BEGINCODE%
-String allToken = SHA1(passphrase + "-ALL");
-String usersAndRepositoriesToken = SHA1(passphrase + "-USERS_AND_REPOSITORIES");
-String repositoriesToken = SHA1(passphrase + "-REPOSITORIES");
-%ENDCODE%
-    
-The *ALL* token allows another Gitblit instance to pull all your repositories, user accounts, server settings, and referenced push scripts.  
-The *USERS_AND_REPOSITORIES* token allows another Gitblit instance to pull all your repositories and  user accounts.  
-The *REPOSITORIES* token only allows pulling of the repositories.
-
-Individual Gitblit repository configurations such as *description* and *accessRestriction* are always mirrored.
-
-If *federation.passphrase* has a non-empty value, the federation tokens are displayed in the log file and are visible, to administrators, in the web ui.
-
-The three standard tokens grant access to ALL your non-excluded repositories.  However, if you only want to specify different groups of repositories to be federated then you need to define *federation sets*. 
-
-#### Federation Sets
-
-Federation Sets (*federation.sets*) are named groups of repositories.  The Federation Sets are defined in `gitblit.properties` and are available for selection in the repository settings page.  You can assign a repository to one or more sets and then distribute the federation token for the set.  This allows you to grant federation pull access to a subset of your available repositories.  Tokens for federation sets only grant pull access for the member repositories.
-
-### Federation Proposals (Origin Gitblit Instance)
-
-Once you have properly setup your passphrase and can see your federation tokens, you are ready to share them with a pulling Gitblit instance.
- 
-The registration process can be partially automated by sending a *federation proposal* to the pulling Gitblit instance.  
-To send a proposal:
-
-1. Login to your Gitblit instance as an administrator
-2. Select and click the *propose* link for the appropriate token on the *federation* page
-3. Confirm the publicly accessible url of your (origin) Gitblit instance
-4. Enter the url of the pulling Gitblit instance you want to federate with
-5. Optionally enter a message for the administrators
-6. Click *propose*
-
-Not all Gitblit instances accept *federation proposals*, there is a setting which allows Gitblit to outright reject them.  In this case an email or instant message to the administrator of the other Gitblit instance is required.  :)
-
-If your proposal is accepted, the proposal is cached to disk on the pulling Gitblit server and, if properly configured, the administrators of that Gitblit server will receive an email notification of your proposal.
-
-Your proposal includes:
-
-1. the url of your Gitblit instance
-2. the federation token you selected and its type
-3. the list of your *non-excluded* repositories, and their configuration details, that you propose to share
-
-Submitting a proposal does not automatically register your server with the pulling Gitblit instance.  
-Registration is a manual process for an administrator.
-
-### Federation Proposals (Pulling Gitblit Instance)
-
-If your Giblit instance has received a *federation proposal*, you will be alerted to that information the next time you login to Gitblit as an administrator.
-
-You may view the details of a proposal by scrolling down to the bottom of the repositories page and selecting a proposal.  Sample registration settings will be generated for you that you may copy & paste into either your `gitblit.properties` file or your `web.xml` file.
-
-### Excluding Repositories (Origin Gitblit Instance)
-
-You may exclude a repository from being pulled by any federated Gitblit instance by setting its *federation strategy* to EXCLUDE in the repository's settings page.
-
-### Excluding Repositories (Pulling Gitblit Instance)
-
-You may exclude repositories to pull in a federation registration.  You may exclude all or you may exclude based on a simple fuzzy pattern.  Only one wildcard character may be used within each pattern.  Patterns are space-separated within the exclude and include fields. 
-
-    federation.example.exclude = skipit.git
-
-**OR**
-
-    federation.example.exclude = *
-    federation.example.include = somerepo.git someotherrepo.git
-
-**OR**
-
-    federation.example.exclude = *
-    federation.example.include = common/* library/*
-    
-### Tracking Status (Pulling Gitblit Instance)
-
-Below the repositories list on the repositories page you will find a section named *federation registrations*.  This section enumerates the other gitblit servers you have configured to periodically pull.  The *status* of the latest pull will be indicated on the left with a colored circle, similar to the status of an executed unit test or Hudson/Jenkins build.  You can drill into the details of the registration to view the status of the last pull from each repository available from that origin Gitblit instance.  Additionally, you can specify the *federation.N.notifyOnError=true* flag, to be alerted via email of regressive status changes to individual registrations.
-
-### Tracking Status (Origin Gitblit Instance)
-
-Origin Gitblit instances can not directly track the success or failure status of Pulling Gitblit instances.  However, the Pulling Gitblit instance may elect to send a status acknowledgment (*federation.N.sendStatus=true*) to the origin Gitblit server that indicates the per-repository status of the pull operation.  This is the same data that is displayed on the Pulling Gitblit instances ui.
-
-### How does it work? (Origin Gitblit Instances)
-
-A pulling Gitblit instance will periodically contact your Gitblit instance and will provide the token as proof that you have granted it federation access.  Your Gitblit instance will decide, based on the supplied token, if the requested data should be returned to the pulling Gitblit instance.  Gitblit data (user accounts, repository metadata, and server settings) are serialized as [JSON](http://json.org) using [google-gson](http://google-gson.googlecode.com) and returned to the pulling Gitblit instance.  Standard Git clone and pull operations are used to transfer commits.
-
-The federation process executes using an internal administrator account, *$gitblit*.  All the normal authentication and authorization processes are used for federation requests. For example, Git commands are authenticated as *$gitblit / token*.
-
-While the *$gitblit* account has access to all repositories, server settings, and user accounts, it is prohibited from accessing the web ui and it is disabled if *federation.passphrase* is empty.
-
-### How does it work? (Pulling Gitblit Instances)
-
-Federated repositories defined in `gitblit.properties` are checked after Gitblit has been running for 1 minute.  The next registration check is scheduled at the completion of the current registration check based on the registration's specified frequency.
-
-- The shortest frequency allowed is every 5 minutes
-- Decimal frequency values are cast to integers
-- Frequency values may be specified in mins, hours, or days
-- Values that can not be parsed default to 60 minutes
-
-After a repository has been cloned it is flagged as *isFederated* (which identifies it as being sourced from another Gitblit instance), *isFrozen* (which prevents Git pushes to this mirror) and *federationStrategy=EXCLUDED* (which prevents this repository from being pulled by another federated Gitblit instance).
-
-#### Origin Verification
-
-During a federated pull operation, Gitblit does check that the *origin* of the local repository starts with the url of the federation registration.  
-If they do not match, the repository is skipped and this is indicated in the log.
-
-#### User Accounts & Teams
-
-By default all user accounts and teams (except the *admin* account) are automatically pulled when using the *ALL* token or the *USERS_AND_REPOSITORIES* token.  You may exclude a user account from being pulled by a federated Gitblit instance by checking *exclude from federation* in the edit user page.
-
-The pulling Gitblit instance will store a registration-specific `users.conf` file for the pulled user accounts and their repository permissions. This file is stored in the *federation.N.folder* folder.
-
-If you specify *federation.N.mergeAccounts=true*, then the user accounts and team definitions from the origin Gitblit instance will be integrated into the `users.conf` file of your Gitblit instance and allow sign-on of those users.
-
-**NOTE:**  
-Upgrades from older Gitblit versions will not have the *#notfederated* role assigned to the *admin* account.  Without that role, your admin account WILL be transferred with an *ALL* or *USERS_AND_REPOSITORIES* token.  
-Please consider adding the *#notfederated* role to your admin account!
-
-#### Server Settings 
-
-Server settings are only pulled when using the *ALL* token.
-
-The pulling Gitblit instance will store a registration-specific `gitblit.properties` file for all pulled settings.  This file is stored in the *federation.N.folder* folder.
-
-These settings are unused by the pulling Gitblit instance.
-
-#### Push Scripts 
-
-Your Groovy push scripts are only pulled when using the *ALL* token.
-
-The pulling Gitblit instance will retrieve any referenced (i.e. used) push script and store it locally as *registration_scriptName.groovy* in the *federation.N.folder* folder.
-
-These scripts are unused by the pulling Gitblit instance.
-
-### Collisions and Conflict Resolution
-
-Gitblit does **not** detect conflict and it does **not** offer conflict resolution of repositories, users, teams, or settings.
-
-If an object exists locally that has the same name as the remote object, it is assumed they are the same and the contents of the remote object are merged into the local object.  If you can not guarantee that this is the case, then you should not store any federated repositories directly in *git.repositoriesFolder* and you should not enable *mergeAccounts*.
-
-By default, federated repositories can not be pushed to, they are read-only by the *isFrozen* flag.  This flag is **ONLY** enforced by Gitblit's JGit servlet.  If you push to a federated repository after resetting the *isFrozen* flag or via some other Git access technique then you may break Gitblit's ability to continue pulling from the origin repository.  If you are only pushing to a local branch then you might be safe.
-
-## Federation Pull Registration Keys
-
-<table class="table">
-<tr><th>federation.N.url</th>
-<td>string</td>
-<td>the url of the origin Gitblit instance <em>(required)</em></td>
-</tr>
-
-<tr><th>federation.N.token</th>
-<td>string</td>
-<td>the token provided by the origin Gitblit instance <em>(required)</em></td>
-</tr>
-
-<tr><th>federation.N.frequency</th>
-<td>x [mins/hours/days]</td>
-<td>the period to wait between pull executions</td>
-</tr>
-
-<tr><th>federation.N.folder</th>
-<td>string</td>
-<td>the destination folder, relative to <em>git.repositoriesFolder</em>, for these repositories.<br/>default is <em>git.repositoriesFolder</em></td>
-</tr>
-
-<tr><th>federation.N.bare</th>
-<td>boolean</td>
-<td>if <b>true</b> <em>(default)</em>, each repository is cloned as a bare repository (i.e. no working folder).</td>
-</tr>
-
-<tr><th>federation.N.mirror</th>
-<td>boolean</td>
-<td>if <b>true</b> <em>(default)</em>, each repository HEAD is reset to <em>origin/master</em> after each pull.  The repository is flagged <em>isFrozen</em> after the initial clone.<br/><br/>If <b>false</b>, each repository HEAD will point to the FETCH_HEAD of the initial clone from the origin until pushed to or otherwise manipulated.</td>
-</tr>
-
-<tr><th>federation.N.mergeAccounts</th>
-<td>boolean</td>
-<td>if <b>true</b>, merge the retrieved accounts into the <code>users.conf</code> of <b>this</b> Gitblit instance.<br/><em>default is false</em></td>
-</tr>
-
-<tr><th>federation.N.sendStatus</th>
-<td>boolean</td>
-<td>if <b>true</b>, send the status of the federated pull to the origin Gitblit instance.<br/><em>default is false</em></td>
-</tr>
-
-<tr><th>federation.N.include</th>
-<td>string array<br/>(space-delimited)</td>
-<td>list of included repositories <em>(wildcard and fuzzy matching supported)</em></td>
-</tr>
-
-<tr><th>federation.N.exclude</th>
-<td>string array<br/>(space-delimited)</td>
-<td>list of excluded repositories <em>(wildcard and fuzzy matching supported)</em></td>
-</tr>
-
-<tr><th>federation.N.notifyOnError</th>
-<td>boolean</td>
-<td>if <b>true</b>, send an email to the administrators on an error.<br/><em>default is false</em></td>
-</tr>
-</table>
-
-## Example Federation Pull Registrations
-
-These examples would be entered into the `gitblit.properties` file of the pulling gitblit instance.
-
-#### (Nearly) Perfect Mirror Example
-
-![block diagram](fed_mirror.png "Gitblit Mirror")
-
-This assumes that the *token* is the *ALL* token from the origin gitblit instance.
-
-The repositories, example1_users.conf, example1_gitblit.propertiesn and all example1_scripts.groovy will be put in *git.repositoriesFolder* and the origin user accounts will be merged into the local user accounts, including passwords and all roles.  The Gitblit instance will also send a status acknowledgment to the origin Gitblit instance at the end of the pull operation.  The status report will include the state of each repository pull (EXCLUDED, SKIPPED, NOCHANGE, PULLED, MIRRORED).  This way the origin Gitblit instance can monitor the health of its mirrors.
-
-This example is considered *nearly* perfect because while the origin Gitblit's server settings & push scripts are pulled and saved locally, they are not merged with your server settings so its not a true mirror.
-
-    federation.example1.url = https://go.gitblit.com
-    federation.example1.token = 6f3b8a24bf970f17289b234284c94f43eb42f0e4
-    federation.example1.frequency = 120 mins
-    federation.example1.folder =
-    federation.example1.bare = true 
-    federation.example1.mirror = true
-    federation.example1.mergeAccounts = true
-    federation.example1.sendStatus = true
-    
-#### Just Repositories Example
-
-This assumes that the *token* is the *REPOSITORIES* token from the origin gitblit instance.  
-The repositories will be put in *git.repositoriesFolder*/example2.
-
-    federation.example2.url = https://tomcat.gitblit.com/gitblit
-    federation.example2.token = 6f3b8a24bf970f17289b234284c94f43eb42f0e4
-    federation.example2.frequency = 120 mins
-    federation.example2.folder = example2
-    federation.example2.bare = true
-    federation.example2.mirror = true
-    
-#### All-but-One Repository Example
-
-This assumes that the *token* is the *REPOSITORIES* token from the origin gitblit instance.  
-The repositories will be put in *git.repositoriesFolder*/example3.
-
-    federation.example3.url = https://tomcat.gitblit.com/gitblit
-    federation.example3.token = 6f3b8a24bf970f17289b234284c94f43eb42f0e4
-    federation.example3.frequency = 120 mins
-    federation.example3.folder = example3
-    federation.example3.bare = true
-    federation.example3.mirror = true
-    federation.example3.exclude = somerepo.git
-    
-#### Just One Repository Example
-
-This assumes that the *token* is the *REPOSITORIES* token from the origin gitblit instance.  
-The repositories will be put in *git.repositoriesFolder*/example4.
-
-    federation.example4.url = https://tomcat.gitblit.com/gitblit
-    federation.example4.token = 6f3b8a24bf970f17289b234284c94f43eb42f0e4
-    federation.example4.frequency = 120 mins
-    federation.example4.folder = example4
-    federation.example4.bare = true
-    federation.example4.mirror = true
-    federation.example4.exclude = *
-    federation.example4.include = somerepo.git
-    
-## Federation Client
-
-Instead of setting up a full-blown pulling Gitblit instance, you can also use the [federation client](http://code.google.com/p/gitblit/downloads/detail?name=%FEDCLIENT%) command-line utility.  This is a packaged subset of the federation feature in a smaller, simpler command-line only tool.
-
-The *federation client* relies on many of the same dependencies as Gitblit and will download them on first execution.
-
-### federation.properties
-You may use the `federation.properties` file to configure one or more Gitblit instances that you want to pull from.  This file is a subset of the standard `gitblit.properties` file.
-
-By default this tool does not daemonize itself; it executes and then quits.  This allows you to use the native scheduling feature of your OS.  Of course, if you'd rather use Gitblit's scheduler you may use that by specifying the `--daemon` parameter.
-
-### http.sslVerify
-
-If you are pulling from a Gitblit with a self-signed SSL certificate you will need to configure Git/JGit to bypass certificate verification.  
-([Git-Config Manual Page](http://www.kernel.org/pub/software/scm/git/docs/git-config.html))  
-
-<pre>git config --global --bool --add http.sslVerify false</pre>
-
-### Command-Line Parameters
-Instead of using `federation.properties` you may directly specify a Gitblit instance to pull from with command-line parameters.
-
-    java -jar fedclient.jar --url https://go.gitblit.com --mirror --bare --token 123456789
-         --repositoriesFolder c:/mymirror
-    
-    java -jar fedclient.jar --url https://go.gitblit.com --mirror --bare --token 123456789
-         --repositoriesFolder c:/mymirror --daemon --frequency "24 hours"
-    
diff --git a/docs/02_rpc.mkd b/docs/02_rpc.mkd
deleted file mode 100644
index 35528bf..0000000
--- a/docs/02_rpc.mkd
+++ /dev/null
@@ -1,295 +0,0 @@
-## Remote Management, Administration and Integration
-
-*SINCE 0.7.0*
-
-Gitblit optionally allows a remote client to administer the Gitblit server.  This client could be a Java-based tool or perhaps a tool written in another language.
-
-    web.enableRpcServlet=true
-    web.enableRpcManagement=false
-    web.enableRpcAdministration=false
-
-**https** is strongly recommended because passwords are insecurely transmitted form your browser/rpc client using Basic authentication!
-
-The Gitblit JSON RPC mechanism, like the Gitblit JGit servlet, syndication/feed servlet, etc, supports request-based authentication.  Making an *admin* request will trigger Gitblit's basic authentication mechanism.  Listing of repositories, generally, will not trigger this authentication mechanism unless *web.authenticateViewPages=true*.  That means its possible to allow anonymous enumeration of repositories that are not *view restricted* or *clone restricted*.  Of course, if credentials are provided then all private repositories that are available to the user account will be enumerated in the JSON response.
-
-### Gitblit Manager
-
-[Gitblit Manager](http://code.google.com/p/gitblit/downloads/detail?name=%MANAGER%) is an example Java/Swing application that allows remote management (repository and user objects) and administration (server settings) of a Gitblit server.
-  
-This application uses a combination of RSS feeds and the JSON RPC interface, both of which are part of the [Gitblit API](http://code.google.com/p/gitblit/downloads/detail?name=%API%) library, to present live information from a Gitblit server.  Some JSON RPC methods from the utility class `com.gitblit.utils.RpcUtils` are not currently used by the Gitblit Manager.
-
-**NOTE:**  
-Gitblit Manager stores your login credentials **INSECURELY** in homedir/.gitblit/config.
-
-### Eclipse/EGit "Import from Gitblit" Feature (Planning)
-
-One obvious goal of a Gitblit RPC mechanism would be to have an Eclipse/EGit Feature that allows authentication and enumeration of Gitblit repositories from the Eclipse *Import...* menu.  Batch cloning would be supported and delegated to EGit.
-
-This particular project should not be difficult as the only external dependency for `com.gitblit.utils.RpcUtils` is [google-gson](http://google-gson.googlecode.com) which is already a dependency of the EGit/GitHub Mylyn feature.
-
-One proposal from the EGit team is to define a common JSON RPC method for enumeration of repositories which can be implemented by Git hosts.  The EGit team would then implement the UI and the client-side enumeration code.  This idea was raised as part of this [feature request for EGit](https://bugs.eclipse.org/bugs/show_bug.cgi?id=361251).
-
-Currently this project is in the planning stage.
-
-## RSS Query Interface
-
-At present, Gitblit does not yet support retrieving Git objects (commits, etc) via the JSON RPC mechanism.  However, the repository/branch RSS feeds can be used to extract log/history information from a repository branch.
-
-The Gitblit API includes methods for retrieving and interpreting RSS feeds.  The Gitblit Manager uses these methods to allow branch activity monitoring and repository searching.
-
-<table class="table">
-<tr><th>url parameter</th><th>default</th><th>description</th></tr>
-<tr><td colspan='3'><b>standard query</b></td></tr>
-<tr><td><em>repository</em></td><td><em>required</em></td><td>repository name is part of the url (see examples below)</td></tr>
-<tr><td>h=</td><td><em>optional</em><br/>default: HEAD</td><td>starting branch, ref, or commit id</td></tr>
-<tr><td>l=</td><td><em>optional</em><br/>default: web.syndicationEntries</td><td>maximum return count</td></tr>
-<tr><td>pg=</td><td><em>optional</em><br/>default: 0</td><td>page number for paging<br/>(offset into history = pagenumber*maximum return count)</td></tr>
-<tr><td colspan='3'><b>search query</b></td></tr>
-<tr><td>s=</td><td><em>required</em></td><td>search string</td></tr>
-<tr><td>st=</td><td><em>optional</em><br/>default: COMMIT</td><td>search type</td></tr>
-</table>
-
-### Example RSS Queries
-
-    https://localhost:8443/feed/gitblit.git?l=50&h=refs/heads/master
-    https://localhost:8443/feed/gitblit.git?l=50&h=refs/heads/master&s=documentation
-    https://localhost:8443/feed/gitblit.git?l=50&h=refs/heads/master&s=james&st=author&pg=2
-
-## JSON Remote Procedure Call (RPC) Interface
-
-### RPC Protocol Versions
-<table class="table">
-<tbody>
-<tr><th>Release</th><th>Protocol Version</th></tr>
-<tr><td>Gitblit v0.7.0</td><td>1 (inferred version)</td></tr>
-<tr><td>Gitblit v0.8.0</td><td>2</td></tr>
-<tr><td>Gitblit v0.9.0 - v1.0.0</td><td>3</td></tr>
-<tr><td>Gitblit v1.1.0</td><td>4</td></tr>
-<tr><td>Gitblit v1.2.0+</td><td>5</td></tr>
-</tbody>
-</table>
-
-#### Protocol Version 5
-
-- *SET_REPOSITORY_MEMBERS* will reject all calls because this would elevate all discrete permissions to RW+  
-Use *SET_REPOSITORY_MEMBER_PERMISSIONS* instead.
-- *SET_REPOSITORY_TEAMS* will reject all calls because this would elevate all discrete permissions to RW+  
-Use *SET_REPOSITORY_TEAM_PERMISSIONS* instead.
-
-### RPC Request and Response Types
-<table class="table">
-<tr><th colspan='2'>url parameters</th><th rowspan='2'>required<br/>user<br/>permission</th><th rowspan='2'>protocol<br/>version</th><th colspan='2'>json</th></tr>
-<tr><th>req=</th><th>name=</th><th>post body</th><th>response body</th></tr>
-<tr><td colspan='6'><em>web.enableRpcServlet=true</em></td></tr>
-<tr><td>GET_PROTOCOL</td><td>-</td><td>-</td><td>2</td><td>-</td><td>Integer</td></tr>
-<tr><td>LIST_REPOSITORIES</td><td>-</td><td>-</td><td>1</td><td>-</td><td>Map&lt;String, RepositoryModel&gt;</td></tr>
-<tr><td>LIST_BRANCHES</td><td>-</td><td>-</td><td>1</td><td>-</td><td>Map&lt;String, List&lt;String&gt;&gt;</td></tr>
-<tr><td>LIST_SETTINGS</td><td>-</td><td><em>-</em></td><td>1</td><td>-</td><td>ServerSettings (basic keys)</td></tr>
-<tr><td colspan='6'><em>web.enableRpcManagement=true</em></td></tr>
-<tr><td>CREATE_REPOSITORY</td><td>repository name</td><td><em>admin</em></td><td>1</td><td>RepositoryModel</td><td>-</td></tr>
-<tr><td>EDIT_REPOSITORY</td><td>repository name</td><td><em>admin</em></td><td>1</td><td>RepositoryModel</td><td>-</td></tr>
-<tr><td>DELETE_REPOSITORY</td><td>repository name</td><td><em>admin</em></td><td>1</td><td>-</td><td>-</td></tr>
-<tr><td>LIST_USERS</td><td>-</td><td><em>admin</em></td><td>1</td><td>-</td><td>List&lt;UserModel&gt;</td></tr>
-<tr><td>CREATE_USER</td><td>user name</td><td><em>admin</em></td><td>1</td><td>UserModel</td><td>-</td></tr>
-<tr><td>EDIT_USER</td><td>user name</td><td><em>admin</em></td><td>1</td><td>UserModel</td><td>-</td></tr>
-<tr><td>DELETE_USER</td><td>user name</td><td><em>admin</em></td><td>1</td><td>-</td><td>-</td></tr>
-<tr><td>LIST_TEAMS</td><td>-</td><td><em>admin</em></td><td>2</td><td>-</td><td>List&lt;TeamModel&gt;</td></tr>
-<tr><td>CREATE_TEAM</td><td>team name</td><td><em>admin</em></td><td>2</td><td>TeamModel</td><td>-</td></tr>
-<tr><td>EDIT_TEAM</td><td>team name</td><td><em>admin</em></td><td>2</td><td>TeamModel</td><td>-</td></tr>
-<tr><td>DELETE_TEAM</td><td>team name</td><td><em>admin</em></td><td>2</td><td>-</td><td>-</td></tr>
-<tr><td>LIST_REPOSITORY_MEMBERS</td><td>repository name</td><td><em>admin</em></td><td>1</td><td>-</td><td>List&lt;String&gt;</td></tr>
-<tr><td><s>SET_REPOSITORY_MEMBERS</s></td><td><s>repository name</s></td><td><em><s>admin</s></em></td><td><s>1</s></td><td><s>List&lt;String&gt;</s></td><td>-</td></tr>
-<tr><td>LIST_REPOSITORY_MEMBER_PERMISSIONS</td><td>repository name</td><td><em>admin</em></td><td>5</td><td>-</td><td>List&lt;String&gt;</td></tr>
-<tr><td>SET_REPOSITORY_MEMBER_PERMISSIONS</td><td>repository name</td><td><em>admin</em></td><td>5</td><td>List&lt;String&gt;</td><td>-</td></tr>
-<tr><td>LIST_REPOSITORY_TEAMS</td><td>repository name</td><td><em>admin</em></td><td>2</td><td>-</td><td>List&lt;String&gt;</td></tr>
-<tr><td><s>SET_REPOSITORY_TEAMS</s></td><td><s>repository name</s></td><td><em><s>admin</s></em></td><td><s>2</s></td><td><s>List&lt;String&gt;</s></td><td>-</td></tr>
-<tr><td>LIST_REPOSITORY_TEAM_PERMISSIONS</td><td>repository name</td><td><em>admin</em></td><td>5</td><td>-</td><td>List&lt;String&gt;</td></tr>
-<tr><td>SET_REPOSITORY_TEAM_PERMISSIONS</td><td>repository name</td><td><em>admin</em></td><td>5</td><td>List&lt;String&gt;</td><td>-</td></tr>
-<tr><td>LIST_SETTINGS</td><td>-</td><td><em>admin</em></td><td>1</td><td>-</td><td>ServerSettings (management keys)</td></tr>
-<tr><td>CLEAR_REPOSITORY_CACHE</td><td>-</td><td><em>-</em></td><td>4</td><td>-</td><td>-</td></tr>
-<tr><td colspan='6'><em>web.enableRpcAdministration=true</em></td></tr>
-<tr><td>LIST_FEDERATION_REGISTRATIONS</td><td>-</td><td><em>admin</em></td><td>1</td><td>-</td><td>List&lt;FederationModel&gt;</td></tr>
-<tr><td>LIST_FEDERATION_RESULTS</td><td>-</td><td><em>admin</em></td><td>1</td><td>-</td><td>List&lt;FederationModel&gt;</td></tr>
-<tr><td>LIST_FEDERATION_PROPOSALS</td><td>-</td><td><em>admin</em></td><td>1</td><td>-</td><td>List&lt;FederationProposal&gt;</td></tr>
-<tr><td>LIST_FEDERATION_SETS</td><td>-</td><td><em>admin</em></td><td>1</td><td>-</td><td>List&lt;FederationSet&gt;</td></tr>
-<tr><td>LIST_SETTINGS</td><td>-</td><td><em>admin</em></td><td>1</td><td>-</td><td>ServerSettings (all keys)</td></tr>
-<tr><td>EDIT_SETTINGS</td><td>-</td><td><em>admin</em></td><td>1</td><td>Map&lt;String, String&gt;</td><td>-</td></tr>
-<tr><td>LIST_STATUS</td><td>-</td><td><em>admin</em></td><td>1</td><td>-</td><td>ServerStatus (see example below)</td></tr>
-</table>
-
-### RPC/HTTP Response Codes
-<table class="table">
-<tr><th>code</th><th>name</th><th>description</th></tr>
-<tr><td>200</td><td>success</td><td>Gitblit processed the request successfully</td></tr>
-<tr><td>401</td><td>unauthorized</td><td>Gitblit requires user credentials to process the request</td></tr>
-<tr><td>403</td><td>forbidden</td><td>Gitblit can not process the request for the supplied credentials</td></tr>
-<tr><td>405</td><td>method not allowed</td><td>Gitblit has disallowed the processing the specified request</td></tr>
-<tr><td>500</td><td>server error</td><td>Gitblit failed to process the request likely because the input object created a conflict</td></tr>
-<tr><td>501</td><td>unknown request</td><td>Gitblit does not recognize the RPC request type</td></tr>
-</table>
-
-### Example: LIST_REPOSITORIES
-
-**url**: https://localhost/rpc?req=LIST_REPOSITORIES  
-**response body**: Map&lt;String, RepositoryModel&gt; where the map key is the clone url of the repository
-<pre>
-{
-  "https://localhost/git/libraries/xmlapache.git": {
-    "name": "libraries/xmlapache.git",
-    "description": "apache xmlrpc client and server",
-    "owner": "admin",
-    "lastChange": "2010-01-28T22:12:06Z",
-    "hasCommits": true,
-    "showRemoteBranches": false,
-    "useTickets": false,
-    "useDocs": false,
-    "accessRestriction": "VIEW",
-    "isFrozen": false,
-    "showReadme": false,
-    "federationStrategy": "FEDERATE_THIS",
-    "federationSets": [
-      "libraries"
-    ],
-    "isFederated": false,
-    "skipSizeCalculation": false,
-    "skipSummaryMetrics": false,
-    "size": "102 KB"
-  },
-  "https://localhost/git/libraries/smack.git": {
-    "name": "libraries/smack.git",
-    "description": "smack xmpp client",
-    "owner": "admin",
-    "lastChange": "2009-01-28T18:38:14Z",
-    "hasCommits": true,
-    "showRemoteBranches": false,
-    "useTickets": false,
-    "useDocs": false,
-    "accessRestriction": "VIEW",
-    "isFrozen": false,
-    "showReadme": false,
-    "federationStrategy": "FEDERATE_THIS",
-    "federationSets": [],
-    "isFederated": false,
-    "skipSizeCalculation": false,
-    "skipSummaryMetrics": false,
-    "size": "4.8 MB"
-  }
-}
-</pre>
-
-### Example: EDIT_REPOSITORY (rename)
-
-The original repository name is specified in the *name* url parameter.  The new name is set within the JSON object.
-
-**url**: https://localhost/rpc?req=EDIT_REPOSITORY&name=libraries/xmlapache.git  
-**post body**: RepositoryModel
-<pre>
-{
-    "name": "libraries/xmlapache-renamed.git",
-    "description": "apache xmlrpc client and server",
-    "owner": "admin",
-    "lastChange": "2010-01-28T22:12:06Z",
-    "hasCommits": true,
-    "showRemoteBranches": false,
-    "useTickets": false,
-    "useDocs": false,
-    "accessRestriction": "VIEW",
-    "isFrozen": false,
-    "showReadme": false,
-    "federationStrategy": "FEDERATE_THIS",
-    "federationSets": [
-      "libraries"
-    ],
-    "isFederated": false,
-    "skipSizeCalculation": false,
-    "skipSummaryMetrics": false,
-    "size": "102 KB"
-}
-</pre>
-
-### Example: LIST_USERS
-**url**: https://localhost/rpc?req=LIST_USERS  
-**response body**: List&lt;UserModel&gt;
-<pre>
-[
-  {
-    "username": "admin",
-    "password": "admin",
-    "canAdmin": true,
-    "excludeFromFederation": true,
-    "repositories": []
-  },
-  {
-    "username": "test",
-    "password": "test",
-    "canAdmin": false,
-    "excludeFromFederation": false,
-    "repositories": [
-      "libraries/xmlapache.git",
-      "libraries/smack.git"
-    ]
-  }
-]
-</pre>
-
-### Example: LIST_SETTINGS
-**url**: https://localhost/rpc?req=LIST_SETTINGS  
-**response body**: ServerSettings
-<pre>
-{
-  "settings": {
-      "web.siteName": {
-        "name": "web.siteName",
-        "currentValue": "",
-        "defaultValue": "",
-        "description": "Gitblit Web Settings\nIf blank Gitblit is displayed.",
-        "since": "0.5.0",
-        "caseSensitive": false,
-        "restartRequired": false,
-        "spaceDelimited": false
-      },
-      "web.summaryCommitCount": {
-        "name": "web.summaryCommitCount",
-        "currentValue": "16",
-        "defaultValue": "16",
-        "description": "The number of commits to display on the summary page\nValue must exceed 0 else default of 16 is used",
-        "since": "0.5.0",
-        "caseSensitive": false,
-        "restartRequired": false,
-        "spaceDelimited": false
-      }
-  }
-}
-</pre>
-
-### Example: LIST_STATUS
-**url**: https://localhost/rpc?req=LIST_STATUS  
-**response body**: ServerStatus
-<pre>
-{
-  "bootDate": "2011-10-22T12:13:00Z",
-  "version": "0.7.0-SNAPSHOT",
-  "releaseDate": "PENDING",
-  "isGO": true,
-  "systemProperties": {
-    "file.encoding": "Cp1252",
-    "java.home": "C:\\Program Files\\Java\\jdk1.6.0_26\\jre",
-    "java.io.tmpdir": "C:\\Users\\JAMESM~1\\AppData\\Local\\Temp\\",
-    "java.runtime.name": "Java(TM) SE Runtime Environment",
-    "java.runtime.version": "1.6.0_26-b03",
-    "java.vendor": "Sun Microsystems Inc.",
-    "java.version": "1.6.0_26",
-    "java.vm.info": "mixed mode",
-    "java.vm.name": "Java HotSpot(TM) 64-Bit Server VM",
-    "java.vm.vendor": "Sun Microsystems Inc.",
-    "java.vm.version": "20.1-b02",
-    "os.arch": "amd64",
-    "os.name": "Windows 7",
-    "os.version": "6.1"
-  },
-  "heapAllocated": 128057344,
-  "heapFree": 120399168,
-  "heapSize": 1899560960,
-  "servletContainer": "jetty/7.4.3.v20110701"
-}
-</pre>
\ No newline at end of file
diff --git a/docs/03_faq.mkd b/docs/03_faq.mkd
deleted file mode 100644
index cdf3d59..0000000
--- a/docs/03_faq.mkd
+++ /dev/null
@@ -1,172 +0,0 @@
-## Troubleshooting
-
-### Eclipse/Egit/JGit complains that it "can't open upload pack"?
-There are a few ways this can occur:
-
-1. You are using https with a self-signed certificate and you **did not** configure *http.sslVerify=false*
-    1. Window->Preferences->Team->Git->Configuration
-    2. Click the *New Entry* button
-    3. <pre>Key = <em>http.sslVerify</em>
-Value = <em>false</em></pre>
-2. Gitblit GO's default self-signed certificate is bound to *localhost* and you are trying to clone/push between machines.
-    1. Review the contents of `makekeystore.cmd`
-    2. Set *your hostname* in the *HOSTNAME* variable.
-    3. Execute the script.<br/>This will generate a new certificate and keystore for *your hostname* protected by *server.storePassword*. 
-3. The repository is clone-restricted and you don't have access.
-4. The repository is clone-restricted and your password changed.
-5. A regression in Gitblit.  :(
-
-### Why can't I access Gitblit GO from another machine?
-1. Please check *server.httpBindInterface* and *server.httpsBindInterface* in `gitblit.properties`, you may be only be serving on *localhost*.
-2. Please see the above answer about "**can't open upload pack**".
-3. Ensure that any firewall you may have running on the Gitblit server either has an exception for your specified ports or for the running process.
-
-### How do I run Gitblit GO on port 80 or 443 in Linux?
-Linux requires root permissions to serve on ports < 1024.<br/>
-Run the server as *root* (security concern) or change the ports you are serving to 8080 (http) and/or 8443 (https). 
-
-### Gitblit GO does not list my repositories?!
-1. Confirm that the value *git.repositoriesFolder* in `gitblit.properties` actually points to your repositories folder.
-2. Confirm that the Gitblit GO process has full read-write-execute permissions to your *git.repositoriesFolder*. 
-
-### Gitblit WAR does not list my repositories?!
-1. Confirm that the &lt;context-param&gt; *git.repositoriesFolder* value in your `web.xml` file actually points to your repositories folder.
-2. Confirm that the servlet container process has full read-write-execute permissions to your *git.repositoriesFolder*.
-
-### Gitblit WAR will not authenticate any users?!
-Confirm that the &lt;context-param&gt; *realm.userService* value in your `web.xml` file actually points to a `users.conf` or `users.properties` file.
-
-### Gitblit won't open my grouped repository (/group/myrepo.git) or browse my log/branch/tag/ref?!
-This is likely an url encoding/decoding problem with forward slashes:
-
-**bad**
-
-    http://192.168.1.2/log/myrepo.git/refs/heads/master
-
-**good**
-
-    http://192.168.1.2/log/myrepo.git/refs%2Fheads%2Fmaster
-
-**NOTE:**  
-You can not trust the url in the address bar of your browser since your browser may decode it for presentation.  When in doubt, *View Source* of the generated html to confirm the *href*.
-
-There are two possible workarounds for this issue.  In `gitblit.properties` or `web.xml`:
-
-1. try setting *web.mountParameters* to *false*.<br/>This changes the url scheme from mounted (*/commit/myrepo.git/abcdef*) to parameterized (*/commit/?r=myrepo.git&h=abcdef*).
-2. try changing *web.forwardSlashCharacter* to an asterisk or a **!**
-
-### Running Gitblit behind mod_proxy or some other proxy layer
-
-You must ensure that the proxy does not decode and then re-encode request urls with interpretation of forward-slashes (*%2F*).  If your proxy layer does re-encode embedded forward-slashes then you may not be able to browse grouped repositories or logs, branches, and tags **unless** you set *web.mountParameters=false*.
-
-If you are using Apache mod_proxy you may have luck with specifying [AllowEncodedSlashes NoDecode](http://httpd.apache.org/docs/2.2/mod/core.html#allowencodedslashes).
-
-### Running Gitblit on Tomcat
-
-Tomcat takes the extra precaution of [disallowing embedded slashes by default](http://tomcat.apache.org/security-6.html#Fixed_in_Apache_Tomcat_6.0.10).  This breaks Gitblit urls.  
-You have a few options on how to handle this scenario:
-
-1. [Tweak Tomcat](http://tomcat.apache.org/security-6.html#Fixed_in_Apache_Tomcat_6.0.10)  
-Add *-Dorg.apache.tomcat.util.buf.UDecoder.ALLOW_ENCODED_SLASH=true* to *CATALINA_OPTS* or to your JVM launch parameters
-2. *web.mountParameters = false* and use non-pretty, parameterized urls
-3. *web.forwardSlashCharacter = !* which tells Gitblit to use **!** instead of **/**
-
-#### UTF-8 Filenames
-
-Tomcat also dislikes urls with non-ASCII characters. If your repositories have non-ASCII filenames you will have to modify your connector properties to allow UTF-8 encoded urls.  
-
-[Tomcat Character Encoding](http://wiki.apache.org/tomcat/FAQ/CharacterEncoding)  
-[Tomcat Connector Properties](http://tomcat.apache.org/tomcat-6.0-doc/config/http.html)
-
-## General Interest Questions
-
-### Gitblit?  What kind of name is that?
-It's a phonetic play on [bitblt][bitblt] which is an image processing operation meaning *bit-block transfer*.
-
-### Why use Gitblit?
-It's a small tool that allows you to easily manage shared repositories and doesn't require alot of setup or git kung-foo.
-
-### Who is the target user for Gitblit?
-Small workgroups that require centralized repositories.
-
-Gitblit is not meant to be a social coding resource like [Github](http://github.com) or [Bitbucket](http://bitbucket.com) with 100s or 1000s of users.  Gitblit is designed to fulfill the same function as your centralized Subversion or CVS server.
-
-### Why does Gitblit exist when there is Git and Gitweb?
-As a Java developer I prefer that as much of my tooling as possible is Java.<br/>
-Originally, I was going to use [Mercurial](http://mercurial.selenic.com) but...
-
-- MercurialEclipse [shells to Python, writes to System.out, and captures System.in](http://mercurial.808500.n3.nabble.com/Hg4J-Mercurial-pure-Java-library-tp2693090p2694555.html)<br/>
-Parsing command-line output is fragile and suboptimal.<br/>Unfortunately this is necessary because Mercurial is an application, not a library.
-- Mercurial HTTP/HTTPS needs to run as CGI through Apache/IIS/etc, as mod_python through Apache, or served with a built-in http server.<br/>
-This requires setup and maintenance of multiple, mixed 3rd party components.
-
-Gitblit eliminates all that complication with its 100% Java stack and simple single configuration file.
-
-Additionally, Git and Gitweb do not offer repository creation or user management.
-
-### Do I need real Git?
-No (mostly).  Gitblit is based on [JGit][jgit] which is a pure Java implementation of the [Git version control system][git].<br/>
-Everything you need for Gitblit (except Java) is either bundled in the distribution file or automatically downloaded on execution.
-
-#### mostly
-JGit does not fully support the git-gc featureset (garbage collection) so you may want native Git to periodically run git-gc until [JGit][jgit] fully supports this feature.
-
-### Can I run Gitblit in conjunction with my existing Git tooling?
-Yes.
-
-### Do I need a JDK or can I use a JRE?
-Gitblit will run just fine with a JRE.  Gitblit can optionally use `keytool` from the JDK to generate self-signed certificates, but normally Gitblit uses [BouncyCastle][bouncycastle] for that need.
-
-### Does Gitblit use a database to store its data?
-No.  Gitblit stores its repository configuration information within the `.git/config` file and its user information in `users.conf`, `users.properties`, or whatever filename is configured in `gitblit.properties`.
-
-### Can I manually edit users.conf, users.properties, gitblit.properties, or .git/config?
-Yes.  You can manually manipulate all of them and (most) changes will be immediately available to Gitblit.<br/>Exceptions to this are noted in `gitblit.properties`.
-
-**NOTE:**  
-Care must be taken to preserve the relationship between user roles and repository names.<br/>Please see the *User Roles* section of the [setup](/setup.html) page for details.
-
-### Can I restrict access to branches or paths within a repository?
-No, not out-of-the-box.  Access restrictions apply to the repository as a whole.
-
-Gitblit's simple authentication and authorization mechanism can be used to facilitate one or more of the [workflows outlined here](http://progit.org/book/ch5-1.html).
-
-Should you require more fine-grained access controls you might consider writing a Groovy *prereceive* script to block updating branch refs based on some permissions file.  I would be interested in a generic, re-usable script to include with Gitblit, should someone want to implement it.
-
-Alternatively, you could use [gitolite](https://github.com/sitaramc/gitolite) and SSH for your repository access.
-
-### Can I authenticate users against XYZ?
-Yes.  The user service is pluggable.  You may write your own complete user service by implementing the *com.gitblit.IUserService* interface.  Or you may subclass *com.gitblit.GitblitUserService* and override just the authentication. Set the fully qualified classname as the *realm.userService* property.
-
-### Why doesn't Gitblit support SSH?
-Gitblit could integrate [Apache Mina][mina] to provide SSH access.  However, doing so violates Gitblit's first design principle: [KISS](http://en.wikipedia.org/wiki/KISS_principle).<br/>
-SSH support requires creating, exchanging, and managing SSH keys (arguably not more complicated than managing users).  While this is possible, JGit's SmartHTTP implementation is a simpler and universal transport mechanism.
-
-You might consider running [Gerrit](http://gerrit.googlecode.org) which does integrate [Apache Mina][mina] and supports SSH or you might consider serving [Git][git] on Linux which would offer real SSH support and also allow use of [many other compelling Git solutions](https://git.wiki.kernel.org/index.php/InterfacesFrontendsAndTools).
-
-### What types of Search does Gitblit support?
-
-As of 0.9.0, Gitblit supports Lucene-based searching.
-
-If Lucene indexing is disabled, Gitblit falls back to brute-force commit-traversal search.  Commit-traversal search supports case-insensitive searching of *commit message* (default), *author*, and *committer*.<br/>
-
-To search by *author* or *committer* use the following syntax in the search box:
-
-    author: james
-    committer: james
-    
-Alternatively, you could enable the search type dropdown list in your `gitblit.properties` file.
-
-### Why did you call the setting federation.N.frequency instead of federation.N.period?!
-
-Yes, yes I know that you are really specifying the period, but Frequency sounds better to me.  :)
-
-### Can Gitblit be translated?
-
-Yes.  Most messages are localized to a standard Java properties file.
-
-[bitblt]: http://en.wikipedia.org/wiki/Bit_blit "Wikipedia Bitblt"
-[jgit]: http://eclipse.org/jgit "Eclipse JGit Site"
-[git]: http://git-scm.com "Official Git Site"
-[mina]: http://mina.apache.org "Apache Mina"
-[bouncycastle]: http://bouncycastle.org "The Legion of the Bouncy Castle"
\ No newline at end of file
diff --git a/docs/04_design.mkd b/docs/04_design.mkd
deleted file mode 100644
index 622e4a2..0000000
--- a/docs/04_design.mkd
+++ /dev/null
@@ -1,84 +0,0 @@
-## Design Principles
-1. [Keep It Simple, Stupid](http://en.wikipedia.org/wiki/KISS_principle)
-2. Offer useful features for serving Git repositories.  If feature is complex, refer to #1.
-3. All dependencies must be retrievable from a publicly accessible [Maven](http://maven.apache.org) repository.<br/>This is to ensure authenticity of dependencies and to automate the setup of developer environments.  
-
-## Architecture
-
-![block diagram](architecture.png "Gitblit Architecture")
-
-### Bundled Dependencies
-The following dependencies are bundled with Gitblit.
-
-- [Bootstrap](http://twitter.github.com/bootstrap) (Apache 2.0)
-- [GLYPHICONS](http://glyphicons.com) (Creative Commons CC-BY)
-- [Clippy](https://github.com/mojombo/clippy) (MIT)
-- [google-code-prettify](http://code.google.com/p/google-code-prettify) (Apache 2.0)
-- [Commons Daemon](http://commons.apache.org/daemon) (Apache 2.0)
-- magnifying glass search icon courtesy of [Gnome](http://gnome.org) (Creative Commons CC-BY)
-- Git logo originally designed by [Jason Long](http://git-scm.com/downloads/logos)
-- modified Git logo originally designed by [Henrik Nyh](http://henrik.nyh.se/2007/06/alternative-git-logo-and-favicon)
-- fork icon courtesy of [Ember.js](http://emberjs.com)
-- other icons courtesy of [FatCow Hosting](http://www.fatcow.com/free-icons) (Creative Commons CC-BY)
-
-### Downloaded Dependencies
-The following dependencies are automatically downloaded by Gitblit GO (or already bundled with the WAR) from the Apache Maven repository and from the Eclipse Maven repository when Gitblit is launched for the first time.
-
-- [JGit][jgit] (EDL 1.0)
-- [Wicket](http://wicket.apache.org) (Apache 2.0)
-- [WicketStuff GoogleCharts](https://github.com/wicketstuff/core/wiki/GoogleCharts) (Apache 2.0)
-- [MarkdownPapers](http://markdown.tautua.org) (Apache 2.0)
-- [Jetty](http://eclipse.org/jetty) (Apache 2.0, EPL 1.0)
-- [SLF4J](http://www.slf4j.org) (MIT/X11)
-- [Log4j](http://logging.apache.org/log4j) (Apache 2.0) 
-- [JCommander](http://jcommander.org) (Apache 2.0)
-- [BouncyCastle](http://www.bouncycastle.org) (MIT/X11)
-- [JSch - Java Secure Channel](http://www.jcraft.com/jsch) (BSD)
-- [Rome](http://rome.dev.java.net) (Apache 1.1)
-- [jdom](http://www.jdom.org) (Apache-style JDOM license)
-- [google-gson](http://code.google.com/google-gson) (Apache 2.0)
-- [javamail](http://kenai.com/projects/javamail) (CDDL-1.0, BSD, GPL-2.0, GNU-Classpath)
-- [Groovy](http://groovy.codehaus.org) (Apache 2.0)
-- [Lucene](http://lucene.apache.org) (Apache 2.0)
-- [UnboundID](http://www.unboundid.com) (LGPL 2.1)
-- [Ivy](http://ant.apache.org/ivy) (Apache 2.0)
-- [JCalendar](http://www.toedter.com/en/jcalendar) (LGPL 2.1)
-- [Commons-Compress](http://commons.apache.org/compress) (Apache 2.0)
-- [XZ for Java](http://tukaani.org/xz/java.html) (Public Domain)
-
-### Other Build Dependencies
-- [Fancybox image viewer](http://fancybox.net) (MIT and GPL dual-licensed)
-- [JUnit](http://junit.org) (Common Public License)
-- [commons-net](http://commons.apache.org/net) (Apache 2.0)
-- [ant-googlecode](http://code.google.com/p/ant-googlecode) (New BSD)
-- [GenJar](http://genjar.sourceforge.net) (Apache 1.1)
-
-## Building from Source
-[Eclipse](http://eclipse.org) is recommended for development as the project settings are preconfigured.
-
-Additionally, [Google CodePro AnalytiX](http://code.google.com/javadevtools), [eclipse-cs](http://eclipse-cs.sourceforge.net), [FindBugs](http://findbugs.sourceforge.net), and [EclEmma](http://www.eclemma.org) are recommended development tools.
-
-1. Clone the git repository from [Github][gitbltsrc].
-2. Import the gitblit project into your Eclipse workspace.  
-*There will be lots of build errors.*
-3. Using Ant, execute the `build.xml` script in the project root.  
-*This will download all necessary build dependencies and will also generate the Keys class for accessing settings.*
-4. Select your gitblit project root and **Refresh** the project, this should correct all build problems.
-5. Using JUnit, execute the `com.gitblit.tests.GitBlitSuite` test suite.  
-*This will clone some repositories from the web and run through the unit tests.*
-5. Review the settings in `gitblit.properties` in your project root.
-    - By default, the *git.repositoriesFolder* points to the repositories cloned by the test suite.  
-    - If running on Linux you may have to change the served port(s) to > 1024 unless you are developing as the root user. 
-6. Execute the *com.gitblit.Launcher* class to start Gitblit.
-
-
-## Contributing
-Patches welcome in any form.
-
-Contributions must be your own original work and must licensed under the [Apache License, Version 2.0][apachelicense], the same license used by Gitblit.
-
-[jgit]: http://eclipse.org/jgit "Eclipse JGit Site"
-[git]: http://git-scm.com "Official Git Site"
-[gitbltsrc]: http://github.com/gitblit "gitblit git repository"
-[googlecode]: http://code.google.com/p/gitblit "gitblit project management"
-[apachelicense]: http://www.apache.org/licenses/LICENSE-2.0 "Apache License, Version 2.0"
\ No newline at end of file
diff --git a/docs/04_releases.mkd b/docs/04_releases.mkd
deleted file mode 100644
index 097dd17..0000000
--- a/docs/04_releases.mkd
+++ /dev/null
@@ -1,547 +0,0 @@
-## Release History
-
-### Current Release
-
-**%VERSION%** ([go](http://code.google.com/p/gitblit/downloads/detail?name=%GO%) | [war](http://code.google.com/p/gitblit/downloads/detail?name=%WAR%) | [express](http://code.google.com/p/gitblit/downloads/detail?name=%EXPRESS%) | [fedclient](http://code.google.com/p/gitblit/downloads/detail?name=%FEDCLIENT%) | [manager](http://code.google.com/p/gitblit/downloads/detail?name=%MANAGER%) | [api](http://code.google.com/p/gitblit/downloads/detail?name=%API%)) based on [%JGIT%][jgit] &nbsp; *released %BUILDDATE%*
-
-#### fixes
-
-- Can't set reset settings with $ or { characters through Gitblit Manager because they are not properly escaped
-
-#### additions
- 
- - FogBugz post-receive hook script (github/djschny)
- - Implemented multiple repository owners (github/akquinet)
- - Chinese translation (github/dapengme, github/yin8086)
-
-### Older Releases
-
-<div class="alert alert-info">
-<h4>Update Note 1.2.1</h4>
-Because there are now several types of files and folders that must be considered Gitblit data, the default location for data has changed.
-<p>You will need to move a few files around when upgrading.  Please see the Upgrading section of the <a href="setup.html">setup</a> page for details.</p>
-
-<b>Express Users</b> make sure to update your web.xml file with the ${baseFolder} values!
-</div>
-
-#### fixes
-
-- Fixed nullpointer on recursively calculating folder sizes when there is a named pipe or symlink in the hierarchy
-- Added nullchecking when concurrently forking a repository and trying to display it's fork network (issue-187)
-- Fixed bug where permission changes were not visible in the web ui to a logged-in user until the user logged-out and then logged back in again (issue-186)
-- Fixed nullpointer on creating a repository with mixed case (issue 185)
-- Include missing model classes in api library (issue-184)
-- Fixed nullpointer when using *web.allowForking = true* && *git.cacheRepositoryList = false* (issue 182)
-- Likely fix for commit and commitdiff page failures when a submodule reference changes (issue 178)
-- Build project models from the repository model cache, when possible, to reduce page load time (issue 172)
-- Fixed loading of Brazilian Portuguese translation from *nix server (github/inaiat)
-
-#### additions
-
-- Fanout PubSub service for self-hosted [Sparkleshare](http://sparkleshare.org) notifications.<br/>
-This service is disabled by default.<br/>
-    **New:** *fanout.bindInterface = localhost*<br/>
-	**New:** *fanout.port = 0*<br/>
-	**New:** *fanout.useNio = true*<br/>
-	**New:** *fanout.connectionLimit = 0*
-- Implemented a simple push log based on a hidden, orphan branch refs/gitblit/pushes (issue 177)<br/>
-The push log is not currently visible in the ui, but the data will be collected and it will be exposed to the ui in the next release.
-- Support for locally and remotely authenticated accounts in LdapUserService and RedmineUserService (issue 183)
-- Added Dutch translation (github/kwoot)
-
-#### changes
-
-- Gitblit GO and Gitblit WAR are now both configured by `gitblit.properties`. WAR is no longer configured by `web.xml`.<br/>
-However, Express for OpenShift continues to be configured by `web.xml`.
-- Support for a *--baseFolder* command-line argument for Gitblit GO and Gitblit Certificate Authority
-- Support for specifying a *${baseFolder}* parameter in `gitblit.properties` and `web.xml` for several settings
-- Improve history display of a submodule link
-- Updated Korean translation (github/ds5apn)
-- Updated checkstyle definition (github/mystygage)
-
-<div class="alert alert-info">
-<h4>Update Note 1.2.0</h4>
-The permissions model has changed in the 1.2.0 release.
-<p>If you are updating your server, you must also update any Gitblit Manager and Federation Client installs to 1.2.0 as well.  The data model used by the RPC mechanism has changed slightly for the new permissions infrastructure.</p>
-</div>
-
-**1.2.0** *released 2012-12-31*
-
-#### fixes
-
-- Fixed regression in *isFrozen* (issue 181)
-- Author metrics can be broken by newlines in email addresses from converted repositories (issue 176)
-- Set subjectAlternativeName on generated SSL cert if CN is an ip address (issue 170)
-- Fixed incorrect links on history page for files not in the current/active commit (issue 166)
-- Empty repository page failed to handle missing repository (issue 160)
-- Fixed broken ticgit urls (issue 157)
-- Exclude submodules from zip downloads (issue 151)
-- Fixed bug where repository ownership was not updated on rename user
-- Fixed bug in create/rename repository if you explicitly specified the alias for the root group (e.g. main/myrepo) (issue 143)
-- Wrapped Markdown parser with improved exception handler (issue 142)
-- Fixed duplicate entries in repository cache (issue 140)
-- Fixed connection leak in LDAPUserService (issue 139)
-- Fixed bug in commit page where changes to a submodule threw a null pointer exception (issue 132)
-- Fixed bug in the diff view for filenames that have non-ASCII characters (issue 128)
-
-#### additions
-
-- Implemented discrete repository permissions (issue 36)
-    - V (view in web ui, RSS feeds, download zip)
-    - R (clone)
-    - RW (clone and push)
-    - RWC (clone and push with ref creation)
-    - RWD (clone and push with ref creation, deletion)
-    - RW+ (clone and push with ref creation, deletion, rewind)
-While not as sophisticated as Gitolite, this does give finer access controls.  These permissions fit in cleanly with the existing users.conf and users.properties files.  In Gitblit <= 1.1.0, all your existing user accounts have RW+ access.   If you are upgrading to 1.2.0, the RW+ access is *preserved* and you will have to lower/adjust accordingly.
-- Implemented *case-insensitive* regex repository permission matching (issue 36)<br/>
-This allows you to specify a permission like `RW:mygroup/.*` to grant push privileges to all repositories within the *mygroup* project/folder.
-- Added DELETE, CREATE, and NON-FAST-FORWARD ref change logging
-- Added support for personal repositories.<br/>
-Personal repositories can be created by accounts with the *create* permission and are stored in *git.repositoriesFolder/~username*.  Each user with personal repositories will have a user page, something like the GitHub profile page.  Personal repositories have all the same features as common repositories, except personal repositories can be renamed by their owner.
-- Added support for server-side forking of a repository to a personal repository (issue 137)<br/>
-In order to fork a repository, the user account must have the *fork* permission **and** the repository must *allow forks*.  The clone inherits the access list of its origin.  i.e. if Team A has clone access to the origin repository, then by default Team A also has clone access to the fork.  This is to facilitate collaboration.  The fork owner may change access to the fork and add/remove users/teams, etc as required <u>however</u> it should be noted that all personal forks will be enumerated in the fork network regardless of access view restrictions.  If you really must have an invisible fork, the clone it locally, create a new repository for your invisible fork, and push it back to Gitblit.<br/>
-    **New:** *web.allowForking=true*
-- Added optional *create-on-push* support<br/>
-    **New:** *git.allowCreateOnPush=true*
-- Added **experimental** JGit-based garbage collection service.  This service is disabled by default.<br/>
-    **New:** *git.allowGarbageCollection=false*<br/>
-    **New:** *git.garbageCollectionHour = 0*<br/>
-    **New:** *git.defaultGarbageCollectionThreshold = 500k*<br/>
-    **New:** *git.defaultGarbageCollectionPeriod = 7 days*
-- Added support for X509 client certificate authentication (github/kevinanderson1).  (issue 106)<br/>
-You can require all git servlet access be authenticated by a client certificate.  You may also specify the OID fingerprint to use for mapping a certificate to a username.  It should be noted that the user account MUST already exist in Gitblit for this authentication mechanism to work; this mechanism can not be used to automatically create user accounts from a certificate.<br/>
-    **New:** *git.requireClientCertificates = false*<br/>
-    **New:** *git.enforceCertificateValidity = true*<br/>
-    **New:** *git.certificateUsernameOIDs = CN*
-- Revised clean install certificate generation to create a Gitblit GO Certificate Authority certificate; an SSL certificate signed by the CA certificate; and to create distinct server key and server trust stores.  <u>The store files have been renamed!</u>
-- Added support for Gitblit GO to require usage of client certificates to access the entire server.<br/>
-This is extreme and should be considered carefully since it affects every https access.  The default is to **want** client certificates.  Setting this value to *true* changes that to **need** client certificates.<br/>
-    **New:** *server.requireClientCertificates = false*
-- Added **Gitblit Certificate Authority**, an x509 PKI management tool for Gitblit GO to encourage use of x509 client certificate authentication.
-- Added setting to control length of shortened commit ids<br/>
-    **New:** *web.shortCommitIdLength=8*
-- Added alternate compressed download formats: tar.gz, tar.xz, tar.bzip2 (issue 174)<br/>
-    **New:** *web.compressedDownloads = zip gz*
-- Added simple project pages.  A project is a subfolder off the *git.repositoriesFolder*.
-- Added support for X-Forwarded-Context for Apache subdomain proxy configurations (issue 135)
-- Delete branch feature (issue 121, Github/ajermakovics)
-- Added line links to blob view (issue 130)
-- Added HTML sendmail hook script and Gitblit.sendHtmlMail method (github/sauthieg)
-- Added RedmineUserService (github/mallowlabs)
-- Support for committer verification.  Requires use of *--no-ff* when merging branches or pull requests.  See setup page for details.
-- Added Brazilian Portuguese translation (github/rafaelcavazin)
-
-#### changes
-
-- Added server setting to specify keystore alias for ssl certificate (issue 98)
-- Added optional global and per-repository activity page commit contribution throttle to help tame *really* active repositories (issue 173)
-- Added support for symlinks in tree page and commit page (issue 171)
-- All access restricted servlets (e.g. DownloadZip, RSS, etc) will try to authenticate using X509 certificates, container principals, cookies, and BASIC headers, in that order.
-- Added *groovy* and *scala* to *web.prettyPrintExtensions*
-- Added short commit id column to log and history tables (issue 168)
-- Teams can now specify the *admin*, *create*, and *fork* roles to simplify user administration
-- Use https Gravatar urls to avoid browser complaints
-- Added frm to default pretty print extensions (issue 156)
-- Expose ReceivePack to Groovy push hooks (issue 125)
-- Redirect to summary page when refreshing the empty repository page on a repository that is not empty (issue 129)
-- Emit a warning in the log file if running on a Tomcat-based servlet container which is unfriendly to %2F forward-slash url encoding AND Gitblit is configured to mount parameters with %2F forward-slash url encoding (Github/jpyeron, issue 126)
-- LDAP admin attribute setting is now consistent with LDAP teams setting and admin teams list.
-If *realm.ldap.maintainTeams==true* **AND** *realm.ldap.admins* is not empty, then User.canAdmin() is controlled by LDAP administrative team membership.  Otherwise, User.canAdmin() is controlled by Gitblit.
-- Support servlet container authentication for existing UserModels (issue 68)
-
-#### dependency changes
-
-- updated to Jetty 7.6.8
-- updated to JGit 2.2.0.201212191850-r
-- updated to Groovy 1.8.8
-- updated to Wicket 1.4.21
-- updated to Lucene 3.6.1
-- updated to BouncyCastle 1.47
-- updated to MarkdownPapers 1.3.2
-- added JCalendar 1.3.2
-- added Commons-Compress 1.4.1
-- added XZ for Java 1.0
-<hr/>
-
-<div class="alert alert-error">
-<h4>Update Note 1.1.0</h4>
-If you are updating from an earlier release AND you have indexed branches with the Lucene indexing feature, you need to be aware that this release will completely re-index your repositories.  Please be sure to provide ample heap resources as appropriate for your installation.
-</div>
-
-**1.1.0** *released 2012-08-25*
-
-#### fixes
-
-- Bypass Wicket's inability to handle direct url addressing of a view-restricted, grouped repository for new, unauthenticated sessions (e.g. click link from email or rss feed without having an active Wicket session)
-- Fixed MailExecutor's failure to cope with mail server connection troubles resulting in 100% CPU usage
-- Fixed generated urls in Groovy *sendmail* hook script for grouped repositories
-- Fixed generated urls in RSS feeds for grouped repositories
-- Fixed nullpointer exception in git servlet security filter (issue 123)
-- Eliminated an unnecessary repository enumeration call on the root page which should result in faster page loads (issue 103)
-- Gitblit could not delete a Lucene index in a working copy on index upgrade
-- Do not index submodule links (issue 119)
-- Restore original user or team object on failure to update (issue 118)
-- Fixes to relative path determination in repository search algorithm for symlinks (issue 116)
-- Fix to GitServlet to allow pushing to symlinked repositories (issue 116)
-- Repository URL now uses `X-Forwarded-Proto` and `X-Forwarded-Port`, if available, for reverse proxy configurations (issue 115)
-- Output real RAW content, not simulated RAW content (issue 114)
-- Fixed Lucene charset encoding bug when reindexing a repository (issue 112)
-- Fixed search box linking to Lucene page for grouped repository on Tomcat (issue 111)
-- Fixed null pointer in LdapUserSerivce if account has a null email address (issue 110)
-- Really fixed failure to update a GO setting from the manager (issue 85)
-
-#### additions
-
-- Identified repository list is now cached by default to reduce disk io and to improve performance (issue 103)<br/>
-    **New:** *git.cacheRepositoryList=true*
-- Preliminary bare repository submodule support<br/>
-    **New:** *git.submoduleUrlPatterns=*
-    - *git.submoduleUrlPatterns* is a space-delimited list of regular expressions for extracting a repository name from a submodule url.<br/>
-    For example, `git.submoduleUrlPatterns = .*?://github.com/(.*)` would extract *gitblit/gitblit.git* from *git://github.git/gitblit/gitblit.git*<br/>
-    **Note:** You may not need this control to work with submodules, but it is there if you do.
-    - If there are no matches from *git.submoduleUrlPatterns* then the repository name is assumed to be whatever comes after the last `/` character *(e.g. gitblit.git)*
-    - Gitblit will try to locate this repository relative to the current repository *(e.g. myfolder/myrepo.git, myfolder/mysubmodule.git)* and then at the root level *(mysubmodule.git)* if that fails.
-    - Submodule references in a working copy will be properly identified as gitlinks, but Gitblit will not traverse into the working copy submodule repository.
-- Added a repository setting to control authorization as AUTHENTICATED or NAMED. (issue 117)<br/>
-NAMED is the original behavior for authorizing against a list of permitted users or permitted teams.
-AUTHENTICATED allows restricted access for any authenticated user.  This is a looser authorization control.
-- Added default authorization control setting (AUTHENTICATED or NAMED)<br/>
-    **New:** *git.defaultAuthorizationControl=NAMED*
-- Added setting to control how deep Gitblit will recurse into *git.repositoriesFolder* looking for repositories (issue 103)<br/>
-    **New:** *git.searchRecursionDepth=-1*
-- Added setting to specify regex exclusions for repositories (issue 103)<br/>
-    **New:** *git.searchExclusions=*
-- Blob page now supports displaying images (issue 6)
-- Non-image binary files can now be downloaded using the RAW link
-- Support StartTLS in LdapUserService (Steffen Gebert, issue 122)
-- Added Korean translation
-
-#### changes
-
-- Line breaks inserted for readability in raw Markdown content display in the event of a parsing/transformation error.  An error message is now displayed prepended to the raw content.
-- Improve UTF-8 reading for Markdown files
-- Updated Polish translation
-- Updated Japanese translation
-- Updated Spanish translation
-
-<hr/>
-
-**1.0.0** *released 2012-07-14*
-
-#### fixes
-
-- Fixed bug in Lucene search where old/stale blobs were never properly deleted during incremental updates.  This resulted in duplicate blob entries in the index.
-- Fixed intermittent bug in identifying line numbers in Lucene search (issue 105)
-- Adjust repository identification algorithm to handle the scenario where a repository name collides with a group/folder name (e.g. foo.git and foo/bar.git) (issue 104)
-- Fixed bug where a repository set as *authenticated push* did not have anonymous clone access (issue 96)
-- Fixed bug in Basic authentication if passwords had a colon (Github/peterloron)
-- Fixed bug where the Gitblit Manager could not update a setting that was not referenced in reference.properties (issue 85)
-
-#### changes
-
-- **Updated Lucene index version which will force a rebuild of ALL your Lucene indexes**<br/>
-Make sure to properly set *web.blobEncodings* before starting Gitblit if you are updating!  (issue 97)
-- Changed default layout for web ui from Fixed-Width layout to Responsive layout (issue 101)
-- IUserService interface has changed to better accomodate custom authentication and/or custom authorization<br/>
-    The default `users.conf` now supports persisting display names and email addresses.
-- Updated Japanese translation (Github/zakki)
-
-#### additions
-
-- Added setting to allow specification of a robots.txt file (issue 99)<br/>
-    **New:** *web.robots.txt =*
-- Added setting to control Responsive layout or Fixed-Width layout (issue 101)<br/>
-    Responsive layout is now the default.  This layout gracefully scales the web ui from a desktop layout to a mobile layout by hiding page components.  It is easy to try, just resize your browser or point your Android/iOS device to the url of your Gitblit install.
-    **New:** *web.useResponsiveLayout = true*
-- Added setting to control charsets for blob string decoding.  Default encodings are UTF-8, ISO-8859-1, and server's default charset. (issue 97)<br/>
-    **New:** *web.blobEncodings = UTF-8 ISO-8859-1*
-- Exposed JGit's internal configuration settings in gitblit.properties/web.xml (issue 93)<br/>
-    Review your `gitblit.properties` or `web.xml` for detailed explanations of these settings.<br/>
-    **New:** *git.packedGitWindowSize = 8k*<br/>
-    **New:** *git.packedGitLimit = 10m*<br/>
-    **New:** *git.deltaBaseCacheLimit = 10m*<br/>
-    **New:** *git.packedGitOpenFiles = 128*<br/>
-    **New:** *git.streamFileThreshold = 50m*<br/>
-    **New:** *git.packedGitMmap = false*
-- Added default access restriction.  Applies to new repositories and repositories that have not been configured with Gitblit. (issue 88)<br/>
-    **New:** *git.defaultAccessRestriction = NONE*
-- Added Ivy 2.2.0 dependency which enables Groovy Grapes, a mechanism to resolve and retrieve library dependencies from a Maven 2 repository within a Groovy push hook script
-- Added setting to control Groovy Grape root folder (location where resolved dependencies are stored)<br/>
-    [Grape](http://groovy.codehaus.org/Grape) allows you to add Maven dependencies to your pre-/post-receive hook script classpath.<br/>
-    **New:** *groovy.grapeFolder = groovy/grape*
-- Added LDAP User Service with many new *realm.ldap* keys (Github/jcrygier)
-- Added support for custom repository properties for Groovy hooks (Github/jcrygier)<br/>
-    Custom repository properties complement hook scripts by providing text field prompts in the web ui and the Gitblit Manager for the defined properties.  This allows your push hooks to be parameterized.
-- Added script to facilitate proxy environment setup on Linux (Github/mragab)
-- Added Polish translation (Lukasz Jader)
-- Added Spanish translation (Eduardo Guervos Narvaez)
-
-#### dependency changes
-
-- updated to Bootstrap 2.0.4
-- updated to JGit 2.0.0.201206130900-r
-- updated to Groovy 1.8.6
-- updated to Gson 1.7.2
-- updated to Log4J 1.2.17
-- updated to SLF4J 1.6.6
-- updated to Apache Commons Daemon 1.0.10
-- added Ivy 2.2.0
-
-<hr/>
-
-**0.9.3** *released 2012-04-11*
-
-#### fixes
-
-- Fixed bug where you could not remove all selections from a RepositoryModel list (permitted users, permitted teams, hook scripts, federation sets, etc) (issue 81)
-- Automatically set *java.awt.headless=true* for Gitblit GO
-
-<hr/>
-
-**0.9.2** *released 2012-04-04*
-
-#### changes
-
-- Added *clientLogger* bound variable to Groovy hook mechanism to allow custom info and error messages to be returned to the client (Github/jcrygier)
-
-#### fixes
-
-- Fixed absolute path/canonical path discrepancy between Gitblit and JGit regarding use of symlinks (issue 78)
-- Fixed row layout on activity page (issue 79)
-- Fixed Centos service script (Github/mohamedmansour)
-- Fixed EditRepositoryPage for IE8; missing save button (issue 80, Github/jonnybbb)
-
-<hr/>
-
-**0.9.1** *released 2012-03-27*
-
-#### fixes
-
-- Lucene folder was stored in working copy instead of in .git folder
-
-<hr/>
-
-**0.9.0** *released 2012-03-27*
-
-#### security
-
-- Fixed session fixation vulnerability where the session identifier was not reset during the login process (issue 62)
-
-#### changes
-
-- Reject pushes to a repository with a working copy (i.e. non-bare repository) (issue-49)
-- Changed default web.datetimestampLongFormat from *EEEE, MMMM d, yyyy h:mm a z* to *EEEE, MMMM d, yyyy HH:mm Z* (issue 50)
-- Expanded commit age coloring from 2 days to 30 days (issue 57)
-
-#### additions
-
-- Added optional Lucene branch indexing (issue 16)<br/>
-    **New:** *web.allowLuceneIndexing = true*<br/>
-    **New:** *web.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*
-Repository branches may be optionally indexed by Lucene for improved searching.  To use this feature you must specify which branches to index within the *Edit Repository* page; _no repositories are automatically indexed_.  Gitblit will build or incrementally update enrolled repositories on a 2 minute cycle. (i.e you will have to wait 2-3 minutes after respecifying indexed branches or pushing new commits before Gitblit will build/update the repository's Lucene index.)
-If a repository has Lucene-indexed branches the *search* form on the repository pages will redirect to the root-level Lucene search page and only the content of those branches can be searched.<br/>
-If the repository does not specify any indexed branches then repository commit-traversal search is used.
-**Note:** Initial indexing of an existing repository can be memory-exhaustive. Be sure to provide your Gitblit server adequate heap space to index your repositories (e.g. -Xmx1024M).<br/>
-See the [setup](setup.html) page for additional details.
-- Allow specifying timezone to use for Gitblit which is independent of both the JVM and the system timezone (issue 54)<br/>
-    **New:** *web.timezone =*
-- Added a built-in AJP connector for integrating Gitblit GO into an Apache mod_proxy setup (issue 59)<br/>
-    **New:** *server.ajpPort = 0*<br/>
-    **New:** *server.ajpBindInterface = localhost*
-- On the Repositories page show a bang *!* character in the color swatch of a repository with a working copy (issue 49)<br/>
-Push requests to these repositories will be rejected.
-- On all non-bare Repository pages show *WORKING COPY* in the upper right corner (issue 49)
-- New setting to prevent display/serving non-bare repositories<br/>
-    **New:** *git.onlyAccessBareRepositories = false*
-- Added *protect-refs.groovy* (Github/plm)
-- Allow setting default branch (relinking HEAD) to a branch or a tag (Github/plm)
-- Added Ubuntu service init script (issue 72)
-- Added partial Japanese translation (Github/zakki)
-
-#### fixes
-
-- Ensure that Welcome message is parsed using UTF-8 encoding (issue 74)
-- Activity page chart layout broken by Google (issue 73)
-- Uppercase repositories not selectable in edit palettes (issue 71)
-- Not all git notes were properly displayed on the commit page (issue 70)
-- Activity page now displays all local branches (issue 65)
-- Fixed (harmless) nullpointer on pushing to an empty repository (issue 69)
-- Fixed possible nullpointer from the servlet container on startup (issue 67)
-- Fixed UTF-8 encoding bug on diff page (issue 66)
-- Fixed timezone bugs on the activity page (issue 54)
-- Prevent add/edit team with no selected repositories (issue 56)
-- Disallow browser autocomplete on add/edit user/team/repository pages
-- Fixed username case-sensitivity issues (issue 43)
-- Disregard searching a subfolder if Gitblit does not have filesystem permissions (Github/lemval issue 51)
-
-#### dependency changes
-
-- updated to Bootstrap 2.0.2
-- added GLYPHICONS (as bundled with Bootstrap 2.0.2)
-- updated to MarkdownPapers 1.2.7
-- updated to JGit 1.3.0.201202151440-r
-- updated to Wicket 1.4.20
-
-<hr/>
-
-**0.8.2** ([go](http://code.google.com/p/gitblit/downloads/detail?name=gitblit-0.8.2.zip) | [war](http://code.google.com/p/gitblit/downloads/detail?name=gitblit-0.8.2.war) | [express](http://code.google.com/p/gitblit/downloads/detail?name=express-0.8.2.zip) | [fedclient](http://code.google.com/p/gitblit/downloads/detail?name=fedclient-0.8.2.zip) | [manager](http://code.google.com/p/gitblit/downloads/detail?name=manager-0.8.2.zip) | [api](http://code.google.com/p/gitblit/downloads/detail?name=gbapi-0.8.2.zip)) based on [JGit 1.2.0 (201112221803-r)][jgit] &nbsp; *released 2012-01-13*
-
-#### fixes
-
-- Fixed bug when upgrading from users.properties to users.conf (issue 41)
-
-<hr/>
-
-**0.8.1** &nbsp; *released 2012-01-11*
-
-#### fixes
-
-- Include missing icon resource for the manager (issue 40)
-- Fixed sendmail.groovy message content with incorrect tag/branch labels
-
-<hr/>
-
-**0.8.0** &nbsp; *released 2012-01-11*
-
-#### additions
-
-- Platform-independent, Groovy push hook script mechanism.<br/>
-Hook scripts can be set per-repository, per-team, or globally for all repositories.<br/>
-    **New:** *groovy.scriptsFolder = groovy*<br/>
-    **New:** *groovy.preReceiveScripts =*<br/>
-    **New:** *groovy.postReceiveScripts =*
-- *sendmail.groovy* for optional email notifications on push.<br/>
-You must properly configure your SMTP server settings in `gitblit.properties` or `web.xml` to use *sendmail.groovy*.
-- New global key for mailing lists.  This is used in conjunction with the *sendmail.groovy* hook script.  All repositories that use the *sendmail.groovy* script will include these addresses in the notification process.  Please see the Setup page for more details about configuring sendmail.<br/>
-    **New:** *mail.mailingLists =*
-- *com.gitblit.GitblitUserService*.  This is a wrapper object for the built-in user service implementations.  For those wanting to only implement custom authentication it is recommended to subclass GitblitUserService and override the appropriate methods.  Going forward, this will help insulate custom authentication from new IUserService API and/or changes in model classes.
-- New default user service implementation: *com.gitblit.ConfigUserService* (`users.conf`)<br/>
-This user service implementation allows for serialization and deserialization of more sophisticated Gitblit User objects without requiring the encoding trickery now present in FileUserService (users.properties).  This will open the door for more advanced Gitblit features.
-For those upgrading from an earlier Gitblit version, a `users.conf` file will automatically be created for you from your existing `users.properties` file on your first launch of Gitblit <u>however</u> you will have to manually set *realm.userService=users.conf* to switch to the new user service.<br/>
-The original `users.properties` file and it's corresponding implementation are **deprecated**.<br/>
-    **New:** *realm.userService = users.conf*
-- Teams for specifying user-repository access in bulk.  Teams may also specify mailing lists addresses and pre- & post- receive hook scripts.
-- Gravatar integration<br/>
-    **New:** *web.allowGravatar = true*
-- Activity page for aggregated repository activity.  This is a timeline of commit activity over the last N days for one or more repositories.<br/>
-   **New:** *web.activityDuration = 14*<br/>
-   **New:** *web.timeFormat = HH:mm*<br/>
-   **New:** *web.datestampLongFormat = EEEE, MMMM d, yyyy*
-- *Filters* menu for the Repositories page and Activity page.  You can filter by federation set, team, and simple custom regular expressions.  Custom expressions can be stored in `gitblit.properties` or `web.xml` or directly defined in your url (issue 27)<br/>
-   **New:** *web.customFilters=*
-- Flash-based 1-step *copy to clipboard* of the primary repository url based on Clippy<br/>
-   **New:** *web.allowFlashCopyToClipboard = true*
-- JavaScript-based 3-step (click, ctrl+c, enter) *copy to clipboard* of the primary repository url in the event that you do not want to use Flash on your installation
-- Empty repositories now link to an *empty repository* page which gives some direction to the user for the next step in using Gitblit.  This page displays the primary push/clone url of the repository and gives sample syntax for the git command-line client. (issue 31)
-- Repositories with a *gh-pages* branch will now have a *pages* link which will serve the content of this branch.  All resource requests are against the repository, Gitblit does not checkout/export this branch to a temporary filesystem.  Jekyll templating is not supported.
-- Gitblit Express bundle to get started running Gitblit on RedHat's OpenShift cloud <span class="label label-warning">BETA</span>
-
-#### changes
-
-- Dropped display of trailing .git from repository names
-- Gitblit GO is now monolithic like the WAR build. (issue 30)<br/>
-This change helps adoption of GO in environments without an internet connection or with a restricted connection.
-- Unit testing framework has been migrated to JUnit4 syntax and the test suite has been redesigned to run all unit tests, including rpc, federation, and git push/clone tests
-
-#### fixes
-
-- Several a bugs in FileUserService related to cleaning up old repository permissions on a rename or delete
-- Renaming a repository into a new subfolder failed (issue 33)
-
-#### dependency changes
-
-- updated to JGit 1.2.0
-- added Groovy 1.8.5
-- added Clippy (bundled)
-
-<hr/>
-
-**0.7.0** &nbsp; *released 2011-11-11*
-
-- **security**: fixed security hole when cloning clone-restricted repository with TortoiseGit (issue 28)
-- improved: updated ui with Twitter's Bootstrap CSS toolkit<br/>
-    **New:** *web.loginMessage = gitblit*
-- improved: repositories list performance by caching repository sizes (issue 27)
-- improved: summary page performance by caching metric calculations (issue 25)
-- added: authenticated JSON RPC mechanism<br/>
-    **New:** *web.enableRpcServlet = true*<br/>
-    **New:** *web.enableRpcManagement = false*<br/>
-    **New:** *web.enableRpcAdministration = false*
-- added: Gitblit API RSS/JSON RPC library
-- added: Gitblit Manager (Java/Swing Application) for remote administration of a Gitblit server.
-- added: per-repository setting to skip size calculation (faster repositories page loading)
-- added: per-repository setting to skip summary metrics calculation (faster summary page loading)
-- added: IUserService.setup(IStoredSettings) for custom user service implementations
-- added: setting to control Gitblit GO context path for proxy setups *(Github/trygvis)*<br/>
-    **New:** *server.contextPath = /*
-- added: *combined-md5* password storage option which stores the hash of username+password as the password *(Github/alyandon)*
-- added: repository owners are automatically granted access for git, feeds, and zip downloads without explicitly selecting them *(Github/dadalar)*
-- added: RSS feeds now include regex substitutions on commit messages for bug trackers, etc
-- fixed: federation protocol timestamps.  dates are now serialized to the [iso8601](http://en.wikipedia.org/wiki/ISO_8601) standard.<br/>
-    **This breaks 0.6.0 federation clients/servers.**
-- fixed: collision on rename for repositories and users
-- fixed: Gitblit can now browse the Linux kernel repository (issue 25)
-- fixed: Gitblit now runs on Servlet 3.0 webservers (e.g. Tomcat 7, Jetty 8) (issue 23)
-- fixed: Set the RSS content type of syndication feeds for Firefox 4 (issue 22)
-- fixed: RSS feeds are now properly encoded to UTF-8
-- fixed: RSS feeds now properly generate parameterized links if *web.mountParameters=false*
-- fixed: Null pointer exception if did not set federation strategy (issue 20)
-- fixed: Gitblit GO allows SSL renegotiation if running on Java 1.6.0_22 or later
-- updated: MarkdownPapers 1.2.5
-- updated: Wicket 1.4.19
-
-<hr/>
-
-**0.6.0** &nbsp; *released 2011-09-27*
-
-- added: federation feature to allow gitblit instances (or gitblit federation clients) to pull repositories and, optionally, settings and accounts from other gitblit instances.  This is something like [svn-sync](http://svnbook.red-bean.com/en/1.5/svn.ref.svnsync.html) for gitblit.<br/>
-    **New:** *federation.name =*<br/>
-    **New:** *federation.passphrase =*<br/>
-    **New:** *federation.allowProposals = false*<br/>
-    **New:** *federation.proposalsFolder = proposals*<br/>
-    **New:** *federation.defaultFrequency = 60 mins*<br/>
-    **New:** *federation.sets =*<br/>
-    **New:** *mail.* settings for sending emails<br/>
-    **New:** user role *#notfederated* to prevent a user account from being pulled by a federated Gitblit instance
-- added: google-gson dependency
-- added: javamail dependency
-- updated: MarkdownPapers 1.1.1
-- updated: Wicket 1.4.18
-- updated: JGit 1.1.0
-- fixed: syndication urls for WAR deployments
-- fixed: authentication for zip downloads
-
-<hr/>
-
-**0.5.2** &nbsp; *released 2011-07-27*
-
-- fixed: active repositories with a HEAD that pointed to an empty branch caused internal errors (issue 14)
-- fixed: bare-cloned repositories were listed as (empty) and were not clickable (issue 13)
-- fixed: default port for Gitblit GO is now 8443 to be more linux/os x friendly (issue 12)
-- fixed: repositories can now be reliably deleted and renamed (issue 10)
-- fixed: users can now change their passwords (issue 1)
-- fixed: always show root repository group first, i.e. don't sort root group with other groups
-- fixed: tone-down repository group header color
-- added: optionally display repository on-disk size on repositories page<br/>
-    **New:** *web.showRepositorySizes = true*
-- added: forward-slashes ('/', %2F) can be encoded using a custom character to workaround some servlet container default security measures for proxy servers<br/>
-    **New:** *web.forwardSlashCharacter = /*
-- updated: MarkdownPapers 1.1.0
-- updated: Jetty 7.4.3
-
-<hr/>
-
-**0.5.1** &nbsp; *released 2011-06-28*
-
-- clarified SSL certificate generation and configuration for both server-side and client-side
-- added some more troubleshooting information to documentation
-- replaced JavaService with Apache Commons Daemon
-
-<hr/>
-
-**0.5.0** &nbsp; *released 2011-06-26*
-
-- initial release
-
-[jgit]: http://eclipse.org/jgit "Eclipse JGit Site"
diff --git a/docs/05_roadmap.mkd b/docs/05_roadmap.mkd
deleted file mode 100644
index 4ac9b47..0000000
--- a/docs/05_roadmap.mkd
+++ /dev/null
@@ -1,31 +0,0 @@
-## Roadmap
-
-This is not exactly a formal roadmap but it is a priority list of what might be implemented in future releases.  
-This list is volatile.
-
-### TODO (high priority)
-
-* Eclipse: create plugin to enumerate repositories and delegate cloning to EGit
-* Manager: support federation RPCs
-* Manager: redesign ref indicators in log, search, and activity views to support multiple local branches, remote branches, and tags
-* Gitblit: Serve repositories on root URL rather than /git (investigate JGit 1.2 GitFilter)
-
-### TODO (medium priority)
-
-* Gitblit: editable settings page in GO/WAR
-* Gitblit: Clone Repository feature (issue 5)
-    * optional scheduled pulls
-    * optional automatic push to origin/remotes?
-    * optional manual push to origin/remotes?
-* Gitblit: Repository regex substitutions should be stored in .git/.config, not gitblit.properties
-
-### IDEAS
-
-* Gitblit: Pull requests
-* Gitblit: Watch/Star like github with personalized activity feed
-* Gitblit: Push database or orphan branch
-* Gitblit: Re-use the EGit branch visualization table cell renderer as some sort of servlet
-* Gitblit: diff should highlight inserted/removed fragment compared to original line
-* Gitblit: respect Gerrit branch permissions
-* Gitblit: Consider creating more Git model objects and exposing them via the JSON RPC interface to allow inspection/retrieval of Git commits, Git trees, etc from Gitblit.
-* Gitblit: Blame coloring by author (issue 2)
diff --git a/docs/doc_footer.html b/docs/doc_footer.html
deleted file mode 100644
index 577380e..0000000
--- a/docs/doc_footer.html
+++ /dev/null
@@ -1,8 +0,0 @@
-		</div> <!-- markdown -->
-		<footer>
-			<p class="pull-right">{0}</p>
-		The content of this page is licensed under the <a href="http://creativecommons.org/licenses/by/3.0">Creative Commons Attribution 3.0 License</a>.
-		</footer>			
-	</div> <!-- container -->	
-</body>
-</html>
\ No newline at end of file
diff --git a/docs/doc_header.html b/docs/doc_header.html
deleted file mode 100644
index c5ba366..0000000
--- a/docs/doc_header.html
+++ /dev/null
@@ -1,33 +0,0 @@
-<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
-<html>
-	<head>
-		<title>Gitblit</title>
-		<link rel="stylesheet" href="./bootstrap/css/bootstrap.css"/>
-		<link rel="stylesheet" type="text/css" href="./gitblit.css"/>
-		<link rel="shortcut icon" type="image/png" href="./gitblt-favicon.png" />
-		<meta name="ROBOTS" content="INDEX, NOFOLLOW">
-		<meta http-equiv="imagetoolbar" content="no" />
-		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
-		<meta name="keywords" content="java git server" />
-
-		<script type="text/javascript" src="./bootstrap/js/jquery.js"></script>
-		<script type="text/javascript" src="./bootstrap/js/bootstrap.js"></script>
-		
-		<script type="text/javascript" src="prettify/prettify.js"></script>
-		<link href="prettify/prettify.css" type="text/css" rel="stylesheet" />		
-	</head>
-	<body onload="prettyPrint()">
-		<div class="navbar navbar-fixed-top">
-			<div class="navbar-inner">
-				<div class="container">
-					<a class="brand" href="http://gitblit.com" title="gitblit homepage">
-						<img src="gitblt_25_white.png" width="79" height="25" alt="gitblit" class="logo"/>
-					</a>
-					<ul class="nav">
-						{1}		
-					</ul>
-				</div>
-			</div>
-		</div>
-		<div class="container">
-			<div class="markdown">
\ No newline at end of file
diff --git a/docs/site_footer.html b/docs/site_footer.html
deleted file mode 100644
index 577380e..0000000
--- a/docs/site_footer.html
+++ /dev/null
@@ -1,8 +0,0 @@
-		</div> <!-- markdown -->
-		<footer>
-			<p class="pull-right">{0}</p>
-		The content of this page is licensed under the <a href="http://creativecommons.org/licenses/by/3.0">Creative Commons Attribution 3.0 License</a>.
-		</footer>			
-	</div> <!-- container -->	
-</body>
-</html>
\ No newline at end of file
diff --git a/docs/site_header.html b/docs/site_header.html
deleted file mode 100644
index ecfa67d..0000000
--- a/docs/site_header.html
+++ /dev/null
@@ -1,51 +0,0 @@
-<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
-<html>
-	<head>
-		<title>Gitblit</title>
-		<link rel="stylesheet" href="./bootstrap/css/bootstrap.css"/>
-		<link rel="stylesheet" type="text/css" href="./gitblit.css"/>
-		<link rel="shortcut icon" type="image/png" href="./gitblt-favicon.png" />
-		<meta name="ROBOTS" content="INDEX">
-		<meta http-equiv="imagetoolbar" content="no" />
-		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
-		<meta name="keywords" content="java git server" />
-
-		<script type="text/javascript" src="./bootstrap/js/jquery.js"></script>
-		<script type="text/javascript" src="./bootstrap/js/bootstrap.js"></script>
-		
-		<script type="text/javascript" src="./fancybox/query.mousewheel-3.0.4.pack.js"></script>
-		<script type="text/javascript" src="./fancybox/jquery.fancybox-1.3.4.pack.js"></script>
-		<link rel="stylesheet" type="text/css" href="./fancybox/jquery.fancybox-1.3.4.css" media="screen" />
-		
-		<script type="text/javascript" src="./screenshots.js"></script>		
-		
-		<script type="text/javascript" src="prettify/prettify.js"></script>
-		<link href="prettify/prettify.css" type="text/css" rel="stylesheet" />
-		
-		<!-- Place this tag in your head or just before your close body tag -->
-		<link rel="canonical" href="http://gitblit.com" />
-		<link rel="publisher" href="https://plus.google.com/114464678392593421684" />
-		<script type="text/javascript" src="https://apis.google.com/js/plusone.js"></script>
-		
-		<!-- ANALYTICS -->
-	</head>
-	<body onload="prettyPrint()">
-		<div class="navbar navbar-fixed-top">
-			<div class="navbar-inner">
-				<div class="container">
-					<a class="brand" href="http://gitblit.com" title="gitblit homepage">
-						<img src="gitblt_25_white.png" width="79" height="25" alt="gitblit" class="logo"/>
-					</a>
-					
-					<ul class="nav">
-						{1}		
-						<!-- Google Plus Badge -->
-						<li><a href="https://plus.google.com/114464678392593421684?prsrc=3" style="margin-top:3px;text-decoration: none;"><img src="https://ssl.gstatic.com/images/icons/gplus-16.png" width="16" height="16" style="border: 0;"/></a></li>
-						<!-- Google Plus One -->
-						<li><div style="margin-top:14px;"><g:plusone size="small" href="http://gitblit.com"></g:plusone></div></li>
-					</ul>
-				</div>
-			</div>
-		</div>
-		<div class="container">
-			<div class="markdown">
\ No newline at end of file
diff --git a/gitblit.iml b/gitblit.iml
index 232f00d..85ba779 100644
--- a/gitblit.iml
+++ b/gitblit.iml
@@ -5,9 +5,9 @@
     <output-test url="file://$MODULE_DIR$/bin/test-classes" />
     <exclude-output />
     <content url="file://$MODULE_DIR$">
-      <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
-      <sourceFolder url="file://$MODULE_DIR$/resources" isTestSource="false" />
-      <sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" />
+      <sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
+      <sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
+      <sourceFolder url="file://$MODULE_DIR$/src/main/resources" isTestSource="false" />
     </content>
     <orderEntry type="sourceFolder" forTests="false" />
     <orderEntry type="module-library">
@@ -17,7 +17,7 @@
         </CLASSES>
         <JAVADOC />
         <SOURCES>
-          <root url="jar://$MODULE_DIR$/ext/src/jcommander-1.17-sources.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/src/jcommander-1.17.jar!/" />
         </SOURCES>
       </library>
     </orderEntry>
@@ -28,7 +28,7 @@
         </CLASSES>
         <JAVADOC />
         <SOURCES>
-          <root url="jar://$MODULE_DIR$/ext/src/log4j-1.2.17-sources.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/src/log4j-1.2.17.jar!/" />
         </SOURCES>
       </library>
     </orderEntry>
@@ -39,7 +39,7 @@
         </CLASSES>
         <JAVADOC />
         <SOURCES>
-          <root url="jar://$MODULE_DIR$/ext/src/slf4j-api-1.6.6-sources.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/src/slf4j-api-1.6.6.jar!/" />
         </SOURCES>
       </library>
     </orderEntry>
@@ -50,7 +50,7 @@
         </CLASSES>
         <JAVADOC />
         <SOURCES>
-          <root url="jar://$MODULE_DIR$/ext/src/slf4j-log4j12-1.6.6-sources.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/src/slf4j-log4j12-1.6.6.jar!/" />
         </SOURCES>
       </library>
     </orderEntry>
@@ -61,7 +61,7 @@
         </CLASSES>
         <JAVADOC />
         <SOURCES>
-          <root url="jar://$MODULE_DIR$/ext/src/mail-1.4.3-sources.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/src/mail-1.4.3.jar!/" />
         </SOURCES>
       </library>
     </orderEntry>
@@ -72,7 +72,7 @@
         </CLASSES>
         <JAVADOC />
         <SOURCES>
-          <root url="jar://$MODULE_DIR$/ext/src/javax.servlet-api-3.0.1-sources.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/src/javax.servlet-api-3.0.1.jar!/" />
         </SOURCES>
       </library>
     </orderEntry>
@@ -83,7 +83,7 @@
         </CLASSES>
         <JAVADOC />
         <SOURCES>
-          <root url="jar://$MODULE_DIR$/ext/src/jetty-webapp-7.6.8.v20121106-sources.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/src/jetty-webapp-7.6.8.v20121106.jar!/" />
         </SOURCES>
       </library>
     </orderEntry>
@@ -94,7 +94,7 @@
         </CLASSES>
         <JAVADOC />
         <SOURCES>
-          <root url="jar://$MODULE_DIR$/ext/src/jetty-ajp-7.6.8.v20121106-sources.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/src/jetty-ajp-7.6.8.v20121106.jar!/" />
         </SOURCES>
       </library>
     </orderEntry>
@@ -105,7 +105,7 @@
         </CLASSES>
         <JAVADOC />
         <SOURCES>
-          <root url="jar://$MODULE_DIR$/ext/src/wicket-1.4.21-sources.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/src/wicket-1.4.21.jar!/" />
         </SOURCES>
       </library>
     </orderEntry>
@@ -116,7 +116,7 @@
         </CLASSES>
         <JAVADOC />
         <SOURCES>
-          <root url="jar://$MODULE_DIR$/ext/src/wicket-auth-roles-1.4.21-sources.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/src/wicket-auth-roles-1.4.21.jar!/" />
         </SOURCES>
       </library>
     </orderEntry>
@@ -127,7 +127,7 @@
         </CLASSES>
         <JAVADOC />
         <SOURCES>
-          <root url="jar://$MODULE_DIR$/ext/src/wicket-extensions-1.4.21-sources.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/src/wicket-extensions-1.4.21.jar!/" />
         </SOURCES>
       </library>
     </orderEntry>
@@ -138,7 +138,7 @@
         </CLASSES>
         <JAVADOC />
         <SOURCES>
-          <root url="jar://$MODULE_DIR$/ext/src/googlecharts-1.4.21-sources.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/src/googlecharts-1.4.21.jar!/" />
         </SOURCES>
       </library>
     </orderEntry>
@@ -149,7 +149,7 @@
         </CLASSES>
         <JAVADOC />
         <SOURCES>
-          <root url="jar://$MODULE_DIR$/ext/src/lucene-core-3.6.1-sources.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/src/lucene-core-3.6.1.jar!/" />
         </SOURCES>
       </library>
     </orderEntry>
@@ -160,7 +160,7 @@
         </CLASSES>
         <JAVADOC />
         <SOURCES>
-          <root url="jar://$MODULE_DIR$/ext/src/lucene-highlighter-3.6.1-sources.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/src/lucene-highlighter-3.6.1.jar!/" />
         </SOURCES>
       </library>
     </orderEntry>
@@ -171,7 +171,7 @@
         </CLASSES>
         <JAVADOC />
         <SOURCES>
-          <root url="jar://$MODULE_DIR$/ext/src/lucene-memory-3.6.1-sources.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/src/lucene-memory-3.6.1.jar!/" />
         </SOURCES>
       </library>
     </orderEntry>
@@ -182,7 +182,7 @@
         </CLASSES>
         <JAVADOC />
         <SOURCES>
-          <root url="jar://$MODULE_DIR$/ext/src/lucene-queries-3.6.1-sources.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/src/lucene-queries-3.6.1.jar!/" />
         </SOURCES>
       </library>
     </orderEntry>
@@ -202,40 +202,51 @@
         </CLASSES>
         <JAVADOC />
         <SOURCES>
-          <root url="jar://$MODULE_DIR$/ext/src/markdownpapers-core-1.3.2-sources.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/src/markdownpapers-core-1.3.2.jar!/" />
         </SOURCES>
       </library>
     </orderEntry>
     <orderEntry type="module-library">
-      <library name="org.eclipse.jgit-2.2.0.201212191850-r.jar">
+      <library name="org.eclipse.jgit-3.0.0.201306101825-r.jar">
         <CLASSES>
-          <root url="jar://$MODULE_DIR$/ext/org.eclipse.jgit-2.2.0.201212191850-r.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/org.eclipse.jgit-3.0.0.201306101825-r.jar!/" />
         </CLASSES>
         <JAVADOC />
         <SOURCES>
-          <root url="jar://$MODULE_DIR$/ext/src/org.eclipse.jgit-2.2.0.201212191850-r-sources.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/src/org.eclipse.jgit-3.0.0.201306101825-r.jar!/" />
         </SOURCES>
       </library>
     </orderEntry>
     <orderEntry type="module-library">
-      <library name="jsch-0.1.44-1.jar">
+      <library name="jsch-0.1.46.jar">
         <CLASSES>
-          <root url="jar://$MODULE_DIR$/ext/jsch-0.1.44-1.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/jsch-0.1.46.jar!/" />
         </CLASSES>
         <JAVADOC />
         <SOURCES>
-          <root url="jar://$MODULE_DIR$/ext/src/jsch-0.1.44-1-sources.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/src/jsch-0.1.46.jar!/" />
         </SOURCES>
       </library>
     </orderEntry>
     <orderEntry type="module-library">
-      <library name="org.eclipse.jgit.http.server-2.2.0.201212191850-r.jar">
+      <library name="JavaEWAH-0.5.6.jar">
         <CLASSES>
-          <root url="jar://$MODULE_DIR$/ext/org.eclipse.jgit.http.server-2.2.0.201212191850-r.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/JavaEWAH-0.5.6.jar!/" />
         </CLASSES>
         <JAVADOC />
         <SOURCES>
-          <root url="jar://$MODULE_DIR$/ext/src/org.eclipse.jgit.http.server-2.2.0.201212191850-r-sources.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/src/JavaEWAH-0.5.6.jar!/" />
+        </SOURCES>
+      </library>
+    </orderEntry>
+    <orderEntry type="module-library">
+      <library name="org.eclipse.jgit.http.server-3.0.0.201306101825-r.jar">
+        <CLASSES>
+          <root url="jar://$MODULE_DIR$/ext/org.eclipse.jgit.http.server-3.0.0.201306101825-r.jar!/" />
+        </CLASSES>
+        <JAVADOC />
+        <SOURCES>
+          <root url="jar://$MODULE_DIR$/ext/src/org.eclipse.jgit.http.server-3.0.0.201306101825-r.jar!/" />
         </SOURCES>
       </library>
     </orderEntry>
@@ -246,7 +257,7 @@
         </CLASSES>
         <JAVADOC />
         <SOURCES>
-          <root url="jar://$MODULE_DIR$/ext/src/bcprov-jdk15on-1.47-sources.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/src/bcprov-jdk15on-1.47.jar!/" />
         </SOURCES>
       </library>
     </orderEntry>
@@ -257,7 +268,7 @@
         </CLASSES>
         <JAVADOC />
         <SOURCES>
-          <root url="jar://$MODULE_DIR$/ext/src/bcmail-jdk15on-1.47-sources.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/src/bcmail-jdk15on-1.47.jar!/" />
         </SOURCES>
       </library>
     </orderEntry>
@@ -268,7 +279,7 @@
         </CLASSES>
         <JAVADOC />
         <SOURCES>
-          <root url="jar://$MODULE_DIR$/ext/src/bcpkix-jdk15on-1.47-sources.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/src/bcpkix-jdk15on-1.47.jar!/" />
         </SOURCES>
       </library>
     </orderEntry>
@@ -279,7 +290,7 @@
         </CLASSES>
         <JAVADOC />
         <SOURCES>
-          <root url="jar://$MODULE_DIR$/ext/src/rome-0.9-sources.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/src/rome-0.9.jar!/" />
         </SOURCES>
       </library>
     </orderEntry>
@@ -290,7 +301,7 @@
         </CLASSES>
         <JAVADOC />
         <SOURCES>
-          <root url="jar://$MODULE_DIR$/ext/src/jdom-1.0-sources.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/src/jdom-1.0.jar!/" />
         </SOURCES>
       </library>
     </orderEntry>
@@ -301,7 +312,7 @@
         </CLASSES>
         <JAVADOC />
         <SOURCES>
-          <root url="jar://$MODULE_DIR$/ext/src/gson-1.7.2-sources.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/src/gson-1.7.2.jar!/" />
         </SOURCES>
       </library>
     </orderEntry>
@@ -312,7 +323,7 @@
         </CLASSES>
         <JAVADOC />
         <SOURCES>
-          <root url="jar://$MODULE_DIR$/ext/src/groovy-all-1.8.8-sources.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/src/groovy-all-1.8.8.jar!/" />
         </SOURCES>
       </library>
     </orderEntry>
@@ -323,7 +334,7 @@
         </CLASSES>
         <JAVADOC />
         <SOURCES>
-          <root url="jar://$MODULE_DIR$/ext/src/unboundid-ldapsdk-2.3.0-sources.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/src/unboundid-ldapsdk-2.3.0.jar!/" />
         </SOURCES>
       </library>
     </orderEntry>
@@ -334,7 +345,7 @@
         </CLASSES>
         <JAVADOC />
         <SOURCES>
-          <root url="jar://$MODULE_DIR$/ext/src/ivy-2.2.0-sources.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/src/ivy-2.2.0.jar!/" />
         </SOURCES>
       </library>
     </orderEntry>
@@ -354,7 +365,7 @@
         </CLASSES>
         <JAVADOC />
         <SOURCES>
-          <root url="jar://$MODULE_DIR$/ext/src/commons-compress-1.4.1-sources.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/src/commons-compress-1.4.1.jar!/" />
         </SOURCES>
       </library>
     </orderEntry>
@@ -365,28 +376,261 @@
         </CLASSES>
         <JAVADOC />
         <SOURCES>
-          <root url="jar://$MODULE_DIR$/ext/src/xz-1.0-sources.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/src/xz-1.0.jar!/" />
         </SOURCES>
       </library>
     </orderEntry>
-    <orderEntry type="module-library" scope="TEST">
-      <library name="junit-4.10.jar">
+    <orderEntry type="module-library">
+      <library name="force-partner-api-24.0.0.jar">
         <CLASSES>
-          <root url="jar://$MODULE_DIR$/ext/junit-4.10.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/force-partner-api-24.0.0.jar!/" />
         </CLASSES>
         <JAVADOC />
         <SOURCES>
-          <root url="jar://$MODULE_DIR$/ext/src/junit-4.10-sources.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/src/force-partner-api-24.0.0.jar!/" />
+        </SOURCES>
+      </library>
+    </orderEntry>
+    <orderEntry type="module-library">
+      <library name="force-wsc-24.0.0.jar">
+        <CLASSES>
+          <root url="jar://$MODULE_DIR$/ext/force-wsc-24.0.0.jar!/" />
+        </CLASSES>
+        <JAVADOC />
+        <SOURCES>
+          <root url="jar://$MODULE_DIR$/ext/src/force-wsc-24.0.0.jar!/" />
+        </SOURCES>
+      </library>
+    </orderEntry>
+    <orderEntry type="module-library">
+      <library name="js-1.7R2.jar">
+        <CLASSES>
+          <root url="jar://$MODULE_DIR$/ext/js-1.7R2.jar!/" />
+        </CLASSES>
+        <JAVADOC />
+        <SOURCES>
+          <root url="jar://$MODULE_DIR$/ext/src/js-1.7R2.jar!/" />
+        </SOURCES>
+      </library>
+    </orderEntry>
+    <orderEntry type="module-library">
+      <library name="freemarker-2.3.19.jar">
+        <CLASSES>
+          <root url="jar://$MODULE_DIR$/ext/freemarker-2.3.19.jar!/" />
+        </CLASSES>
+        <JAVADOC />
+        <SOURCES>
+          <root url="jar://$MODULE_DIR$/ext/src/freemarker-2.3.19.jar!/" />
+        </SOURCES>
+      </library>
+    </orderEntry>
+    <orderEntry type="module-library">
+      <library name="waffle-jna-1.5.jar">
+        <CLASSES>
+          <root url="jar://$MODULE_DIR$/ext/waffle-jna-1.5.jar!/" />
+        </CLASSES>
+        <JAVADOC />
+        <SOURCES>
+          <root url="jar://$MODULE_DIR$/ext/src/waffle-jna-1.5.jar!/" />
+        </SOURCES>
+      </library>
+    </orderEntry>
+    <orderEntry type="module-library">
+      <library name="platform-3.5.0.jar">
+        <CLASSES>
+          <root url="jar://$MODULE_DIR$/ext/platform-3.5.0.jar!/" />
+        </CLASSES>
+        <JAVADOC />
+        <SOURCES>
+          <root url="jar://$MODULE_DIR$/ext/src/platform-3.5.0.jar!/" />
+        </SOURCES>
+      </library>
+    </orderEntry>
+    <orderEntry type="module-library">
+      <library name="jna-3.5.0.jar">
+        <CLASSES>
+          <root url="jar://$MODULE_DIR$/ext/jna-3.5.0.jar!/" />
+        </CLASSES>
+        <JAVADOC />
+        <SOURCES>
+          <root url="jar://$MODULE_DIR$/ext/src/jna-3.5.0.jar!/" />
+        </SOURCES>
+      </library>
+    </orderEntry>
+    <orderEntry type="module-library">
+      <library name="guava-13.0.1.jar">
+        <CLASSES>
+          <root url="jar://$MODULE_DIR$/ext/guava-13.0.1.jar!/" />
+        </CLASSES>
+        <JAVADOC />
+        <SOURCES>
+          <root url="jar://$MODULE_DIR$/ext/src/guava-13.0.1.jar!/" />
         </SOURCES>
       </library>
     </orderEntry>
     <orderEntry type="module-library" scope="TEST">
-      <library name="hamcrest-core-1.1.jar">
+      <library name="junit-4.11.jar">
         <CLASSES>
-          <root url="jar://$MODULE_DIR$/ext/hamcrest-core-1.1.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/junit-4.11.jar!/" />
         </CLASSES>
         <JAVADOC />
-        <SOURCES />
+        <SOURCES>
+          <root url="jar://$MODULE_DIR$/ext/src/junit-4.11.jar!/" />
+        </SOURCES>
+      </library>
+    </orderEntry>
+    <orderEntry type="module-library" scope="TEST">
+      <library name="hamcrest-core-1.3.jar">
+        <CLASSES>
+          <root url="jar://$MODULE_DIR$/ext/hamcrest-core-1.3.jar!/" />
+        </CLASSES>
+        <JAVADOC />
+        <SOURCES>
+          <root url="jar://$MODULE_DIR$/ext/src/hamcrest-core-1.3.jar!/" />
+        </SOURCES>
+      </library>
+    </orderEntry>
+    <orderEntry type="module-library" scope="TEST">
+      <library name="selenium-java-2.28.0.jar">
+        <CLASSES>
+          <root url="jar://$MODULE_DIR$/ext/selenium-java-2.28.0.jar!/" />
+        </CLASSES>
+        <JAVADOC />
+        <SOURCES>
+          <root url="jar://$MODULE_DIR$/ext/src/selenium-java-2.28.0.jar!/" />
+        </SOURCES>
+      </library>
+    </orderEntry>
+    <orderEntry type="module-library" scope="TEST">
+      <library name="selenium-support-2.28.0.jar">
+        <CLASSES>
+          <root url="jar://$MODULE_DIR$/ext/selenium-support-2.28.0.jar!/" />
+        </CLASSES>
+        <JAVADOC />
+        <SOURCES>
+          <root url="jar://$MODULE_DIR$/ext/src/selenium-support-2.28.0.jar!/" />
+        </SOURCES>
+      </library>
+    </orderEntry>
+    <orderEntry type="module-library" scope="TEST">
+      <library name="selenium-firefox-driver-2.28.0.jar">
+        <CLASSES>
+          <root url="jar://$MODULE_DIR$/ext/selenium-firefox-driver-2.28.0.jar!/" />
+        </CLASSES>
+        <JAVADOC />
+        <SOURCES>
+          <root url="jar://$MODULE_DIR$/ext/src/selenium-firefox-driver-2.28.0.jar!/" />
+        </SOURCES>
+      </library>
+    </orderEntry>
+    <orderEntry type="module-library" scope="TEST">
+      <library name="selenium-remote-driver-2.28.0.jar">
+        <CLASSES>
+          <root url="jar://$MODULE_DIR$/ext/selenium-remote-driver-2.28.0.jar!/" />
+        </CLASSES>
+        <JAVADOC />
+        <SOURCES>
+          <root url="jar://$MODULE_DIR$/ext/src/selenium-remote-driver-2.28.0.jar!/" />
+        </SOURCES>
+      </library>
+    </orderEntry>
+    <orderEntry type="module-library" scope="TEST">
+      <library name="cglib-nodep-2.1_3.jar">
+        <CLASSES>
+          <root url="jar://$MODULE_DIR$/ext/cglib-nodep-2.1_3.jar!/" />
+        </CLASSES>
+        <JAVADOC />
+        <SOURCES>
+          <root url="jar://$MODULE_DIR$/ext/src/cglib-nodep-2.1_3.jar!/" />
+        </SOURCES>
+      </library>
+    </orderEntry>
+    <orderEntry type="module-library" scope="TEST">
+      <library name="json-20080701.jar">
+        <CLASSES>
+          <root url="jar://$MODULE_DIR$/ext/json-20080701.jar!/" />
+        </CLASSES>
+        <JAVADOC />
+        <SOURCES>
+          <root url="jar://$MODULE_DIR$/ext/src/json-20080701.jar!/" />
+        </SOURCES>
+      </library>
+    </orderEntry>
+    <orderEntry type="module-library" scope="TEST">
+      <library name="selenium-api-2.28.0.jar">
+        <CLASSES>
+          <root url="jar://$MODULE_DIR$/ext/selenium-api-2.28.0.jar!/" />
+        </CLASSES>
+        <JAVADOC />
+        <SOURCES>
+          <root url="jar://$MODULE_DIR$/ext/src/selenium-api-2.28.0.jar!/" />
+        </SOURCES>
+      </library>
+    </orderEntry>
+    <orderEntry type="module-library" scope="TEST">
+      <library name="httpclient-4.2.1.jar">
+        <CLASSES>
+          <root url="jar://$MODULE_DIR$/ext/httpclient-4.2.1.jar!/" />
+        </CLASSES>
+        <JAVADOC />
+        <SOURCES>
+          <root url="jar://$MODULE_DIR$/ext/src/httpclient-4.2.1.jar!/" />
+        </SOURCES>
+      </library>
+    </orderEntry>
+    <orderEntry type="module-library" scope="TEST">
+      <library name="httpcore-4.2.1.jar">
+        <CLASSES>
+          <root url="jar://$MODULE_DIR$/ext/httpcore-4.2.1.jar!/" />
+        </CLASSES>
+        <JAVADOC />
+        <SOURCES>
+          <root url="jar://$MODULE_DIR$/ext/src/httpcore-4.2.1.jar!/" />
+        </SOURCES>
+      </library>
+    </orderEntry>
+    <orderEntry type="module-library" scope="TEST">
+      <library name="commons-logging-1.1.1.jar">
+        <CLASSES>
+          <root url="jar://$MODULE_DIR$/ext/commons-logging-1.1.1.jar!/" />
+        </CLASSES>
+        <JAVADOC />
+        <SOURCES>
+          <root url="jar://$MODULE_DIR$/ext/src/commons-logging-1.1.1.jar!/" />
+        </SOURCES>
+      </library>
+    </orderEntry>
+    <orderEntry type="module-library" scope="TEST">
+      <library name="commons-codec-1.6.jar">
+        <CLASSES>
+          <root url="jar://$MODULE_DIR$/ext/commons-codec-1.6.jar!/" />
+        </CLASSES>
+        <JAVADOC />
+        <SOURCES>
+          <root url="jar://$MODULE_DIR$/ext/src/commons-codec-1.6.jar!/" />
+        </SOURCES>
+      </library>
+    </orderEntry>
+    <orderEntry type="module-library" scope="TEST">
+      <library name="commons-exec-1.1.jar">
+        <CLASSES>
+          <root url="jar://$MODULE_DIR$/ext/commons-exec-1.1.jar!/" />
+        </CLASSES>
+        <JAVADOC />
+        <SOURCES>
+          <root url="jar://$MODULE_DIR$/ext/src/commons-exec-1.1.jar!/" />
+        </SOURCES>
+      </library>
+    </orderEntry>
+    <orderEntry type="module-library" scope="TEST">
+      <library name="commons-io-2.2.jar">
+        <CLASSES>
+          <root url="jar://$MODULE_DIR$/ext/commons-io-2.2.jar!/" />
+        </CLASSES>
+        <JAVADOC />
+        <SOURCES>
+          <root url="jar://$MODULE_DIR$/ext/src/commons-io-2.2.jar!/" />
+        </SOURCES>
       </library>
     </orderEntry>
     <orderEntry type="inheritedJdk" />
diff --git a/release.template b/release.template
new file mode 100644
index 0000000..8342bb5
--- /dev/null
+++ b/release.template
@@ -0,0 +1,52 @@
+#!/bin/bash
+#
+# ${project.version} release script
+#
+
+# go back one commit to RELEASE commit
+echo ""
+echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
+echo "Checking out ${project.version} RELEASE commit ${project.commitId}"
+echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
+echo ""
+git checkout ${project.commitId}
+
+# build RELEASE artifacts
+echo ""
+echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
+echo "Building ${project.version} RELEASE artifacts"
+echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
+echo ""
+ant clean buildAll
+
+# upload artifacts
+echo ""
+echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
+echo "Uploading ${project.version} artifacts"
+echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
+echo ""
+ant publishBinaries
+
+# build site, update gh-pages, and ftp upload site to hosting provider
+echo ""
+echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
+echo "Building ${project.version} website"
+echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
+echo ""
+ant publishSite
+
+# return to project master
+echo ""
+echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
+echo "Checking out master"
+echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
+echo ""
+git checkout master
+
+# push project branches
+echo ""
+echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
+echo "Pushing master, gh-pages, and tag ${project.tag}"
+echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
+echo ""
+git push origin master gh-pages tag ${project.tag}
diff --git a/releases.moxie b/releases.moxie
new file mode 100644
index 0000000..0bf1578
--- /dev/null
+++ b/releases.moxie
@@ -0,0 +1,920 @@
+#
+# ${project.version} release
+#
+r18: {
+    title: ${project.name} ${project.version} released
+    id: ${project.version}
+    date: ${project.buildDate}
+    note: ''
+          If you have forked repositories and your are upgrading from 1.2.x to 1.3.x, please DO NOT RELOCATE your repositories folder when running 1.3.x the first time.  Gitblit will update forked repository configs on the first execution and it is critical that ${git.repositoriesFolder} points to the same location used by 1.2.x.
+          ''
+    html: ~
+    text: ~
+    security: ~
+    fixes:
+	- Gitblit-as-viewer with no repository urls failed to display summary page (issue 269)
+	- Fixed missing model class dependencies in Gitblit Manager build
+	- Fix for IE10 compatibility mode
+	- Reset dashboard and activity commit cache on branch REWIND or DELETE
+	- Fixed bug with adding new local users with external authentication
+	- Fixed missing clone url on the empty repository page
+    changes:
+	- updated Chinese translation
+	- updated Dutch translation
+	- updated Spanish translation
+	- updated Korean translation
+    additions:
+	- Added optional browser-side page caching using Last-Modified and Cache-Control for the dashboard, activity, project, and several repository pages
+    dependencyChanges: ~
+	settings:
+	- { name: 'web.pageCacheExpires', defaultValue: 0 }
+    contributors:
+	- Rainer Alföldi 
+	- Liyu Wang
+	- Jeroen Baten
+	- James Moger
+	- Stardrad Yin
+	- Chad Horohoe
+	- Eduardo Guervós Narvaez
+	- Dongsu, KIM
+}
+
+#
+# 1.3.0
+#
+r17: {
+    title: Gitblit 1.3.0 Released
+    id: 1.3.0
+    date: 2013-07-14
+    html: ''
+          Release highlights include:
+          <ul>
+          <li>integrated git daemon</li>
+          <li>compare refs or commits page</li>
+          <li>completed the Gitblit reflog (formerly pushlog) introduced in 1.2.1</li>
+          <li>added new dashboard pages</li>
+          <li>added a stars feature</li>
+          <li>improved the repository url panel to show your access permission and to offer native app clone links</li>
+          <li>improved navigation and theme</li>
+          <li>customizable page header colors and logo</li>
+          <li>recent activity commit caching to improve performance of dashboard and activity pages</li>
+          <li>Windows authentication</li>
+          <li>Salesforce.com authentication</li>
+          <li>lots of bug fixes</li>
+          </ul>
+          <p> </p>
+          Thank you to <a href="http://syntevo.com">syntevo</a>, <a href="http://atlassian.com">Atlassian</a>, <a href="http://fournova.com">fournova</a>, and <a href="http://github.com">Github</a> for their permission and use of their artwork for the native app clone menus.
+          ''
+    note: ''
+          If you have forked repositories and your are upgrading to 1.3.0, please DO NOT RELOCATE your repositories folder when running 1.3.0 the first time.  Gitblit will update forked repository configs on the first execution and it is critical that ${git.repositoriesFolder} points to the same location used by 1.2.x.
+          ''
+	security:
+	- Raw servlet was insecure. If someone knew the exact repository name and path to a file, the raw blob could be retrieved bypassing security constraints. (issue 198)
+    fixes:
+	 - Use bash instead of sh in Linux/OSX shell scripts (issue 154)
+	 - Fix NPE when getting user's fork without repository list caching (issue 182)
+	 - Fix internal error on folder history links (issue 192)
+	 - Fix NPE in repositories panel when viewing a federation proposal (issue 195)
+	 - Fix NPEs when initializing the context on a servlet containers which returns a null contextFolder (issue 199)
+	 - Fixed incorrect icon file name for .doc files (issue 200)
+	 - Do not queue emails with no recipients (issue 201)
+	 - Disable view and blame links for deleted blobs (issue 216)
+	 - Fixed 1.2.x regression with individually symlinked repositories (issue 217)
+	 - Fixed UTF-8 encoding errors in email notifications (issue 218)
+	 - Fixed NPE in 1.2.1 Federation Client (issue 219)
+	 - Fixed extracting Groovy scripts on Express installs (issue 220)
+	 - Ensure Redmine url is properly formatted (issue 223)
+	 - Use standard ServletRequestWrapper instead of custom wrapper (issue 224)
+	 - Switch commit message back to a pre and ensure that it is properly escaped when combined with commit message regex substitution (issue 242)
+	 - Fixed AddIndexedBranch tool --branch parameter (issue 247)  
+	 - Improve NPE handling for hook script enumeration (issue-253)
+	 - Workaround missing commit information in blame page (JGit bug 374382, issue-254) 
+	 - Ignore orphan ".git" folder in the repositories root folder (issue-256)
+	 - Fixed bug where a null permission was added to a user model on a repository rename when the permission had really been inherited from a team membership (issue-259)
+	 - Fixed committer verification with merge commits (issue-264)
+	 - Fixed bug in submodule repository linking (issue-266)
+     - Could not reset settings with $ or { characters through Gitblit Manager because they are not properly escaped
+	 - Added more error checking to blob page and blame page
+	 - Disable SNI extensions for client SSL connections
+	 - Fixed prettify language extension loading
+	 - Fixed index out of bounds exceptions when generating client certificates for a user when the user's table has been filtered
+	 - Fixed AddindexedBranch tool when specifying the non-default branch.
+	 - Fixed submodule diff display
+
+	changes:
+	 - Retrieve summary and metric graphs from Google over https (issue-61)
+	 - Persist originRepository (for forks) in the repository config instead of relying on parsing origin urls which are susceptible to filesystem relocation (issue 190) 
+	 - Improved error logging for servlet containers which provide a null contextFolder (issue 199)
+	 - Improve Gerrit change ref decoration in the refs panel (issue 206)
+	 - Display full commit message on commitdiff page (issue-258)
+	 - Improved the repository url display.  This display now indicates your repository access permission, per-protocol.
+	 - Automatically encode/decode usernames for urls using %XX notation on space, @, and \
+ 	 - Disable Gson's pretty printing which has a huge performance gain
+	 - Properly set application/json content-type on api calls
+	 - Make days back filter choices a setting
+	 - Changed default days back filter setting to 7 days
+	 - Set rel="nofollow" on compressed download links
+	 - Improved page title
+	 - Updated Polish translation
+	 - Updated Japanese translation
+	 
+    additions: 
+	 - Added a ui for the ref log introduced in 1.2.1 (issue-177)
+	 - Added weblogic.xml to WAR for deployment on WebLogic (issue 199)
+	 - Support setting a custom header logo (issue 208)
+	 - Support header color customizations (issue 209)
+	 - Support username substitution in web.otherUrls (issue 213)
+	 - Option to force client-side basic authentication instead of form-based authentication if web.authenticateViewPages=true (issue 222)
+	 - Set author as tooltip of last change column in the repositories panel (issue-238)
+	 - Setting to automatically create an user account based on an authenticated user principal from the servlet container (issue-246)
+	 - Added WindowsUserService to authenticate users against Windows accounts (issue-250)
+	 - Global and per-repository setting to exclude authors from metrics (issue-251)
+	 - Added commit cache to improve Activity, Dashboard, and Project page generation times
+	 - Added SalesForce.com user service
+     - Added simple star/unstar function to flag or bookmark interesting repositories
+     - Added Dashboard page which shows a news feed for starred repositories and offers a filterable list of repositories you care about
+	 - Added client application menus for Git, SmartGit/Hg, SourceTree, Tower, GitHub for Windows, and GitHub for Mac
+	 - Added GO http/https connector thread pool size setting
+	 - Added a server setting to force a particular translation/Locale for all sessions
+	 - Added smart Git Daemon serving.  If enabled, git:// access will be offered for any repository which permits anonymous access.  If the repository permits anonymous cloning, anonymous git:// clone will be permitted while anonmymous git:// pushes will be rejected.
+	 - Option to automatically tag branch tips on each push with an incremental revision number
+     - Implemented multiple repository owners
+     - Optional periodic LDAP user and team pre-fetching & synchronization
+	 - Added config setting to use SMTPS
+	 - Added option to index all local branches in AddIndexedBranches tool
+     - Display name and version in Tomcat Manager
+     - FogBugz post-receive hook script
+     - Chinese translation
+	 - Support --baseFolder parameter in Federation Client
+
+    contributors:
+	- James Moger
+	- Bandarupalli Satyanarayana
+	- Chad Horohoe
+	- Christian Aistleitner
+	- Colin Bowern
+	- David Ostrovsky
+	- Egbert Teeselink
+	- Hige Maniya
+	- Hirotaka Honma
+	- Ikslawek
+	- Jay Meyer
+	- John Crygier
+	- Kensuke Matsuzaki
+	- Laurens Vrijnsen
+	- Lee Grofit
+	- Lukasz Jader
+	- Martijn Laan
+	- Matthias Bauer
+	- Michael Pailloncy
+	- Michael Schaefers
+	- Oliver Doepner
+	- Philip Boutros
+	- Rafael Cavazin
+	- Ryan Schneider
+	- Sakurai Youhei
+	- Sarah Haselbauer
+	- Slawomir Bochenski
+	- Stardrad Yin
+	- Thomas Pummer
+	- William Whittle
+	- Yukihiko Sawanobori
+	- github/akquinet
+	- github/dapengme
+	
+	dependencyChanges:
+	- JGit 3.0.0.201306101825-r
+	- Iconic font
+	- AngularJS 1.0.7
+	- FreeMarker 2.3.19
+	- Waffle 1.5
+	- JNA 3.5.0
+	- Guava 13.0.1
+	
+	settings:
+	- { name: 'git.daemonBindInterface', defaultValue: 'localhost' }
+	- { name: 'git.daemonPort', defaultValue: 0 }
+	- { name: 'git.defaultIncrementalPushTagPrefix', defaultValue: 'r' }
+	- { name: 'mail.smtps', defaultValue: 'false' }
+	- { name: 'realm.container.autoCreateAccounts', defaultValue: 'false' }
+	- { name: 'realm.salesforce.backingUserService', defaultValue: 'users.conf' }
+	- { name: 'realm.salesforce.orgId', defaultValue: 0 }
+	- { name: 'realm.windows.defaultDomain', defaultValue: ' ' }
+	- { name: 'realm.windows.backingUserService', defaultValue: 'users.conf' }
+	- { name: 'web.activityDuration', defaultValue: 7 }
+	- { name: 'web.activityDurationChoices', defaultValue: '1 3 7 14 21 28' }
+	- { name: 'web.activityCacheDays', defaultValue: 14 }
+	- { name: 'web.allowAppCloneLinks', defaultValue: 'true' }
+	- { name: 'web.forceDefaultLocale', defaultValue: ' ' }
+	- { name: 'web.headerLogo', defaultValue: '${baseFolder}/logo.png' }
+	- { name: 'web.headerBackgroundColor', defaultValue: ' ' }
+	- { name: 'web.headerForegroundColor', defaultValue: ' ' }
+	- { name: 'web.headerHoverColor', defaultValue: ' ' }
+	- { name: 'web.headerBorderColor', defaultValue: ' ' }
+	- { name: 'web.headerBorderFocusColor', defaultValue: ' ' }
+	- { name: 'web.metricAuthorExclusions', defaultValue: ' ' }
+	- { name: 'web.overviewReflogCount', defaultValue: 5 }
+	- { name: 'web.reflogChangesPerPage', defaultValue: 10 }
+	- { name: 'server.nioThreadPoolSize', defaultValue: 50 }
+}
+
+#
+# 1.2.1
+#
+r16: {
+    title: Gitblit 1.2.1 Released
+    id: 1.2.1
+    date: 2013-01-15
+    html: ''
+          Because there are now several types of files and folders that must be considered Gitblit data, the default location for data has changed.
+          <p />
+          You will need to move a few files around when upgrading.  Please review the <a href="upgrade_go.html">upgrading GO</a> or <a href="upgrade_war.html">upgrading WAR</a> page for details.
+          <p />
+          <b>Express Users</b> make sure to update your web.xml file with the ${baseFolder} values!          
+          ''
+    fixes:
+    - Fixed nullpointer on recursively calculating folder sizes when there is a named pipe or symlink in the hierarchy
+    - Added nullchecking when concurrently forking a repository and trying to display the fork network (issue-187)
+    - Fixed bug where permission changes were not visible in the web ui to a logged-in user until the user logged-out and then logged back in again (issue-186)
+    - Fixed nullpointer on creating a repository with mixed case (issue 185)
+    - Include missing model classes in api library (issue-184)
+    - Fixed nullpointer when using *web.allowForking = true* && *git.cacheRepositoryList = false* (issue 182)
+    - Likely fix for commit and commitdiff page failures when a submodule reference changes (issue 178)
+    - Build project models from the repository model cache, when possible, to reduce page load time (issue 172)
+    - Fixed loading of Brazilian Portuguese translation from *nix server
+
+    additions:
+    - ''Fanout PubSub service for self-hosted [Sparkleshare](http://sparkleshare.org) notifications.
+      This service is disabled by default.''
+    - ''Implemented a simple push log based on a hidden, orphan branch refs/gitblit/pushes (issue 177)
+      The push log is not currently visible in the ui, but the data will be collected and it will be exposed to the ui in the next release.''
+    - Support for locally and remotely authenticated accounts in LdapUserService and RedmineUserService (issue 183)
+    - Added Dutch translation
+
+    changes:
+    - ''Gitblit GO and Gitblit WAR are now both configured by `gitblit.properties`. WAR is no longer configured by `web.xml`.
+      However, Express for OpenShift continues to be configured by `web.xml`.''
+    - Support for a *--baseFolder* command-line argument for Gitblit GO and Gitblit Certificate Authority
+    - Support for specifying a *${baseFolder}* parameter in `gitblit.properties` and `web.xml` for several settings
+    - Improve history display of a submodule link
+    - Updated Korean translation
+    - Updated checkstyle definition
+    
+    settings:
+    - { name: fanout.bindInterface, defaultValue: localhost }
+    - { name: fanout.port, defaultValue: 0 }
+    - { name: fanout.useNio, defaultValue: 'true' }
+    - { name: fanout.connectionLimit, defaultValue: 0 }
+
+    contributors:
+	- James Moger
+    - github/mystygage
+    - Dongsu, KIM
+    - Jeroen Baten
+    - github/inaiat
+}
+
+#
+# 1.2.0
+#
+r15: {
+    title: Gitblit 1.2.0 Released
+    id: 1.2.0
+    date: 2012-12-31
+    note: ''
+          The permissions model has changed in the 1.2.0 release.
+          If you are updating your server, you must also update any Gitblit Manager and Federation Client installs to 1.2.0 as well.  The data model used by the RPC mechanism has changed slightly for the new permissions infrastructure.
+          ''
+    fixes:
+    - Fixed regression in *isFrozen* (issue 181)
+    - Author metrics can be broken by newlines in email addresses from converted repositories (issue 176)
+    - Set subjectAlternativeName on generated SSL cert if CN is an ip address (issue 170)
+    - Fixed incorrect links on history page for files not in the current/active commit (issue 166)
+    - Empty repository page failed to handle missing repository (issue 160)
+    - Fixed broken ticgit urls (issue 157)
+    - Exclude submodules from zip downloads (issue 151)
+    - Fixed bug where repository ownership was not updated on rename user
+    - Fixed bug in create/rename repository if you explicitly specified the alias for the root group (e.g. main/myrepo) (issue 143)
+    - Wrapped Markdown parser with improved exception handler (issue 142)
+    - Fixed duplicate entries in repository cache (issue 140)
+    - Fixed connection leak in LDAPUserService (issue 139)
+    - Fixed bug in commit page where changes to a submodule threw a null pointer exception (issue 132)
+    - Fixed bug in the diff view for filenames that have non-ASCII characters (issue 128)
+
+    additions:
+    - ''
+      Implemented discrete repository permissions (issue 36)
+      
+        - V (view in web ui, RSS feeds, download zip)
+        - R (clone)
+        - RW (clone and push)
+        - RWC (clone and push with ref creation)
+        - RWD (clone and push with ref creation, deletion)
+        - RW+ (clone and push with ref creation, deletion, rewind)
+        
+      While not as sophisticated as Gitolite, this does give finer access controls.  These permissions fit in cleanly with the existing users.conf and users.properties files.  In Gitblit <= 1.1.0, all your existing user accounts have RW+ access.   If you are upgrading to 1.2.0, the RW+ access is *preserved* and you will have to lower/adjust accordingly.
+      ''
+    - ''Implemented *case-insensitive* regex repository permission matching (issue 36)
+
+      This allows you to specify a permission like `RW:mygroup/.*` to grant push privileges to all repositories within the *mygroup* project/folder.''
+    - Added DELETE, CREATE, and NON-FAST-FORWARD ref change logging
+    - ''Added support for personal repositories.
+      Personal repositories can be created by accounts with the *create* permission and are stored in *git.repositoriesFolder/~username*.  Each user with personal repositories will have a user page, something like the GitHub profile page.  Personal repositories have all the same features as common repositories, except personal repositories can be renamed by their owner.''
+    - ''Added support for server-side forking of a repository to a personal repository (issue 137)
+      In order to fork a repository, the user account must have the *fork* permission **and** the repository must *allow forks*.  The clone inherits the access list of its origin.  i.e. if Team A has clone access to the origin repository, then by default Team A also has clone access to the fork.  This is to facilitate collaboration.  The fork owner may change access to the fork and add/remove users/teams, etc as required <u>however</u> it should be noted that all personal forks will be enumerated in the fork network regardless of access view restrictions.  If you really must have an invisible fork, the clone it locally, create a new repository for your invisible fork, and push it back to Gitblit.''
+    - Added optional *create-on-push* support
+    - Added **experimental** JGit-based garbage collection service.  This service is disabled by default.
+    - ''Added support for X509 client certificate authentication.  (issue 106)
+      You can require all git servlet access be authenticated by a client certificate.  You may also specify the OID fingerprint to use for mapping a certificate to a username.  It should be noted that the user account MUST already exist in Gitblit for this authentication mechanism to work; this mechanism can not be used to automatically create user accounts from a certificate.''
+    - Revised clean install certificate generation to create a Gitblit GO Certificate Authority certificate; an SSL certificate signed by the CA certificate; and to create distinct server key and server trust stores.  <u>The store files have been renamed!</u>
+    - Added support for Gitblit GO to require usage of client certificates to access the entire server.
+    - Added **Gitblit Certificate Authority**, an x509 PKI management tool for Gitblit GO to encourage use of x509 client certificate authentication.
+    - Added web.shortCommitId setting to control length of shortened commit ids
+    - Added alternate compressed download formats: tar.gz, tar.xz, tar.bzip2 (issue 174)
+    - Added simple project pages.  A project is a subfolder off the *git.repositoriesFolder*.
+    - Added support for X-Forwarded-Context for Apache subdomain proxy configurations (issue 135)
+    - Delete branch feature (issue 121)
+    - Added line links to blob view (issue 130)
+    - Added HTML sendmail hook script and Gitblit.sendHtmlMail method
+    - Added RedmineUserService
+    - Support for committer verification.  Requires use of *--no-ff* when merging branches or pull requests.  See setup page for details.
+    - Added Brazilian Portuguese translation
+
+    changes:
+    - Added server setting to specify keystore alias for ssl certificate (issue 98)
+    - Added optional global and per-repository activity page commit contribution throttle to help tame *really* active repositories (issue 173)
+    - Added support for symlinks in tree page and commit page (issue 171)
+    - All access restricted servlets (e.g. DownloadZip, RSS, etc) will try to authenticate using X509 certificates, container principals, cookies, and BASIC headers, in that order.
+    - Added *groovy* and *scala* to *web.prettyPrintExtensions*
+    - Added short commit id column to log and history tables (issue 168)
+    - Teams can now specify the *admin*, *create*, and *fork* roles to simplify user administration
+    - Use https Gravatar urls to avoid browser complaints
+    - Added frm to default pretty print extensions (issue 156)
+    - Expose ReceivePack to Groovy push hooks (issue 125)
+    - Redirect to summary page when refreshing the empty repository page on a repository that is not empty (issue 129)
+    - Emit a warning in the log file if running on a Tomcat-based servlet container which is unfriendly to %2F forward-slash url encoding AND Gitblit is configured to mount parameters with %2F forward-slash url encoding (issue 126)
+    - ''LDAP admin attribute setting is now consistent with LDAP teams setting and admin teams list.
+      If *realm.ldap.maintainTeams==true* **AND** *realm.ldap.admins* is not empty, then User.canAdmin() is controlled by LDAP administrative team membership.  Otherwise, User.canAdmin() is controlled by Gitblit.''
+    - Support servlet container authentication for existing UserModels (issue 68)
+
+	settings:
+	- { name: web.allowForking, defaultValue: 'true' }
+	- { name: git.allowCreateOnPush, defaultValue: 'true' }
+	- { name: git.allowGarbageCollection, defaultValue: 'false' }
+	- { name: git.garbageCollectionHour, defaultValue: 0 }
+	- { name: git.defaultGarbageCollectionThreshold, defaultValue: 500k }
+	- { name: git.defaultGarbageCollectionPeriod, defaultValue: 7 days }
+	- { name: git.requireClientCertificates, defaultValue: 'false' }
+	- { name: git.enforceCertificateValidity, defaultValue: 'true' }
+	- { name: git.certificateUsernameOIDs, defaultValue: CN }
+	- { name: web.shortCommitIdLength, defaultValue: 8 }
+	- { name: web.compressedDownloads, defaultValue: zip gz }
+	- { name: server.requireClientCertificates, defaultValue: 'false' }
+
+    dependencyChanges:
+    - Jetty 7.6.8
+    - JGit 2.2.0.201212191850-r
+    - Groovy 1.8.8
+    - Wicket 1.4.21
+    - Lucene 3.6.1
+    - BouncyCastle 1.47
+    - MarkdownPapers 1.3.2
+    - JCalendar 1.3.2
+    - Commons-Compress 1.4.1
+    - XZ for Java 1.0
+
+    contributors:
+	- James Moger
+    - github/rafaelcavazin
+    - github/mallowlabs
+    - github/sauthieg
+    - github/ajermakovics
+    - github/kevinanderson1
+    - github/jpyeron
+}
+
+#
+# 1.1.0
+#
+r14: {
+    title: Gitblit 1.1.0 Released
+    id: 1.1.0
+    date: 2012-08-25
+    note: If you are updating from an earlier release AND you have indexed branches with the Lucene indexing feature, you need to be aware that this release will completely re-index your repositories.  Please be sure to provide ample heap resources as appropriate for your installation.
+
+    fixes:
+    - Bypass Wicket's inability to handle direct url addressing of a view-restricted, grouped repository for new, unauthenticated sessions (e.g. click link from email or rss feed without having an active Wicket session)
+    - Fixed MailExecutor's failure to cope with mail server connection troubles resulting in 100% CPU usage
+    - Fixed generated urls in Groovy *sendmail* hook script for grouped repositories
+    - Fixed generated urls in RSS feeds for grouped repositories
+    - Fixed nullpointer exception in git servlet security filter (issue 123)
+    - Eliminated an unnecessary repository enumeration call on the root page which should result in faster page loads (issue 103)
+    - Gitblit could not delete a Lucene index in a working copy on index upgrade
+    - Do not index submodule links (issue 119)
+    - Restore original user or team object on failure to update (issue 118)
+    - Fixes to relative path determination in repository search algorithm for symlinks (issue 116)
+    - Fix to GitServlet to allow pushing to symlinked repositories (issue 116)
+    - Repository URL now uses `X-Forwarded-Proto` and `X-Forwarded-Port`, if available, for reverse proxy configurations (issue 115)
+    - Output real RAW content, not simulated RAW content (issue 114)
+    - Fixed Lucene charset encoding bug when reindexing a repository (issue 112)
+    - Fixed search box linking to Lucene page for grouped repository on Tomcat (issue 111)
+    - Fixed null pointer in LdapUserSerivce if account has a null email address (issue 110)
+    - Really fixed failure to update a GO setting from the manager (issue 85)
+
+    additions:
+    - Identified repository list is now cached by default to reduce disk io and to improve performance (issue 103)
+    - Preliminary bare repository submodule support
+    - ''
+      *git.submoduleUrlPatterns* is a space-delimited list of regular expressions for extracting a repository name from a submodule url.
+      For example, `git.submoduleUrlPatterns = .*?://github.com/(.*)` would extract *gitblit/gitblit.git* from *git://github.git/gitblit/gitblit.git*
+      **Note:** You may not need this control to work with submodules, but it is there if you do.
+        - If there are no matches from *git.submoduleUrlPatterns* then the repository name is assumed to be whatever comes after the last `/` character *(e.g. gitblit.git)*
+        - Gitblit will try to locate this repository relative to the current repository *(e.g. myfolder/myrepo.git, myfolder/mysubmodule.git)* and then at the root level *(mysubmodule.git)* if that fails.
+        - Submodule references in a working copy will be properly identified as gitlinks, but Gitblit will not traverse into the working copy submodule repository.
+      ''
+    - ''
+      Added a repository setting to control authorization as AUTHENTICATED or NAMED. (issue 117)
+
+      NAMED is the original behavior for authorizing against a list of permitted users or permitted teams.
+      AUTHENTICATED allows restricted access for any authenticated user.  This is a looser authorization control.
+      ''
+    - Added default authorization control setting (AUTHENTICATED or NAMED)
+    - Added setting to control how deep Gitblit will recurse into *git.repositoriesFolder* looking for repositories (issue 103)
+    - Added setting to specify regex exclusions for repositories (issue 103)
+    - Blob page now supports displaying images (issue 6)
+    - Non-image binary files can now be downloaded using the RAW link
+    - Support StartTLS in LdapUserService (issue 122)
+    - Added Korean translation
+
+    changes:
+    - Line breaks inserted for readability in raw Markdown content display in the event of a parsing/transformation error.  An error message is now displayed prepended to the raw content.
+    - Improve UTF-8 reading for Markdown files
+    - Updated Polish translation
+    - Updated Japanese translation
+    - Updated Spanish translation
+    
+    settings:
+    - { name: git.cacheRepositoryList, defaultValue: 'true' }
+    - { name: git.submoduleUrlPatterns, defaultValue: * }
+    - { name: git.searchExclusions, defaultValue: * }
+    - { name: git.searchRecursionDepth, defaultValue: -1 }
+    - { name: git.defaultAuthorizationControl, defaultValue: NAMED }
+
+    contributors:
+	- James Moger
+    - Steffen Gebert
+}
+
+#
+# 1.0.0
+#
+r13: {
+    title: Gitblit 1.0.0 Released
+    id: 1.0.0
+    date: 2012-07-14
+
+    fixes:
+    - Fixed bug in Lucene search where old/stale blobs were never properly deleted during incremental updates.  This resulted in duplicate blob entries in the index.
+    - Fixed intermittent bug in identifying line numbers in Lucene search (issue 105)
+    - Adjust repository identification algorithm to handle the scenario where a repository name collides with a group/folder name (e.g. foo.git and foo/bar.git) (issue 104)
+    - Fixed bug where a repository set as *authenticated push* did not have anonymous clone access (issue 96)
+    - Fixed bug in Basic authentication if passwords had a colon
+    - Fixed bug where the Gitblit Manager could not update a setting that was not referenced in reference.properties (issue 85)
+
+    changes:
+    - ''**Updated Lucene index version which will force a rebuild of ALL your Lucene indexes**
+      Make sure to properly set *web.blobEncodings* before starting Gitblit if you are updating!  (issue 97)''
+    - Changed default layout for web ui from Fixed-Width layout to Responsive layout (issue 101)
+    - ''IUserService interface has changed to better accomodate custom authentication and/or custom authorization<
+      The default `users.conf` now supports persisting display names and email addresses.''
+    - Updated Japanese translation
+
+    additions:
+    - Added setting to allow specification of a robots.txt file (issue 99)
+    - ''Added setting to control Responsive layout or Fixed-Width layout (issue 101)
+      Responsive layout is now the default.  This layout gracefully scales the web ui from a desktop layout to a mobile layout by hiding page components.  It is easy to try, just resize your browser or point your Android/iOS device to the url of your Gitblit install.''
+    - Added setting to control charsets for blob string decoding.  Default encodings are UTF-8, ISO-8859-1, and the server default charset. (issue 97)      
+    - ''Exposed JGit internal configuration settings in gitblit.properties/web.xml (issue 93)
+      Review your `gitblit.properties` or `web.xml` for detailed explanations of these settings.''
+    - Added default access restriction.  Applies to new repositories and repositories that have not been configured with Gitblit. (issue 88)
+    - Added Ivy 2.2.0 dependency which enables Groovy Grapes, a mechanism to resolve and retrieve library dependencies from a Maven 2 repository within a Groovy push hook script
+    - ''Added setting to control Groovy Grape root folder (location where resolved dependencies are stored)
+      [Grape](http://groovy.codehaus.org/Grape) allows you to add Maven dependencies to your pre-/post-receive hook script classpath.''
+    - Added LDAP User Service with many new *realm.ldap* keys
+    - ''Added support for custom repository properties for Groovy hooks
+      Custom repository properties complement hook scripts by providing text field prompts in the web ui and the Gitblit Manager for the defined properties.  This allows your push hooks to be parameterized.''
+    - Added script to facilitate proxy environment setup on Linux
+    - Added Polish translation
+    - Added Spanish translation
+
+    settings:
+    - { name: groovy.grapeFolder, defaultValue: groovy/grape }
+    - { name: web.robots.txt, defaultValue: }
+    - { name: web.useResponsiveLayout, defaultValue: 'true' }
+    - { name: web.blobEncodings, defaultValue: UTF-8 ISO-8859-1 }
+    - { name: git.defaultAccessRestriction, defaultValue: NONE }
+    - { name: git.packedGitWindowSize, defaultValue: 8k }
+    - { name: git.packedGitLimit, defaultValue: 10m }
+    - { name: git.deltaBaseCacheLimit, defaultValue: 10m }
+    - { name: git.packedGitOpenFiles, defaultValue: 128 }
+    - { name: git.streamFileThreshold, defaultValue: 50m }
+    - { name: git.packedGitMmap, defaultValue: 'false' }
+
+    dependencyChanges:
+    - Bootstrap 2.0.4
+    - JGit 2.0.0.201206130900-r
+    - Groovy 1.8.6
+    - Gson 1.7.2
+    - Log4J 1.2.17
+    - SLF4J 1.6.6
+    - Apache Commons Daemon 1.0.10
+    - Ivy 2.2.0
+
+    contributors:
+	- James Moger
+    - Eduardo Guervos Narvaez
+    - Lukasz Jader
+    - github/mragab
+    - github/jcrygier
+    - github/zakki
+    - github/peterloron
+}
+
+#
+# 0.9.3
+#
+r12: {
+    title: Gitblit 0.9.3 Released
+    id: 0.9.3
+    date: 2012-04-11
+
+    fixes:
+    - Fixed bug where you could not remove all selections from a RepositoryModel list (permitted users, permitted teams, hook scripts, federation sets, etc) (issue 81)
+    - Automatically set *java.awt.headless=true* for Gitblit GO
+
+    contributors:
+	- James Moger
+}
+
+#
+# 0.9.2
+#
+r11: {
+    title: Gitblit 0.9.2 Released
+    id: 0.9.2
+    date: 2012-04-04
+    
+    changes:
+    - Added *clientLogger* bound variable to Groovy hook mechanism to allow custom info and error messages to be returned to the client
+
+   fixes:
+    - Fixed absolute path/canonical path discrepancy between Gitblit and JGit regarding use of symlinks (issue 78)
+    - Fixed row layout on activity page (issue 79)
+    - Fixed Centos service script
+    - Fixed EditRepositoryPage for IE8; missing save button (issue 80)
+
+    contributors:
+	- James Moger
+    - github/jonnybbb
+    - github/mohamedmansour
+    - github/jcrygier
+}
+
+#
+# 0.9.1
+#
+r10: {
+    title: Gitblit 0.9.1 Released
+    id: 0.9.1
+    date: 2012-03-27
+
+    fixes:
+    - Lucene folder was stored in working copy instead of in .git folder
+
+    contributors:
+	- James Moger
+}
+
+#
+# 0.9.0
+#
+r9: {
+    title: Gitblit 0.9.0 Released
+    id: 0.9.0
+    date: 2012-03-27
+
+    security:
+    - Fixed session fixation vulnerability where the session identifier was not reset during the login process (issue 62)
+
+    changes:
+    - Reject pushes to a repository with a working copy (i.e. non-bare repository) (issue-49)
+    - Changed default web.datetimestampLongFormat from *EEEE, MMMM d, yyyy h:mm a z* to *EEEE, MMMM d, yyyy HH:mm Z* (issue 50)
+    - Expanded commit age coloring from 2 days to 30 days (issue 57)
+
+    additions:
+    - ''Added optional Lucene branch indexing (issue 16)
+      Repository branches may be optionally indexed by Lucene for improved searching.  To use this feature you must specify which branches to index within the *Edit Repository* page; _no repositories are automatically indexed_.  Gitblit will build or incrementally update enrolled repositories on a 2 minute cycle. (i.e you will have to wait 2-3 minutes after respecifying indexed branches or pushing new commits before Gitblit will build/update the repository Lucene index.)
+      If a repository has Lucene-indexed branches the *search* form on the repository pages will redirect to the root-level Lucene search page and only the content of those branches can be searched.<br/>
+      If the repository does not specify any indexed branches then repository commit-traversal search is used.
+
+      **Note:** Initial indexing of an existing repository can be memory-exhaustive. Be sure to provide your Gitblit server adequate heap space to index your repositories (e.g. -Xmx1024M).<br/>
+      See the [setup](setup.html) page for additional details.''
+    - Allow specifying timezone to use for Gitblit which is independent of both the JVM and the system timezone (issue 54)
+    - Added a built-in AJP connector for integrating Gitblit GO into an Apache mod_proxy setup (issue 59)
+    - ''On the Repositories page show a bang *!* character in the color swatch of a repository with a working copy (issue 49)
+      Push requests to these repositories will be rejected.''
+    - On all non-bare Repository pages show *WORKING COPY* in the upper right corner (issue 49)
+    - New setting to prevent display/serving non-bare repositories
+    - Added *protect-refs.groovy*
+    - Allow setting default branch (relinking HEAD) to a branch or a tag
+    - Added Ubuntu service init script (issue 72)
+    - Added partial Japanese translation
+
+    fixes:
+    - Ensure that Welcome message is parsed using UTF-8 encoding (issue 74)
+    - Activity page chart layout broken by Google (issue 73)
+    - Uppercase repositories not selectable in edit palettes (issue 71)
+    - Not all git notes were properly displayed on the commit page (issue 70)
+    - Activity page now displays all local branches (issue 65)
+    - Fixed (harmless) nullpointer on pushing to an empty repository (issue 69)
+    - Fixed possible nullpointer from the servlet container on startup (issue 67)
+    - Fixed UTF-8 encoding bug on diff page (issue 66)
+    - Fixed timezone bugs on the activity page (issue 54)
+    - Prevent add/edit team with no selected repositories (issue 56)
+    - Disallow browser autocomplete on add/edit user/team/repository pages
+    - Fixed username case-sensitivity issues (issue 43)
+    - Disregard searching a subfolder if Gitblit does not have filesystem permissions (issue 51)
+
+    settings:
+    - { name: web.allowLuceneIndexing, defaultValue: 'true' }
+    - { name: web.luceneIgnoreExtensions, defaultValue: 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 }
+    - { name: web.timezone, defaultValue: }
+    - { name: server.ajpPort, defaultValue: 0 }
+    - { name: server.ajpBindInterface, defaultValue: localhost }
+    - { name: git.onlyAccessBareRepositories, defaultValue: 'false' }
+
+    dependencyChanges:
+    - Bootstrap 2.0.2
+    - MarkdownPapers 1.2.7
+    - JGit 1.3.0.201202151440-r
+    - Wicket 1.4.20
+
+    contributors:
+	- James Moger
+    - github/lemval
+    - github/zakki
+    - github/plm
+}
+
+#
+# 0.8.2
+#
+r8: {
+    title: Gitblit 0.8.2 Released
+    id: 0.8.2
+    date: 2012-01-13
+
+    fixes:
+    - Fixed bug when upgrading from users.properties to users.conf (issue 41)
+
+    contributors:
+	- James Moger
+}
+
+#
+# 0.8.1
+#
+r7: {
+    title: Gitblit 0.8.1 Released
+    id: 0.8.1
+    date: 2012-01-11
+
+    fixes:
+    - Include missing icon resource for the manager (issue 40)
+    - Fixed sendmail.groovy message content with incorrect tag/branch labels
+
+    contributors:
+	- James Moger
+}
+
+#
+# 0.8.0
+#
+r6: {
+    title: Gitblit 0.8.0 Released
+    id: 0.8.0
+    date: 2012-01-11
+
+    additions:
+    - ''Platform-independent, Groovy push hook script mechanism.
+      Hook scripts can be set per-repository, per-team, or globally for all repositories.''
+    - ''*sendmail.groovy* for optional email notifications on push.
+      You must properly configure your SMTP server settings in `gitblit.properties` or `web.xml` to use *sendmail.groovy*.''
+    - New global key for mailing lists.  This is used in conjunction with the *sendmail.groovy* hook script.  All repositories that use the *sendmail.groovy* script will include these addresses in the notification process.  Please see the Setup page for more details about configuring sendmail.
+    - *com.gitblit.GitblitUserService*.  This is a wrapper object for the built-in user service implementations.  For those wanting to only implement custom authentication it is recommended to subclass GitblitUserService and override the appropriate methods.  Going forward, this will help insulate custom authentication from new IUserService API and/or changes in model classes.
+    - ''New default user service implementation: *com.gitblit.ConfigUserService* (`users.conf`)
+      This user service implementation allows for serialization and deserialization of more sophisticated Gitblit User objects without requiring the encoding trickery now present in FileUserService (users.properties).  This will open the door for more advanced Gitblit features.
+      For those upgrading from an earlier Gitblit version, a `users.conf` file will automatically be created for you from your existing `users.properties` file on your first launch of Gitblit <u>however</u> you will have to manually set *realm.userService=users.conf* to switch to the new user service.
+      The original `users.properties` file and the corresponding implementation are **deprecated**.''
+    - Teams for specifying user-repository access in bulk.  Teams may also specify mailing lists addresses and pre- & post- receive hook scripts.
+    - Gravatar integration
+    - Activity page for aggregated repository activity.  This is a timeline of commit activity over the last N days for one or more repositories.
+    - *Filters* menu for the Repositories page and Activity page.  You can filter by federation set, team, and simple custom regular expressions.  Custom expressions can be stored in `gitblit.properties` or `web.xml` or directly defined in your url (issue 27)
+    - Flash-based 1-step *copy to clipboard* of the primary repository url based on Clippy
+    - JavaScript-based 3-step (click, ctrl+c, enter) *copy to clipboard* of the primary repository url in the event that you do not want to use Flash on your installation
+    - Empty repositories now link to an *empty repository* page which gives some direction to the user for the next step in using Gitblit.  This page displays the primary push/clone url of the repository and gives sample syntax for the git command-line client. (issue 31)
+    - Repositories with a *gh-pages* branch will now have a *pages* link which will serve the content of this branch.  All resource requests are against the repository, Gitblit does not checkout/export this branch to a temporary filesystem.  Jekyll templating is not supported.
+    - Gitblit Express bundle to get started running Gitblit on RedHat OpenShift cloud <span class="label label-warning">BETA</span>
+
+    changes:
+    - Dropped display of trailing .git from repository names
+    - ''Gitblit GO is now monolithic like the WAR build. (issue 30)
+      This change helps adoption of GO in environments without an internet connection or with a restricted connection.''
+    - Unit testing framework has been migrated to JUnit4 syntax and the test suite has been redesigned to run all unit tests, including rpc, federation, and git push/clone tests
+
+    fixes:
+    - Several a bugs in FileUserService related to cleaning up old repository permissions on a rename or delete
+    - Renaming a repository into a new subfolder failed (issue 33)
+
+    settings:
+    - { name: groovy.scriptsFolder, defaultValue: groovy }
+    - { name: groovy.preReceiveScripts, defaultValue: }
+    - { name: groovy.postReceiveScripts, defaultValue: }
+    - { name: mail.mailingLists, defaultValue: }
+    - { name: realm.userService, defaultValue: users.conf }
+    - { name: web.allowGravatar, defaultValue: 'true' }
+    - { name: web.activityDuration, defaultValue: 14 }
+    - { name: web.timeFormat, defaultValue: HH:mm }
+    - { name: web.datestampLongFormat, defaultValue: "EEEE, MMMM d, yyyy" }
+    - { name: web.customFilters, defaultValue: }
+    - { name: web.allowFlashCopyToClipboard, defaultValue: 'true' }
+
+    dependencyChanges:
+    - JGit 1.2.0
+    - Groovy 1.8.5
+    - Clippy
+
+    contributors:
+	- James Moger
+}
+
+#
+# 0.7.0
+#
+r5: {
+    title: Gitblit 0.7.0 Released
+    id: 0.7.0
+    date: 2011-11-11
+
+    security:
+    - fixed security hole when cloning clone-restricted repository with TortoiseGit (issue 28)
+
+    fixes:
+    - ''federation protocol timestamps.  dates are now serialized to the [iso8601](http://en.wikipedia.org/wiki/ISO_8601) standard.
+      **This breaks 0.6.0 federation clients/servers.**''
+    - collision on rename for repositories and users
+    - Gitblit can now browse the Linux kernel repository (issue 25)
+    - Gitblit now runs on Servlet 3.0 webservers (e.g. Tomcat 7, Jetty 8) (issue 23)
+    - Set the RSS content type of syndication feeds for Firefox 4 (issue 22)
+    - RSS feeds are now properly encoded to UTF-8
+    - RSS feeds now properly generate parameterized links if *web.mountParameters=false*
+    - Null pointer exception if did not set federation strategy (issue 20)
+    - Gitblit GO allows SSL renegotiation if running on Java 1.6.0_22 or later
+        
+    changes:
+    - updated ui with Twitter Bootstrap CSS toolkit
+    - repositories list performance by caching repository sizes (issue 27)
+    - summary page performance by caching metric calculations (issue 25)
+    
+    additions:
+    - authenticated JSON RPC mechanism
+    - Gitblit API RSS/JSON RPC library
+    - Gitblit Manager (Java/Swing Application) for remote administration of a Gitblit server.
+    - per-repository setting to skip size calculation (faster repositories page loading)
+    - per-repository setting to skip summary metrics calculation (faster summary page loading)
+    - IUserService.setup(IStoredSettings) for custom user service implementations
+    - setting to control Gitblit GO context path for proxy setups
+    - *combined-md5* password storage option which stores the hash of username+password as the password
+    - repository owners are automatically granted access for git, feeds, and zip downloads without explicitly selecting them
+    - RSS feeds now include regex substitutions on commit messages for bug trackers, etc
+    
+    settings:
+    - { name: web.loginMessage, defaultValue: gitblit }
+    - { name: web.enableRpcServlet, defaultValue: 'true' }
+    - { name: web.enableRpcManagement, defaultValue: 'false' }
+    - { name: web.enableRpcAdministration, defaultValue: 'false' }
+    - { name: server.contextPath, defaultValue: / }
+    
+    dependencyChanges:
+    - MarkdownPapers 1.2.5
+    - Wicket 1.4.19
+
+    contributors:
+	- James Moger
+    - github/dadalar
+    - github/alyandon
+    - github/trygvis
+}
+
+#
+# 0.6.0
+#
+r4: {
+    title: Gitblit 0.6.0 Released
+    id: 0.6.0
+    date: 2011-09-27
+
+    fixes:
+    - syndication urls for WAR deployments
+    - authentication for zip downloads
+
+    additions:
+    - federation feature to allow gitblit instances (or gitblit federation clients) to pull repositories and, optionally, settings and accounts from other gitblit instances.  This is something like [svn-sync](http://svnbook.red-bean.com/en/1.5/svn.ref.svnsync.html) for gitblit.
+    - user role *#notfederated* to prevent a user account from being pulled by a federated Gitblit instance
+
+    settings:
+    - { name: federation.name, defaultValue: }
+    - { name: federation.passphrase, defaultValue: }
+    - { name: federation.allowProposals, defaultValue: 'false' }
+    - { name: federation.proposalsFolder, defaultValue: proposals }
+    - { name: federation.defaultFrequency, defaultValue: 60 mins }
+    - { name: federation.sets, defaultValue: }
+    - { name: "mail.*", defaultValue: }
+        
+    dependencyChanges:
+    - MarkdownPapers 1.1.1
+    - Wicket 1.4.18
+    - JGit 1.1.0
+    - google-gson
+    - javamail
+
+    contributors:
+	- James Moger
+}
+
+#
+# 0.5.2
+#
+r3: {
+    title: Gitblit 0.5.2 Released
+    id: 0.5.2
+    date: 2011-07-27
+
+    fixes:
+    - active repositories with a HEAD that pointed to an empty branch caused internal errors (issue 14)
+    - bare-cloned repositories were listed as (empty) and were not clickable (issue 13)
+    - default port for Gitblit GO is now 8443 to be more linux/os x friendly (issue 12)
+    - repositories can now be reliably deleted and renamed (issue 10)
+    - users can now change their passwords (issue 1)
+    - always show root repository group first, i.e. do not sort root group with other groups
+    - tone-down repository group header color
+    
+    additions:
+    - optionally display repository on-disk size on repositories page
+    - forward-slashes ('/', %2F) can be encoded using a custom character to workaround some servlet container default security measures for proxy servers
+    
+    settings:
+    - { name: web.showRepositorySizes, defaultValue: 'true' }
+    - { name: web.forwardSlashCharacter, defaultValue: / }
+    
+    dependencyChanges:
+    - MarkdownPapers 1.1.0
+    - Jetty 7.4.3
+
+    contributors:
+	- James Moger
+}
+
+#
+# 0.5.1
+#
+r2: {
+    title: Gitblit 0.5.1 Released
+    id: 0.5.1
+    date: 2011-06-28
+
+    changes:
+    - clarified SSL certificate generation and configuration for both server-side and client-side
+    - added some more troubleshooting information to documentation
+    - replaced JavaService with Apache Commons Daemon
+
+    contributors:
+	- James Moger
+}
+
+#
+# 0.5.0
+#
+r1: {
+    title: Gitblit 0.5.0 Released
+    id: 0.5.0
+    date: 2011-06-26
+    text: initial release
+
+    contributors:
+	- James Moger
+}
+
+snapshot: &r18
+release: &r17
+releases: &r[1..17]
diff --git a/resources/arrow_page.png b/resources/arrow_page.png
deleted file mode 100644
index 6d93024..0000000
--- a/resources/arrow_page.png
+++ /dev/null
Binary files differ
diff --git a/resources/bootstrap/css/bootstrap-responsive.css b/resources/bootstrap/css/bootstrap-responsive.css
deleted file mode 100644
index 06e55c0..0000000
--- a/resources/bootstrap/css/bootstrap-responsive.css
+++ /dev/null
@@ -1,815 +0,0 @@
-/*!
- * Bootstrap Responsive v2.0.4
- *
- * Copyright 2012 Twitter, Inc
- * Licensed under the Apache License v2.0
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Designed and built with all the love in the world @twitter by @mdo and @fat.
- */
-
-.clearfix {
-  *zoom: 1;
-}
-
-.clearfix:before,
-.clearfix:after {
-  display: table;
-  content: "";
-}
-
-.clearfix:after {
-  clear: both;
-}
-
-.hide-text {
-  font: 0/0 a;
-  color: transparent;
-  text-shadow: none;
-  background-color: transparent;
-  border: 0;
-}
-
-.input-block-level {
-  display: block;
-  width: 100%;
-  min-height: 28px;
-  -webkit-box-sizing: border-box;
-     -moz-box-sizing: border-box;
-      -ms-box-sizing: border-box;
-          box-sizing: border-box;
-}
-
-.hidden {
-  display: none;
-  visibility: hidden;
-}
-
-.visible-phone {
-  display: none !important;
-}
-
-.visible-tablet {
-  display: none !important;
-}
-
-.hidden-desktop {
-  display: none !important;
-}
-
-@media (max-width: 767px) {
-  .visible-phone {
-    display: inherit !important;
-  }
-  .hidden-phone {
-    display: none !important;
-  }
-  .hidden-desktop {
-    display: inherit !important;
-  }
-  .visible-desktop {
-    display: none !important;
-  }
-}
-
-@media (min-width: 768px) and (max-width: 979px) {
-  .visible-tablet {
-    display: inherit !important;
-  }
-  .hidden-tablet {
-    display: none !important;
-  }
-  .hidden-desktop {
-    display: inherit !important;
-  }
-  .visible-desktop {
-    display: none !important ;
-  }
-}
-
-@media (max-width: 480px) {
-  .nav-collapse {
-    -webkit-transform: translate3d(0, 0, 0);
-  }
-  .page-header h1 small {
-    display: block;
-    line-height: 18px;
-  }
-  input[type="checkbox"],
-  input[type="radio"] {
-    border: 1px solid #ccc;
-  }
-  .form-horizontal .control-group > label {
-    float: none;
-    width: auto;
-    padding-top: 0;
-    text-align: left;
-  }
-  .form-horizontal .controls {
-    margin-left: 0;
-  }
-  .form-horizontal .control-list {
-    padding-top: 0;
-  }
-  .form-horizontal .form-actions {
-    padding-right: 10px;
-    padding-left: 10px;
-  }
-  .modal {
-    position: absolute;
-    top: 10px;
-    right: 10px;
-    left: 10px;
-    width: auto;
-    margin: 0;
-  }
-  .modal.fade.in {
-    top: auto;
-  }
-  .modal-header .close {
-    padding: 10px;
-    margin: -10px;
-  }
-  .carousel-caption {
-    position: static;
-  }
-}
-
-@media (max-width: 767px) {
-  body {
-    padding-right: 20px;
-    padding-left: 20px;
-  }
-  .navbar-fixed-top,
-  .navbar-fixed-bottom {
-    margin-right: -20px;
-    margin-left: -20px;
-  }
-  .container-fluid {
-    padding: 0;
-  }
-  .dl-horizontal dt {
-    float: none;
-    width: auto;
-    clear: none;
-    text-align: left;
-  }
-  .dl-horizontal dd {
-    margin-left: 0;
-  }
-  .container {
-    width: auto;
-  }
-  .row-fluid {
-    width: 100%;
-  }
-  .row,
-  .thumbnails {
-    margin-left: 0;
-  }
-  [class*="span"],
-  .row-fluid [class*="span"] {
-    display: block;
-    float: none;
-    width: auto;
-    margin-left: 0;
-  }
-  .input-large,
-  .input-xlarge,
-  .input-xxlarge,
-  input[class*="span"],
-  select[class*="span"],
-  textarea[class*="span"],
-  .uneditable-input {
-    display: block;
-    width: 100%;
-    min-height: 28px;
-    -webkit-box-sizing: border-box;
-       -moz-box-sizing: border-box;
-        -ms-box-sizing: border-box;
-            box-sizing: border-box;
-  }
-  .input-prepend input,
-  .input-append input,
-  .input-prepend input[class*="span"],
-  .input-append input[class*="span"] {
-    display: inline-block;
-    width: auto;
-  }
-}
-
-@media (min-width: 768px) and (max-width: 979px) {
-  .row {
-    margin-left: -20px;
-    *zoom: 1;
-  }
-  .row:before,
-  .row:after {
-    display: table;
-    content: "";
-  }
-  .row:after {
-    clear: both;
-  }
-  [class*="span"] {
-    float: left;
-    margin-left: 20px;
-  }
-  .container,
-  .navbar-fixed-top .container,
-  .navbar-fixed-bottom .container {
-    width: 724px;
-  }
-  .span12 {
-    width: 724px;
-  }
-  .span11 {
-    width: 662px;
-  }
-  .span10 {
-    width: 600px;
-  }
-  .span9 {
-    width: 538px;
-  }
-  .span8 {
-    width: 476px;
-  }
-  .span7 {
-    width: 414px;
-  }
-  .span6 {
-    width: 352px;
-  }
-  .span5 {
-    width: 290px;
-  }
-  .span4 {
-    width: 228px;
-  }
-  .span3 {
-    width: 166px;
-  }
-  .span2 {
-    width: 104px;
-  }
-  .span1 {
-    width: 42px;
-  }
-  .offset12 {
-    margin-left: 764px;
-  }
-  .offset11 {
-    margin-left: 702px;
-  }
-  .offset10 {
-    margin-left: 640px;
-  }
-  .offset9 {
-    margin-left: 578px;
-  }
-  .offset8 {
-    margin-left: 516px;
-  }
-  .offset7 {
-    margin-left: 454px;
-  }
-  .offset6 {
-    margin-left: 392px;
-  }
-  .offset5 {
-    margin-left: 330px;
-  }
-  .offset4 {
-    margin-left: 268px;
-  }
-  .offset3 {
-    margin-left: 206px;
-  }
-  .offset2 {
-    margin-left: 144px;
-  }
-  .offset1 {
-    margin-left: 82px;
-  }
-  .row-fluid {
-    width: 100%;
-    *zoom: 1;
-  }
-  .row-fluid:before,
-  .row-fluid:after {
-    display: table;
-    content: "";
-  }
-  .row-fluid:after {
-    clear: both;
-  }
-  .row-fluid [class*="span"] {
-    display: block;
-    float: left;
-    width: 100%;
-    min-height: 28px;
-    margin-left: 2.762430939%;
-    *margin-left: 2.709239449638298%;
-    -webkit-box-sizing: border-box;
-       -moz-box-sizing: border-box;
-        -ms-box-sizing: border-box;
-            box-sizing: border-box;
-  }
-  .row-fluid [class*="span"]:first-child {
-    margin-left: 0;
-  }
-  .row-fluid .span12 {
-    width: 99.999999993%;
-    *width: 99.9468085036383%;
-  }
-  .row-fluid .span11 {
-    width: 91.436464082%;
-    *width: 91.38327259263829%;
-  }
-  .row-fluid .span10 {
-    width: 82.87292817100001%;
-    *width: 82.8197366816383%;
-  }
-  .row-fluid .span9 {
-    width: 74.30939226%;
-    *width: 74.25620077063829%;
-  }
-  .row-fluid .span8 {
-    width: 65.74585634900001%;
-    *width: 65.6926648596383%;
-  }
-  .row-fluid .span7 {
-    width: 57.182320438000005%;
-    *width: 57.129128948638304%;
-  }
-  .row-fluid .span6 {
-    width: 48.618784527%;
-    *width: 48.5655930376383%;
-  }
-  .row-fluid .span5 {
-    width: 40.055248616%;
-    *width: 40.0020571266383%;
-  }
-  .row-fluid .span4 {
-    width: 31.491712705%;
-    *width: 31.4385212156383%;
-  }
-  .row-fluid .span3 {
-    width: 22.928176794%;
-    *width: 22.874985304638297%;
-  }
-  .row-fluid .span2 {
-    width: 14.364640883%;
-    *width: 14.311449393638298%;
-  }
-  .row-fluid .span1 {
-    width: 5.801104972%;
-    *width: 5.747913482638298%;
-  }
-  input,
-  textarea,
-  .uneditable-input {
-    margin-left: 0;
-  }
-  input.span12,
-  textarea.span12,
-  .uneditable-input.span12 {
-    width: 714px;
-  }
-  input.span11,
-  textarea.span11,
-  .uneditable-input.span11 {
-    width: 652px;
-  }
-  input.span10,
-  textarea.span10,
-  .uneditable-input.span10 {
-    width: 590px;
-  }
-  input.span9,
-  textarea.span9,
-  .uneditable-input.span9 {
-    width: 528px;
-  }
-  input.span8,
-  textarea.span8,
-  .uneditable-input.span8 {
-    width: 466px;
-  }
-  input.span7,
-  textarea.span7,
-  .uneditable-input.span7 {
-    width: 404px;
-  }
-  input.span6,
-  textarea.span6,
-  .uneditable-input.span6 {
-    width: 342px;
-  }
-  input.span5,
-  textarea.span5,
-  .uneditable-input.span5 {
-    width: 280px;
-  }
-  input.span4,
-  textarea.span4,
-  .uneditable-input.span4 {
-    width: 218px;
-  }
-  input.span3,
-  textarea.span3,
-  .uneditable-input.span3 {
-    width: 156px;
-  }
-  input.span2,
-  textarea.span2,
-  .uneditable-input.span2 {
-    width: 94px;
-  }
-  input.span1,
-  textarea.span1,
-  .uneditable-input.span1 {
-    width: 32px;
-  }
-}
-
-@media (min-width: 1200px) {
-  .row {
-    margin-left: -30px;
-    *zoom: 1;
-  }
-  .row:before,
-  .row:after {
-    display: table;
-    content: "";
-  }
-  .row:after {
-    clear: both;
-  }
-  [class*="span"] {
-    float: left;
-    margin-left: 30px;
-  }
-  .container,
-  .navbar-fixed-top .container,
-  .navbar-fixed-bottom .container {
-    width: 1170px;
-  }
-  .span12 {
-    width: 1170px;
-  }
-  .span11 {
-    width: 1070px;
-  }
-  .span10 {
-    width: 970px;
-  }
-  .span9 {
-    width: 870px;
-  }
-  .span8 {
-    width: 770px;
-  }
-  .span7 {
-    width: 670px;
-  }
-  .span6 {
-    width: 570px;
-  }
-  .span5 {
-    width: 470px;
-  }
-  .span4 {
-    width: 370px;
-  }
-  .span3 {
-    width: 270px;
-  }
-  .span2 {
-    width: 170px;
-  }
-  .span1 {
-    width: 70px;
-  }
-  .offset12 {
-    margin-left: 1230px;
-  }
-  .offset11 {
-    margin-left: 1130px;
-  }
-  .offset10 {
-    margin-left: 1030px;
-  }
-  .offset9 {
-    margin-left: 930px;
-  }
-  .offset8 {
-    margin-left: 830px;
-  }
-  .offset7 {
-    margin-left: 730px;
-  }
-  .offset6 {
-    margin-left: 630px;
-  }
-  .offset5 {
-    margin-left: 530px;
-  }
-  .offset4 {
-    margin-left: 430px;
-  }
-  .offset3 {
-    margin-left: 330px;
-  }
-  .offset2 {
-    margin-left: 230px;
-  }
-  .offset1 {
-    margin-left: 130px;
-  }
-  .row-fluid {
-    width: 100%;
-    *zoom: 1;
-  }
-  .row-fluid:before,
-  .row-fluid:after {
-    display: table;
-    content: "";
-  }
-  .row-fluid:after {
-    clear: both;
-  }
-  .row-fluid [class*="span"] {
-    display: block;
-    float: left;
-    width: 100%;
-    min-height: 28px;
-    margin-left: 2.564102564%;
-    *margin-left: 2.510911074638298%;
-    -webkit-box-sizing: border-box;
-       -moz-box-sizing: border-box;
-        -ms-box-sizing: border-box;
-            box-sizing: border-box;
-  }
-  .row-fluid [class*="span"]:first-child {
-    margin-left: 0;
-  }
-  .row-fluid .span12 {
-    width: 100%;
-    *width: 99.94680851063829%;
-  }
-  .row-fluid .span11 {
-    width: 91.45299145300001%;
-    *width: 91.3997999636383%;
-  }
-  .row-fluid .span10 {
-    width: 82.905982906%;
-    *width: 82.8527914166383%;
-  }
-  .row-fluid .span9 {
-    width: 74.358974359%;
-    *width: 74.30578286963829%;
-  }
-  .row-fluid .span8 {
-    width: 65.81196581200001%;
-    *width: 65.7587743226383%;
-  }
-  .row-fluid .span7 {
-    width: 57.264957265%;
-    *width: 57.2117657756383%;
-  }
-  .row-fluid .span6 {
-    width: 48.717948718%;
-    *width: 48.6647572286383%;
-  }
-  .row-fluid .span5 {
-    width: 40.170940171000005%;
-    *width: 40.117748681638304%;
-  }
-  .row-fluid .span4 {
-    width: 31.623931624%;
-    *width: 31.5707401346383%;
-  }
-  .row-fluid .span3 {
-    width: 23.076923077%;
-    *width: 23.0237315876383%;
-  }
-  .row-fluid .span2 {
-    width: 14.529914530000001%;
-    *width: 14.4767230406383%;
-  }
-  .row-fluid .span1 {
-    width: 5.982905983%;
-    *width: 5.929714493638298%;
-  }
-  input,
-  textarea,
-  .uneditable-input {
-    margin-left: 0;
-  }
-  input.span12,
-  textarea.span12,
-  .uneditable-input.span12 {
-    width: 1160px;
-  }
-  input.span11,
-  textarea.span11,
-  .uneditable-input.span11 {
-    width: 1060px;
-  }
-  input.span10,
-  textarea.span10,
-  .uneditable-input.span10 {
-    width: 960px;
-  }
-  input.span9,
-  textarea.span9,
-  .uneditable-input.span9 {
-    width: 860px;
-  }
-  input.span8,
-  textarea.span8,
-  .uneditable-input.span8 {
-    width: 760px;
-  }
-  input.span7,
-  textarea.span7,
-  .uneditable-input.span7 {
-    width: 660px;
-  }
-  input.span6,
-  textarea.span6,
-  .uneditable-input.span6 {
-    width: 560px;
-  }
-  input.span5,
-  textarea.span5,
-  .uneditable-input.span5 {
-    width: 460px;
-  }
-  input.span4,
-  textarea.span4,
-  .uneditable-input.span4 {
-    width: 360px;
-  }
-  input.span3,
-  textarea.span3,
-  .uneditable-input.span3 {
-    width: 260px;
-  }
-  input.span2,
-  textarea.span2,
-  .uneditable-input.span2 {
-    width: 160px;
-  }
-  input.span1,
-  textarea.span1,
-  .uneditable-input.span1 {
-    width: 60px;
-  }
-  .thumbnails {
-    margin-left: -30px;
-  }
-  .thumbnails > li {
-    margin-left: 30px;
-  }
-  .row-fluid .thumbnails {
-    margin-left: 0;
-  }
-}
-
-@media (max-width: 979px) {
-  body {
-    padding-top: 0;
-  }
-  .navbar-fixed-top,
-  .navbar-fixed-bottom {
-    position: static;
-  }
-  .navbar-fixed-top {
-    margin-bottom: 18px;
-  }
-  .navbar-fixed-bottom {
-    margin-top: 18px;
-  }
-  .navbar-fixed-top .navbar-inner,
-  .navbar-fixed-bottom .navbar-inner {
-    padding: 5px;
-  }
-  .navbar .container {
-    width: auto;
-    padding: 0;
-  }
-  .navbar .brand {
-    padding-right: 10px;
-    padding-left: 10px;
-    margin: 0 0 0 -5px;
-  }
-  .nav-collapse {
-    clear: both;
-  }
-  .nav-collapse .nav {
-    float: none;
-    margin: 0 0 9px;
-  }
-  .nav-collapse .nav > li {
-    float: none;
-  }
-  .nav-collapse .nav > li > a {
-    margin-bottom: 2px;
-  }
-  .nav-collapse .nav > .divider-vertical {
-    display: none;
-  }
-  .nav-collapse .nav .nav-header {
-    color: #999999;
-    text-shadow: none;
-  }
-  .nav-collapse .nav > li > a,
-  .nav-collapse .dropdown-menu a {
-    padding: 6px 15px;
-    font-weight: bold;
-    color: #999999;
-    -webkit-border-radius: 3px;
-       -moz-border-radius: 3px;
-            border-radius: 3px;
-  }
-  .nav-collapse .btn {
-    padding: 4px 10px 4px;
-    font-weight: normal;
-    -webkit-border-radius: 4px;
-       -moz-border-radius: 4px;
-            border-radius: 4px;
-  }
-  .nav-collapse .dropdown-menu li + li a {
-    margin-bottom: 2px;
-  }
-  .nav-collapse .nav > li > a:hover,
-  .nav-collapse .dropdown-menu a:hover {
-    background-color: #222222;
-  }
-  .nav-collapse.in .btn-group {
-    padding: 0;
-    margin-top: 5px;
-  }
-  .nav-collapse .dropdown-menu {
-    position: static;
-    top: auto;
-    left: auto;
-    display: block;
-    float: none;
-    max-width: none;
-    padding: 0;
-    margin: 0 15px;
-    background-color: transparent;
-    border: none;
-    -webkit-border-radius: 0;
-       -moz-border-radius: 0;
-            border-radius: 0;
-    -webkit-box-shadow: none;
-       -moz-box-shadow: none;
-            box-shadow: none;
-  }
-  .nav-collapse .dropdown-menu:before,
-  .nav-collapse .dropdown-menu:after {
-    display: none;
-  }
-  .nav-collapse .dropdown-menu .divider {
-    display: none;
-  }
-  .nav-collapse .navbar-form,
-  .nav-collapse .navbar-search {
-    float: none;
-    padding: 9px 15px;
-    margin: 9px 0;
-    border-top: 1px solid #222222;
-    border-bottom: 1px solid #222222;
-    -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);
-       -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);
-            box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);
-  }
-  .navbar .nav-collapse .nav.pull-right {
-    float: none;
-    margin-left: 0;
-  }
-  .nav-collapse,
-  .nav-collapse.collapse {
-    height: 0;
-    overflow: hidden;
-  }
-  .navbar .btn-navbar {
-    display: block;
-  }
-  .navbar-static .navbar-inner {
-    padding-right: 10px;
-    padding-left: 10px;
-  }
-}
-
-@media (min-width: 980px) {
-  .nav-collapse.collapse {
-    height: auto !important;
-    overflow: visible !important;
-  }
-}
diff --git a/resources/gitblit.css b/resources/gitblit.css
deleted file mode 100644
index 811b08a..0000000
--- a/resources/gitblit.css
+++ /dev/null
@@ -1,1157 +0,0 @@
-body {
-	 /* 50px to start the container 10px below the navbar */
-	padding-top: 60px;
-}
-
-footer {
-	margin-top: 25px;
-	padding: 15px 0 16px;
-	border-top: 1px solid #E5E5E5;
-}
-
-body, input, select {
-	color: #202020;
-}
-
-ul, ol {
-	margin-bottom: 10px !important;
-}
-a:focus {
-	outline: none;
-}
-
-[class^="icon-"], [class*=" icon-"] a i {
-	/* override for a links that look like bootstrap buttons */
-	vertical-align: text-bottom;
-}
-
-hr {
-	margin-top: 10px;
-	margin-bottom: 10px;
-}
-
-.settings th {
-	vertical-align: top;
-}
-
-.pageTitle {
-	padding-bottom: 5px;
-	margin: 0;
-	border-bottom: 1px solid #eee;
-}
-
-.pageTitle h1, .pageTitle h2 {
-	color: #0069D6;
-}
-
-.navbar .brand {
-	padding: 10px 20px;
-}
-
-.navbar .pull-right {
-	margin: 0;
-}
-
-.navbar ul.nav {
-	margin: 0 !important;
-	padding: 4px 0px 0px 0px;
-}
-
-.navbar ul.nav li a {
-  	color: white; 
-	text-shadow: none;
-	outline: 0;
-}
-
-.navbar ul.nav li a:hover {
-	color: #abd4ff !important;
-	text-decoration: underline;
-}
-
-.navbar .nav .active > a:hover {
-	text-decoration: underline;
-}
-
-.navbar-inner {
-	background-color:#000050;
-	background-repeat:repeat-x;
-	background-image:-khtml-gradient(linear, left top, left bottom, from(#000060), to(#000040));
-	background-image:-moz-linear-gradient(top, #000060, #000040);
-	background-image:-ms-linear-gradient(top, #000060, #000040);
-	background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #000060), color-stop(100%, #000040));
-	background-image:-webkit-linear-gradient(top, #000060, #000040);
-	background-image:-o-linear-gradient(top, #000060, #000040);
-	background-image:linear-gradient(top, #000060, #000040);
-	filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#000060', endColorstr='#000040', GradientType=0);
-	-webkit-box-shadow:0 1px 3px rgba(0, 0, 0, 0.25),inset 0 -1px 0 rgba(0, 0, 0, 0.1);
-	-moz-box-shadow:0 1px 3px rgba(0, 0, 0, 0.25),inset 0 -1px 0 rgba(0, 0, 0, 0.1);
-	box-shadow:0 1px 3px rgba(0, 0, 0, 0.25),inset 0 -1px 0 rgba(0, 0, 0, 0.1);
-	border-bottom: 2px solid #ff9900 !important;
-}
-
-.navbar ul li:focus, .navbar .active {
-	background-repeat:no-repeat;
-	background-image: url(arrow_page.png);
-	background-position: center bottom;
-	outline: 0;
-	padding-bottom:3px;
-}
-
-.navbar .active a {
-	background-color: transparent !important;
-	outline: 0;
-}
-
-.navbar div > ul .menu-dropdown .selected, .nav .menu-dropdown .selected, .navbar div > ul .dropdown-menu .selected, .nav .dropdown-menu .selected {	
-	background-image: url("bullet_blue.png");
-	background-repeat: no-repeat;
-	background-position: left;	
-}
-
-.navbar div>ul .dropdown-menu li a {
-	color: #555;
-}
-
-navbar div>ul .menu-dropdown li a:hover,.nav .menu-dropdown li a:hover,.navbar div>ul .dropdown-menu li a:hover,.nav .dropdown-menu li a:hover{
-	background-color: #000070;
-	color: #ffffff !important;
-}
-
-.breadcrumb {
-	margin-top: 5px !important;
-	margin-bottom: 5px !important;
-}
-
-.pageTitle {	
-	margin-bottom: 5px;	
-}
-
-.pageTitle h2 small {
-	font-size: 80%;
-	font-weight: bold;
-}
-
-.pageTitle {
-	color: #888;
-	font-size: 18px;
-	line-height: 27px;
-}
-.pageTitle .project, .pageTitle .repository {
-	font-family: Helvetica, arial, freesans, clean, sans-serif;
-	font-size: 22px;
-}
-
-.pageTitle .controls {
-	font-size: 12px;
-}
-
-.pageTitle .repository {
-	font-weight: bold;
-}
-
-.originRepository {
-	font-family: Helvetica, arial, freesans, clean, sans-serif;
-	color: #888;
-	font-size: 12px;
-	line-height: 14px;
-	margin: 0px;
-}
-
-.forkSource, .forkEntry {
-	color: #888;
-}
-
-.forkSource {
-	font-size: 18px;
-	line-height: 20px;
-	padding: 5px 0px;
-}
-
-.forkEntry {
-	font-size: 14px;
-	padding: 2px 0px;
-}
-
-.forkSource .forks, .forkEntry .forks {
-	font-size: 10px;
-	padding-left: 5px;
-	text-decoration: underline;
-	vertical-align: middle;
-}
-
-div.odd {
-	
-}
-
-div.even {
-	background-color: whiteSmoke;
-	vertical-align: middle;
-}
-
-span.authorizationControl label {
-	display: inline;
-	color: #777;
-	padding:5px 0px 5px 10px;	
-}
-
-div.page_footer {
-	clear: both;
-	height: 17px;
-	color: black;
-	background-color: #ffffff;
-	padding: 5px;
-	border-top: 1px solid #bbb;
-	font-style: italic;
-}
-
-pre, code, pre.prettyprint, pre.plainprint {
-	background-color: #ffffff;
-	color: black;
-	font-family: monospace;
-	font-size:12px;
-	border:0px;
-	padding: 0;
-	line-height: 1.35em;
-	vertical-align:top;
-}
-
-table {
-	margin-bottom: 5px;
-	font-size: inherit;
-}
-
-.table th {
-	vertical-align: top;
-}
-
-th {
-	vertical-align: middle;
-	text-align: left;	
-}
-
-div.sourceview {
-	overflow: hidden;
-}
-
-pre.prettyprint ol {
-	padding-left:25px;
-}
-
-#nums {
-    text-align: right;
-    padding-right:10px;
-    border-right:1px solid #ddd;
-    font-family: monospace;
-    line-height: 1.35em;
-    vertical-align:top;
-}
-
-#nums pre {
-    white-space: pre;
-}
-
-#nums pre, #lines pre {
-	margin: 0;	
-}
-
-#lines pre {
-	padding: 0px !important;	
-	border: 0px !important;
-	white-space: nowrap;
-}
-
-/* CSS trick to workaround #link topOfWindow offset problem */
-#nums .num {
-    border-top: 160px solid transparent;
-    margin-top: -160px;
-    -webkit-background-clip: padding-box;
-    -moz-background-clip: padding;
-    background-clip: padding-box;
-
-    color: #888;
-}
-
-#nums span:target {
-	background-color: #ffffbf;
-	color: black;
-	font-weight: bold;
-	border-bottom: 1px solid red;
-}
-
-#lines table {
-	margin: 0;
-}
-
-#lines td {
-	padding: 0;
-}
-
-#lines a {
-	padding-left: 5px;
-}
-
-#lines a:hover {
-	background-color: #ffffbf;
-	text-decoration: none;
-}
-
-#lines tr:hover {
-	background-color: #ffffbf;
-}
-#lines .odd {
-	background-color: white;
-}
-
-#lines .even {
-	background-color: #fafafa;
-}
-
-
-
-h1 small, h2 small, h3 small, h4 small, h5 small, h6 small {
-    color: #888;
-}
-
-.age0, .age1, .age2, .age3, .age4 {	
-	font-size: 12px;
-}
-
-/* age0: age < 2 hours */
-.age0 {
-	font-style: italic;
-	color: #008000;
-	font-weight: bold;
-}
-
-/* age1: 2 hours <= age < 2 days */
-.age1 {
-	font-style: italic;
-	color: #0000ff;
-	font-weight: bold;	
-}
-
-/* age2: 2 days < age <= 7 days */
-.age2 {
-	font-style: italic;
-	color: #2b60de;
-}
-
-/* age3: 7 days < age <= 30 days */
-.age3 {
-	color: #800080;
-}
-
-/* age4: > 30 days */
-.age4 {
-}
-
-/* Ensure that hovered ages are white */
-tr.light:hover .age0,
-tr.light:hover .age1,
-tr.light:hover .age2,
-tr.light:hover .age3,
-tr.light:hover .age4,
-tr.dark:hover .age0,
-tr.dark:hover .age1,
-tr.dark:hover .age2,
-tr.dark:hover .age3,
-tr.dark:hover .age4 {
-	color: #ffffff !important;
-}
-
-a.list {
-	text-decoration: none;
-	color: inherit;
-}
-
-a.list.subject {
-	font-weight: bold;
-}
-
-a.list.name {
-	font-weight: bold;	
-}
-
-a.list:hover {
-	text-decoration: underline;
-	color: #880000;
-}
-
-span.empty {
-	font-size: 0.9em;
-	font-style: italic;
-	padding-left:10px;
-	color: #008000;
-}
-
-span.link {
-	color: #888;
-}
-
-span.link, span.link a {
-	font-family: sans-serif;
-	font-size: 11px;
-}
-
-span.link em, div.link span em {
-	font-style: normal;
-	font-family: sans-serif;
-	font-size: 11px;	
-}
-
-span.repositorySwatch {
-	border-radius: 3px;	
-	padding: 1px 4px 2px 4px;	
-	color: #ffffff;
-	vertical-align: center;
-}
-span.repositorySwatch a {
-	color: inherit;
-}
-
-img.inlineIcon {
-	padding-left: 1px;
-	padding-right: 1px;
-}
-
-img.overview {
-	float:right;
-	border:1px solid #CCCCCC;
-}
-
-img.gravatar {
-    background-color: #ffffff;
-    border: 1px solid #ddd;
-    border-radius: 5px;
-    padding: 2px;
-}
-
-div.searchResult {
-	padding: 10px 5px 10px 5px;
-}
-
-div.searchResult .summary {
-	font-weight: bold;
-}
-
-div.searchResult .branch {
-	color: #008000;
-}
-
-div.searchResult .author {
-	font-style: italic !important;
-}
-
-div.searchResult .date {
-	color:#999;
-}
-
-div.searchResult .body {
-	padding-left:20px;
-}
-
-div.searchResult .fragment {
-	padding: 7px 0;
-}
-
-div.searchResult .highlight {
-	background-color: #ccff66;
-	padding: 0 2px;
-}
-
-div.searchResult .ellipses {	
-	padding-left:25px;
-	color: #aaa;
-}
-
-div.searchResult pre {
-	margin: 1px 0px;
-	border: 0px;
-}
-
-div.searchResult .text {
-	border-left: 2px solid #ccc;
-	border-radius: 0px;
-	
-    padding: 0 0 0 15px;
-}
-
-div.searchResult ol {	
-	margin-bottom: 0px !important;
-}
-
-div.header, div.commitHeader, table.repositories th {
-	background-color:#e0e0e0;
-	background-repeat:repeat-x;
-	background-image:-khtml-gradient(linear, left top, left bottom, from(#ffffff), to(#e0e0e0));
-	background-image:-moz-linear-gradient(top, #ffffff, #e0e0e0);
-	background-image:-ms-linear-gradient(top, #ffffff, #e0e0e0);
-	background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #ffffff), color-stop(100%, #e0e0e0));
-	background-image:-webkit-linear-gradient(top, #ffffff, #e0e0e0);
-	background-image:-o-linear-gradient(top, #ffffff, #e0e0e0);
-	background-image:linear-gradient(top, #ffffff, #e0e0e0);
-	filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#e0e0e0', GradientType=0);
-	-webkit-box-shadow:inset 0 1px 0 #ffffff;
-	-moz-box-shadow:inset 0 1px 0 #ffffff;
-	box-shadow:inset 0 1px 0 #ffffff;	
-}
-
-div.header {
-	padding: 3px;
-	border: 1px solid #ddd;
-	border-bottom: 0;
-	border-radius: 3px 3px 0 0;
-	font-weight: bold;
-}
-
-div.commitHeader {
-	margin:0 0 2px;
-	padding:7px 14px;	
-	border:1px solid #ddd;
-	border-radius: 3px;
-	-webkit-border-radius:3px;
-	-moz-border-radius:3px;border-radius:3px;
-}
-
-div.header a, div.commitHeader a {
-	color: black;
-	text-decoration: none;
-	font-weight: bold;
-}
-
-div.header a:hover, div.commitHeader a:hover {
-	text-decoration: underline;
-}
-
-div.page_nav2 {
-	padding: 2px 5px 7px 5px;	
-}
-
-div.admin_nav {
-	border-bottom: 0px;
-	text-align: right;
-	padding: 5px 5px 5px 2px;	
-}
-
-div.admin_nav a {
-	text-decoration: none;
-}
-
-div.admin_nav a:hover {	
-	text-decoration: underline;
-}
-
-span.search {
-	height: 40px;
-	padding-top:2px;
-}
-
-span.search input {
-	-webkit-border-radius:0;-moz-border-radius:0x;border-radius:0;
-	vertical-align: top;
-	background: url(search-icon.png) no-repeat 4px center;
-	background-color: transparent;
-	border: 1px solid transparent;
-	outline: none;
-	padding: 2px 2px 2px 22px;
-	text-shadow: none;
-	margin: 0px;
-	
-	color: #ddd;
-}
-
-span.search input:hover, span.search input:focus {
-	background-color: transparent;
-	border: 1px solid transparent;
-	padding: 2px 2px 2px 22px;
-	box-shadow: none;
-	color: #ddd;
-	border-bottom: 1px solid #ff9900;	
-}
-
-span.search input:focus {
-	color: white;
-}
-
-/* div.search input:focused { */
-/* 	background-color: transparent; */
-/* 	border: 1px solid transparent; */
-/* 	padding: 2px 2px 2px 22px; */
-/* 	text-shadow: none; */
-/* } */
-
-span.login input:focus {
-	background-color: rgba(255, 255, 255, 0.6);	
-	text-shadow: none;
-	color: white;
-}
-
-.commit_message {
-	padding: 8px;
-	border: solid #ddd;
-	border-width: 1px 0px 0px;
-	border-radius: 0px;
-}
-
-div.bug_open, span.bug_open {
-	padding: 2px;
-	background-color: #803333;
-	color: white;	
-	text-align: center;
-}
-
-div.bug_resolved, span.bug_resolved {
-	padding: 2px;
-	background-color: #408040;
-	color: white;
-	text-align: center;
-}
-
-div.bug_invalid, span.bug_invalid {
-	padding: 2px;
-	background-color: gray;
-	text-align: center;
-}
-
-div.bug_hold, span.bug_hold {
-	padding: 2px;
-	background-color: orange;
-	text-align: center;
-}
-
-div.diff {
-	font-family: monospace;
-	overflow: auto;
-}
-
-div.diff.header {
-	-moz-border-bottom-colors: none;
-    -moz-border-image: none;
-    -moz-border-left-colors: none;
-    -moz-border-right-colors: none;
-    -moz-border-top-colors: none;
-    background-color: #EDECE6;
-    border-color: #D9D8D1;
-    border-style: solid;
-    border-width: 1px;
-    font-weight: bold;
-    margin-top: 10px;
-    padding: 4px 0 2px;
-}
-
-div.diff.extended_header {
-	background-color: #F6F5EE;
-    padding: 2px 0;
-    font-family: inherit;
-}
-
-span.diff.add {
-	color: #008800;
-	font-family: inherit;
-}
-
-span.diff.remove {
-	color: #FFDDDD;
-	font-family: inherit;
-}
-
-span.diff.unchanged {
-	color: inherit;
-	font-family: inherit;
-}
-
-div.diff.hunk_header {
-	-moz-border-bottom-colors: none;
-    -moz-border-image: none;
-    -moz-border-left-colors: none;
-    -moz-border-right-colors: none;
-    -moz-border-top-colors: none;
-    border-color: #FFE0FF;
-    border-style: dotted;
-    border-width: 1px 0 0;
-    margin-top: 2px;
-    font-family: inherit;
-}
-
-span.diff.hunk_info {
-	background-color: #FFEEFF;	
-	color: #990099;
-	font-family: inherit;
-}
-
-span.diff.hunk_section {	
-	color: #AA22AA;
-	font-family: inherit;
-}
-
-div.diff.add2 {
-	background-color: #DDFFDD;
-    font-family: inherit;
-}
-
-div.diff.remove2 {
-	background-color: #FFDDDD;
-    font-family: inherit;
-}
-
-div.diff table {
-	border-radius: 0;
-	border-right: 1px solid #bbb;
-	border-bottom: 1px solid #bbb;
-	width: 100%;
-}
-
-div.diff table th, div.diff table td {
-	margin: 0px;
-	padding: 0px;
-	font-family: monospace;
-	border: 0;
-}
-
-div.diff table th {
-	background-color: #f0f0f0;
-	text-align: center;
-	color: #999;
-	padding-left: 5px;
-	padding-right: 5px;
-	width: 30px;
-}
-
-div.diff table th.header {
-	background-color: #D2C3AF;
-	border-right: 0px;
-	border-bottom: 1px solid #808080;
-	font-family: inherit;
-	font-size:0.9em;
-	color: black;
-	padding: 2px;
-	text-align: left;
-}
-
-div.diff table td.hunk_header {
-	background-color: #dAe2e5 !important;
-	border-top: 1px solid #bac2c5;	
-	border-bottom: 1px solid #bac2c5;
-	color: #555;
-}
-
-div.diff table td {
-	border-left: 1px solid #bbb;
-	background-color: #f5f5f5;
-}
-
-td.changeType {
-	width: 15px;
-}
-
-span.addition, span.modification, span.deletion, span.rename {
-	border: 1px solid #888;
-	float: left;
-	height: 0.8em;
-	margin: 0.2em 0.5em 0 0;
-	overflow: hidden;
-	width: 0.8em;
-}
-
-span.addition {
-	background-color: #ccffcc;
-}
-
-span.modification {
-	background-color: #ffdd88;
-}
-
-span.deletion {
-	background-color: #f8bbbb;
-}
-
-span.rename {
-	background-color: #cAc2f5;
-}
-
-div.commitLegend {
-	float: right;
-	padding: 0.4em 0.4em 0.2em 0.4em;
-	vertical-align:top;
-	margin: 0px;
-}
-
-div.commitLegend span {
-	font-size: 0.9em;
-	vertical-align: top;
-}
-
-div.references {
-	float: right;
-	text-align: right;
-}
-
-table.plain {
-	width: 0 !important;
-	border: 0;
-}
-
-table.plain th, table.plain td {
-	white-space: nowrap;
-	padding: 1px 3px;
-	border: 0;
-}
-
-table.pretty {
-	border:1px solid #ddd;
-	border-radius: 0 0 3px 3px;
-	width: 100%;
-}
-
-table.pretty td.icon {
-	padding: 0px 0px 0px 2px;	
-	width: 18px;
-	vertical-align: middle;
-}
-
-table.pretty td.icon img {
-	vertical-align: top;
-}
-
-table.pretty td {
-	padding: 2px 4px;
-	border-left: 0;
-}
-
-table.pretty td.message {
-	padding: 0px;
-}
-
-table.pretty table.nestedTable {
-	width: 100%;
-	margin-left: 4px !important;
-	margin-bottom: 0px !important;
-}
-
-table.comments td {
-	padding: 4px;
-	line-height: 17px;
-}
-
-table.repositories {	
-	border:1px solid #ddd;
-	border-spacing: 0px;
-	width: 100%;
-}
-
-table.repositories th {
-	padding: 4px;
-	border:0;
-}
-
-table.repositories th.right {	
-	border-right: 1px solid #ddd;	
-}	
-
-table.repositories td {
-	padding: 2px;
-	border-left: 0;
-}
-
-table.repositories td.rightAlign {	
-	text-align: right;
-	border-right: 1px solid #ddd;	
-}	
-
-table.repositories td.icon img {
-	vertical-align: top;
-}
-
-table.repositories tr.group {
-	background-color: #ccc;
-	border-left: 1px solid #ccc;
-	border-right: 1px solid #ccc;
-}
-
-table.repositories tr.group td {
-	font-weight: bold;		
-	color: black;
-	background-color: #ddd;
-	padding-left: 5px;
-	border-top: 1px solid #aaa; 	
- 	border-bottom: 1px solid #aaa; 
-}
-
-table.repositories tr.group td a {
-	color: black;
-}
-
-table.palette { border:0; width: 0 !important; }
-table.palette td.header { 
-	font-weight: bold; 
-	background-color: #ffffff !important;
-	padding-top: 0px !important;
-	margin-bottom: 0 !imporant;	
-	border: 0 !important;
-	border-radius: 0 !important;
-	line-height: 1em;
-}
-table.palette td.pane {
-	padding: 0px;
-}
-
-table.gitnotes {		
-	border: 0;	
-}
-table.gitnotes td {
-	border-top: 1px solid #ddd;
-	padding-top: 3px;
-	vertical-align:top;
-}
-
-table.gitnotes table {
-	border: none;
-}
-
-table.gitnotes td table td {
-	border: none;
-	padding: 0px;
-}
-
-table.gitnotes td.info {
-	padding-right: 10px;
-}
-
-table.gitnotes td.message {
-	width: 65%;
-	border-left: 1px solid #ddd;
-	padding-left: 10px;
-}
-
-table.annotated {
-	border:1px solid #ddd;
-}
-
-table.annotated tr.even {
-	background-color: white;
-}
-
-table.annotated tr.odd {
-	background-color: #f5f5f5;
-}
-
-table.annotated td {
-	padding: 0px;
-	border: 0;
-}
-
-table.activity {
-	width: 100%;
-	margin-top: 10px;
-}
-
-table.activity td {
-	padding-top:7px;
-	padding-bottom:7px;
-}
-
-tr th a { background-position: right; padding-right: 15px; background-repeat:no-repeat; }
-tr th.wicket_orderDown a {background-image: url(arrow_down.png); }
-tr th.wicket_orderUp a { background-image: url(arrow_up.png); }
-tr th.wicket_orderNone a { background-image: url(arrow_off.png); }
-
-tr.light {
-	background-color: #ffffff;
-}
-
-tr.dark {
-	background-color: #f5f5f5;
-}
-
-/* currently both use the same, but it can change */
-tr.light:hover,
-tr.dark:hover {
-	background-color: #000070;
-	color: white;
-}
-
-tr.light:hover a,
-tr.dark:hover a {
-	color: white;	
-}
-
-td.author {
-	font-style: italic !important;
-}
-
-td.date {
-	/*font-style: italic !important;*/
-	white-space: nowrap;
-}
-
-span.sha1, span.sha1 a, span.sha1 a span, .commit_message, span.shortsha1 {
-	font-family: consolas, monospace;
-	font-size: 13px;
-}
-
-span.shortsha1 {
-	font-size: 12px;
-}
-
-td.mode {
-	text-align: right;
-	font-family: monospace;
-	width: 8em;
-	padding-right:15px;
-}
-
-td.size {
-	text-align: right;
-	width: 8em;	
-	padding-right:15px;
-}
-
-td.rightAlign {
-	text-align: right;
-}
-
-td.treeLinks {
-	text-align: right;
-	width: 13em;
-}
-
-span.help-inline {
-	color: #777;
-}
-
-span.metricsTitle {
-	font-size: 2em;
-}
-
-.tagRef, .headRef, .localBranch, .remoteBranch, .otherRef {	
-	padding: 0px 3px;
-	margin-right:2px;
-	font-family: sans-serif;
-	font-size: 9px;
-	font-weight: normal;
-	border: 1px solid;
-	color: black;	
-}
-
-.tagRef a, .headRef a, .localBranch a, .remoteBranch a, .otherRef a {
-	font-size: 9px;
-	text-decoration: none;
-	color: black !important;
-}
-
-.tagRef a:hover, .headRef a:hover, .localBranch a:hover, .remoteBranch a:hover, .otherRef a:hover {
-	color: black !important;
-	text-decoration: underline;
-}
-
-.otherRef {
-	background-color: #b0e0f0;
-	border-color: #80aaaa;	
-}
-
-.remoteBranch {
-	background-color: #cAc2f5;
-	border-color: #6c6cbf;
-}
-
-.tagRef {
-	background-color: #ffffaa;
-	border-color: #ffcc00;
-}
-
-.headRef {
-	background-color: #ffaaff;
-	border-color: #ff00ee;
-}
-
-.localBranch {
-	background-color: #ccffcc;
-	border-color: #00cc33;
-}
-
-table .palette td.buttons button {
-	-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;	
-	border: 1px solid #ccc !important;
-	padding: 10px;
-	margin-bottom: 10px;
-}
-
-table .palette td.buttons button:hover {
-	border: 1px solid #0069D6 !important;
-}
-
-table .palette td.buttons button:active {
-	border: 1px solid orange !important;
-}
-
-.feedbackPanelERROR, .feedbackPanelINFO {	
-	list-style: none;
-	line-height: 35px;
-}
-
-.feedbackPanelINFO span, .feedbackPanelERROR span {
-	position:relative;padding:7px 15px;margin-top:5px;margin-bottom:5px;color:#404040;background-color:#eedc94;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#fceec1), to(#eedc94));background-image:-moz-linear-gradient(top, #fceec1, #eedc94);background-image:-ms-linear-gradient(top, #fceec1, #eedc94);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #fceec1), color-stop(100%, #eedc94));background-image:-webkit-linear-gradient(top, #fceec1, #eedc94);background-image:-o-linear-gradient(top, #fceec1, #eedc94);background-image:linear-gradient(top, #fceec1, #eedc94);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fceec1', endColorstr='#eedc94', GradientType=0);text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);border-color:#eedc94 #eedc94 #e4c652;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);text-shadow:0 1px 0 rgba(255, 255, 255, 0.5);border-width:1px;border-style:solid;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.25);-moz-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.25);box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.25);
-}
-
-.feedbackPanelERROR span {
-	color: #ffffff;
-	background-color:#c43c35;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#ee5f5b), to(#c43c35));background-image:-moz-linear-gradient(top, #ee5f5b, #c43c35);background-image:-ms-linear-gradient(top, #ee5f5b, #c43c35);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #ee5f5b), color-stop(100%, #c43c35));background-image:-webkit-linear-gradient(top, #ee5f5b, #c43c35);background-image:-o-linear-gradient(top, #ee5f5b, #c43c35);background-image:linear-gradient(top, #ee5f5b, #c43c35);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ee5f5b', endColorstr='#c43c35', GradientType=0);text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);border-color:#c43c35 #c43c35 #882a25;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
-}
-
-/* google-code-prettify line numbers */
-li.L0,
-li.L1,
-li.L2,
-li.L3,
-li.L4,
-li.L5,
-li.L6,
-li.L7,
-li.L8,
-li.L9 { color: #888; border-left: 1px solid #ccc; padding-left:5px; list-style-type: decimal !important; }
-
-/* Alternate shading for lines */
-li.L1,
-li.L3,
-li.L5,
-li.L7,
-li.L9 { background: #fafafa !important; }
-
-div.markdown pre {
-    background-color: #F5F5F5;
-    border: 1px solid rgba(0, 0, 0, 0.15);
-    border-radius: 4px 4px 4px 4px;
-    display: block;
-    font-size: 12px;
-    line-height: 18px;
-    margin: 0 0 9px;
-    padding: 8.5px;
-    white-space: pre-wrap;
-}
-
-div.markdown pre code {
-    background-color: inherit;
-    border: none;    
-    padding: 0;
-}
-
-div.markdown code {
-	background-color: #ffffe0;
-    border: 1px solid orange;
-    border-radius: 3px;
-    padding: 0 0.2em;
-}
-
-div.markdown a {
-	text-decoration: underline;	
-}
-
-div.markdown em {
-	color: #b05000;
-}
-
-div.markdown table.text th, div.markdown table.text td {
-	vertical-align: top;
-	border-top: 1px solid #ccc;
-	padding:5px;
-}
\ No newline at end of file
diff --git a/resources/gitblt-favicon.png b/resources/gitblt-favicon.png
deleted file mode 100644
index c3c6dc1..0000000
--- a/resources/gitblt-favicon.png
+++ /dev/null
Binary files differ
diff --git a/src/WEB-INF/web.xml b/src/WEB-INF/web.xml
deleted file mode 100644
index 75ccf9b..0000000
--- a/src/WEB-INF/web.xml
+++ /dev/null
@@ -1,249 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<web-app version="2.4"
-	xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-	xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
-
-	<!-- The base folder is used to specify the root location of your Gitblit data.
-	
-			${baseFolder}/gitblit.properties
-			${baseFolder}/users.conf
-			${baseFolder}/projects.conf
-			${baseFolder}/robots.txt
-			${baseFolder}/git
-			${baseFolder}/groovy
-			${baseFolder}/groovy/grape
-			${baseFolder}/proposals
-
-		By default, this location is WEB-INF/data.  It is recommended to set this
-		path to a location outside your webapps folder that is writable by your
-		servlet container.  Gitblit will copy the WEB-INF/data files to that
-		location for you when it restarts.  This approach makes upgrading simpler.
-		All you have to do is set this parameter for the new release and then
-		review the defaults for any new settings.  Settings are always versioned
-		with a SINCE x.y.z attribute and also noted in the release changelog.
-		-->
-	<context-param>
-		<param-name>baseFolder</param-name>
-		<param-value>${contextFolder}/WEB-INF/data</param-value>
-	</context-param>
-
-	<!-- PARAMS --> 
-	 
-	<!-- Gitblit Context Listener --><!-- STRIP	 
-	<listener>
- 		<listener-class>com.gitblit.GitBlit</listener-class>
- 	</listener>STRIP --> 	
-	
-	
-	<!-- Git Servlet
-		 <url-pattern> MUST match: 
-			* GitFilter
-			* com.gitblit.Constants.GIT_PATH
-			* Wicket Filter ignorePaths parameter -->
-	<servlet>
-		<servlet-name>GitServlet</servlet-name>
-		<servlet-class>com.gitblit.GitServlet</servlet-class>
-	</servlet>
-	<servlet-mapping>
-		<servlet-name>GitServlet</servlet-name>		
-		<url-pattern>/git/*</url-pattern>
-	</servlet-mapping>
-	
-	
-	<!-- Syndication Servlet
-		 <url-pattern> MUST match: 
-			* SyndicationFilter
-			* com.gitblit.Constants.SYNDICATION_PATH
-			* Wicket Filter ignorePaths parameter -->
-	<servlet>
-		<servlet-name>SyndicationServlet</servlet-name>
-		<servlet-class>com.gitblit.SyndicationServlet</servlet-class>		
-	</servlet>
-	<servlet-mapping>
-		<servlet-name>SyndicationServlet</servlet-name>
-		<url-pattern>/feed/*</url-pattern>
-	</servlet-mapping>
-	
-	
-	<!-- Zip Servlet
-		 <url-pattern> MUST match: 
-			* ZipServlet
-			* com.gitblit.Constants.ZIP_PATH
-			* Wicket Filter ignorePaths parameter -->
-	<servlet>
-		<servlet-name>ZipServlet</servlet-name>
-		<servlet-class>com.gitblit.DownloadZipServlet</servlet-class>		
-	</servlet>
-	<servlet-mapping>
-		<servlet-name>ZipServlet</servlet-name>
-		<url-pattern>/zip/*</url-pattern>
-	</servlet-mapping>
-	
-	
-	<!-- Federation Servlet
-		 <url-pattern> MUST match: 
-		 	* com.gitblit.Constants.FEDERATION_PATH		 
-			* Wicket Filter ignorePaths parameter -->
-	<servlet>
-		<servlet-name>FederationServlet</servlet-name>
-		<servlet-class>com.gitblit.FederationServlet</servlet-class>		
-	</servlet>
-	<servlet-mapping>
-		<servlet-name>FederationServlet</servlet-name>
-		<url-pattern>/federation/*</url-pattern>
-	</servlet-mapping>	
-	
-	
-	<!-- Rpc Servlet
-		 <url-pattern> MUST match: 
-		 	* com.gitblit.Constants.RPC_PATH		 
-			* Wicket Filter ignorePaths parameter -->
-	<servlet>
-		<servlet-name>RpcServlet</servlet-name>
-		<servlet-class>com.gitblit.RpcServlet</servlet-class>		
-	</servlet>
-	<servlet-mapping>
-		<servlet-name>RpcServlet</servlet-name>
-		<url-pattern>/rpc/*</url-pattern>
-	</servlet-mapping>	
-
-
-	<!-- Pages Servlet
-		 <url-pattern> MUST match: 
-			* PagesFilter
-			* com.gitblit.Constants.PAGES_PATH
-			* Wicket Filter ignorePaths parameter -->
-	<servlet>
-		<servlet-name>PagesServlet</servlet-name>
-		<servlet-class>com.gitblit.PagesServlet</servlet-class>
-	</servlet>
-	<servlet-mapping>
-		<servlet-name>PagesServlet</servlet-name>		
-		<url-pattern>/pages/*</url-pattern>
-	</servlet-mapping>	
-	
-
-	<!-- Robots.txt Servlet
-		 <url-pattern> MUST match: 
-			* Wicket Filter ignorePaths parameter -->
-	<servlet>
-		<servlet-name>RobotsTxtServlet</servlet-name>
-		<servlet-class>com.gitblit.RobotsTxtServlet</servlet-class>
-	</servlet>
-	<servlet-mapping>
-		<servlet-name>RobotsTxtServlet</servlet-name>		
-		<url-pattern>/robots.txt</url-pattern>
-	</servlet-mapping>
-
-	
-	<!-- Git Access Restriction Filter
-		 <url-pattern> MUST match: 
-			* GitServlet
-			* com.gitblit.Constants.GIT_PATH
-			* Wicket Filter ignorePaths parameter -->
-	<filter>
-		<filter-name>GitFilter</filter-name>
-		<filter-class>com.gitblit.GitFilter</filter-class>
-	</filter>
-	<filter-mapping>
-		<filter-name>GitFilter</filter-name>
-		<url-pattern>/git/*</url-pattern>
-	</filter-mapping>
-	
-	
-	<!-- Syndication Restriction Filter
-		 <url-pattern> MUST match: 
-			* SyndicationServlet
-			* com.gitblit.Constants.SYNDICATION_PATH
-			* Wicket Filter ignorePaths parameter -->
-	<filter>
-		<filter-name>SyndicationFilter</filter-name>
-		<filter-class>com.gitblit.SyndicationFilter</filter-class>
-	</filter>
-	<filter-mapping>
-		<filter-name>SyndicationFilter</filter-name>
-		<url-pattern>/feed/*</url-pattern>
-	</filter-mapping>
-	
-	
-	<!-- Download Zip Restriction Filter
-		 <url-pattern> MUST match: 
-			* DownloadZipServlet
-			* com.gitblit.Constants.ZIP_PATH
-			* Wicket Filter ignorePaths parameter -->
-	<filter>
-		<filter-name>ZipFilter</filter-name>
-		<filter-class>com.gitblit.DownloadZipFilter</filter-class>
-	</filter>
-	<filter-mapping>
-		<filter-name>ZipFilter</filter-name>
-		<url-pattern>/zip/*</url-pattern>
-	</filter-mapping>
-
-		
-	<!-- Rpc Restriction Filter
-		 <url-pattern> MUST match: 
-			* RpcServlet
-			* com.gitblit.Constants.RPC_PATH
-			* Wicket Filter ignorePaths parameter -->
-	<filter>
-		<filter-name>RpcFilter</filter-name>
-		<filter-class>com.gitblit.RpcFilter</filter-class>
-	</filter>
-	<filter-mapping>
-		<filter-name>RpcFilter</filter-name>
-		<url-pattern>/rpc/*</url-pattern>
-	</filter-mapping>
-
-
-	<!-- Pges Restriction Filter
-		 <url-pattern> MUST match: 
-			* PagesServlet
-			* com.gitblit.Constants.PAGES_PATH
-			* Wicket Filter ignorePaths parameter -->
-	<filter>
-		<filter-name>PagesFilter</filter-name>
-		<filter-class>com.gitblit.PagesFilter</filter-class>
-	</filter>
-	<filter-mapping>
-		<filter-name>PagesFilter</filter-name>
-		<url-pattern>/pages/*</url-pattern>
-	</filter-mapping>
-
-
-	<!-- Wicket Filter -->
-    <filter>
-        <filter-name>wicketFilter</filter-name>
-        <filter-class>
-            org.apache.wicket.protocol.http.WicketFilter
-        </filter-class>
-        <init-param>
-            <param-name>applicationClassName</param-name>
-            <param-value>com.gitblit.wicket.GitBlitWebApp</param-value>
-        </init-param>
-        <init-param>
-            <param-name>ignorePaths</param-name>
-            <!-- Paths should match 
-             	* SyndicationFilter <url-pattern>
-             	* SyndicationServlet <url-pattern>
-             	* com.gitblit.Constants.SYNDICATION_PATH
-             	* GitFilter <url-pattern>
-             	* GitServlet <url-pattern>
-             	* com.gitblit.Constants.GIT_PATH
-             	* Zipfilter <url-pattern>
-             	* ZipServlet <url-pattern>
-             	* com.gitblit.Constants.ZIP_PATH
-             	* FederationServlet <url-pattern>
-             	* RpcFilter <url-pattern>
-             	* RpcServlet <url-pattern>
-             	* PagesFilter <url-pattern>
-             	* PagesServlet <url-pattern>
-             	* com.gitblit.Constants.PAGES_PATH -->
-            <param-value>git/,feed/,zip/,federation/,rpc/,pages/,robots.txt</param-value>
-        </init-param>
-    </filter>
-    <filter-mapping>
-        <filter-name>wicketFilter</filter-name>
-        <url-pattern>/*</url-pattern>
-    </filter-mapping>
-</web-app>
\ No newline at end of file
diff --git a/src/com/gitblit/AddIndexedBranch.java b/src/com/gitblit/AddIndexedBranch.java
deleted file mode 100644
index 6699706..0000000
--- a/src/com/gitblit/AddIndexedBranch.java
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
- * 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;
-import java.text.MessageFormat;
-import java.util.ArrayList;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Set;
-import java.util.TreeSet;
-
-import org.eclipse.jgit.lib.RepositoryCache.FileKey;
-import org.eclipse.jgit.storage.file.FileBasedConfig;
-import org.eclipse.jgit.storage.file.FileRepository;
-import org.eclipse.jgit.util.FS;
-
-import com.beust.jcommander.JCommander;
-import com.beust.jcommander.Parameter;
-import com.beust.jcommander.ParameterException;
-import com.beust.jcommander.Parameters;
-import com.gitblit.utils.ArrayUtils;
-import com.gitblit.utils.JGitUtils;
-import com.gitblit.utils.StringUtils;
-
-/**
- * Utility class to add an indexBranch setting to matching repositories.
- * 
- * @author James Moger
- * 
- */
-public class AddIndexedBranch {
-
-	public static void main(String... args) {
-		Params params = new Params();
-		JCommander jc = new JCommander(params);
-		try {
-			jc.parse(args);
-		} catch (ParameterException t) {
-			System.err.println(t.getMessage());
-			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) {
-			boolean skip = false;
-			for (String exclusion : exclusions) {
-				if (StringUtils.fuzzyMatch(repo, exclusion)) {
-					skip = true;
-					break;
-				}
-			}
-			
-			if (skip) {
-				System.out.println("skipping " + repo);
-				skipCount++;
-				continue;
-			}
-
-			System.out.println(MessageFormat.format("adding [gitblit] indexBranch={0} for {1}", params.branch, repo));
-			try {
-				// load repository config
-				File gitDir = FileKey.resolve(new File(folder, repo), FS.DETECTED);
-				FileRepository repository = new FileRepository(gitDir);
-				FileBasedConfig config = repository.getConfig();
-				config.load();
-				
-				Set<String> indexedBranches = new LinkedHashSet<String>();
-				indexedBranches.add(Constants.DEFAULT_BRANCH);
-				
-				String [] branches = config.getStringList("gitblit", null, "indexBranch");
-				if (!ArrayUtils.isEmpty(branches)) {
-					for (String branch : branches) {
-						indexedBranches.add(branch);
-					}
-				}
-				config.setStringList("gitblit", null, "indexBranch", new ArrayList<String>(indexedBranches));
-				config.save();
-				modCount++;
-			} catch (Exception e) {
-				System.err.println(repo);
-				e.printStackTrace();
-			}
-		}
-		
-		System.out.println(MessageFormat.format("updated {0} repository configurations, skipped {1}", modCount, skipCount));
-	}
-
-	
-
-	/**
-	 * JCommander Parameters class for AddIndexedBranch.
-	 */
-	@Parameters(separators = " ")
-	private static class Params {
-
-		@Parameter(names = { "--repositoriesFolder" }, description = "The root repositories folder ", required = true)
-		public String folder;
-
-		@Parameter(names = { "--branch" }, description = "The branch to index", required = true)
-		public String branch = "default";
-
-		@Parameter(names = { "--skip" }, description = "Skip the named repository (simple fizzy matching is supported)", required = false)
-		public List<String> exclusions = new ArrayList<String>();
-	}
-}
diff --git a/src/com/gitblit/AuthenticationFilter.java b/src/com/gitblit/AuthenticationFilter.java
deleted file mode 100644
index eb6e95b..0000000
--- a/src/com/gitblit/AuthenticationFilter.java
+++ /dev/null
@@ -1,187 +0,0 @@
-/*
- * Copyright 2011 gitblit.com.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.gitblit;
-
-import java.io.IOException;
-import java.security.Principal;
-import java.util.Enumeration;
-import java.util.HashMap;
-import java.util.Map;
-
-import javax.servlet.Filter;
-import javax.servlet.FilterChain;
-import javax.servlet.FilterConfig;
-import javax.servlet.ServletException;
-import javax.servlet.ServletRequest;
-import javax.servlet.ServletResponse;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import javax.servlet.http.HttpSession;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.gitblit.models.UserModel;
-import com.gitblit.utils.StringUtils;
-
-/**
- * The AuthenticationFilter is a servlet filter that preprocesses requests that
- * match its url pattern definition in the web.xml file.
- * 
- * http://en.wikipedia.org/wiki/Basic_access_authentication
- * 
- * @author James Moger
- * 
- */
-public abstract class AuthenticationFilter implements Filter {
-
-	protected static final String CHALLENGE = "Basic realm=\"" + Constants.NAME + "\"";
-
-	protected static final String SESSION_SECURED = "com.gitblit.secured";
-
-	protected transient Logger logger = LoggerFactory.getLogger(getClass());
-
-	/**
-	 * doFilter does the actual work of preprocessing the request to ensure that
-	 * the user may proceed.
-	 * 
-	 * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest,
-	 *      javax.servlet.ServletResponse, javax.servlet.FilterChain)
-	 */
-	@Override
-	public abstract void doFilter(final ServletRequest request, final ServletResponse response,
-			final FilterChain chain) throws IOException, ServletException;
-	
-	/**
-	 * Allow the filter to require a client certificate to continue processing.
-	 * 
-	 * @return true, if a client certificate is required
-	 */
-	protected boolean requiresClientCertificate() {
-		return false;
-	}
-
-	/**
-	 * Returns the full relative url of the request.
-	 * 
-	 * @param httpRequest
-	 * @return url
-	 */
-	protected String getFullUrl(HttpServletRequest httpRequest) {
-		String servletUrl = httpRequest.getContextPath() + httpRequest.getServletPath();
-		String url = httpRequest.getRequestURI().substring(servletUrl.length());
-		String params = httpRequest.getQueryString();
-		if (url.length() > 0 && url.charAt(0) == '/') {
-			url = url.substring(1);
-		}
-		String fullUrl = url + (StringUtils.isEmpty(params) ? "" : ("?" + params));
-		return fullUrl;
-	}
-
-	/**
-	 * Returns the user making the request, if the user has authenticated.
-	 * 
-	 * @param httpRequest
-	 * @return user
-	 */
-	protected UserModel getUser(HttpServletRequest httpRequest) {
-		UserModel user = GitBlit.self().authenticate(httpRequest, requiresClientCertificate());
-		return user;
-	}
-
-	/**
-	 * Taken from Jetty's LoginAuthenticator.renewSessionOnAuthentication()
-	 */
-	@SuppressWarnings("unchecked")
-	protected void newSession(HttpServletRequest request, HttpServletResponse response) {
-		HttpSession oldSession = request.getSession(false);
-		if (oldSession != null && oldSession.getAttribute(SESSION_SECURED) == null) {
-			synchronized (this) {
-				Map<String, Object> attributes = new HashMap<String, Object>();
-				Enumeration<String> e = oldSession.getAttributeNames();
-				while (e.hasMoreElements()) {
-					String name = e.nextElement();
-					attributes.put(name, oldSession.getAttribute(name));
-					oldSession.removeAttribute(name);
-				}
-				oldSession.invalidate();
-
-				HttpSession newSession = request.getSession(true);
-				newSession.setAttribute(SESSION_SECURED, Boolean.TRUE);
-				for (Map.Entry<String, Object> entry : attributes.entrySet()) {
-					newSession.setAttribute(entry.getKey(), entry.getValue());
-				}
-			}
-		}
-	}
-
-	/**
-	 * @see javax.servlet.Filter#init(javax.servlet.FilterConfig)
-	 */
-	@Override
-	public void init(final FilterConfig config) throws ServletException {
-	}
-
-	/**
-	 * @see javax.servlet.Filter#destroy()
-	 */
-	@Override
-	public void destroy() {
-	}
-
-	/**
-	 * Wraps a standard HttpServletRequest and overrides user principal methods.
-	 */
-	public static class AuthenticatedRequest extends ServletRequestWrapper {
-
-		private UserModel user;
-
-		public AuthenticatedRequest(HttpServletRequest req) {
-			super(req);
-			user = new UserModel("anonymous");
-			user.isAuthenticated = false;
-		}
-
-		UserModel getUser() {
-			return user;
-		}
-
-		void setUser(UserModel user) {
-			this.user = user;
-		}
-
-		@Override
-		public String getRemoteUser() {
-			return user.username;
-		}
-
-		@Override
-		public boolean isUserInRole(String role) {
-			if (role.equals(Constants.ADMIN_ROLE)) {
-				return user.canAdmin();
-			}
-			// Gitblit does not currently use actual roles in the traditional
-			// servlet container sense.  That is the reason this is marked
-			// deprecated, but I may want to revisit this.
-			return user.canAccessRepository(role);
-		}
-
-		@Override
-		public Principal getUserPrincipal() {
-			return user;
-		}
-	}
-}
\ No newline at end of file
diff --git a/src/com/gitblit/ConfigUserService.java b/src/com/gitblit/ConfigUserService.java
deleted file mode 100644
index 67ad053..0000000
--- a/src/com/gitblit/ConfigUserService.java
+++ /dev/null
@@ -1,1078 +0,0 @@
-/*
- * Copyright 2011 gitblit.com.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.gitblit;
-
-import java.io.File;
-import java.io.IOException;
-import java.text.MessageFormat;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-
-import org.eclipse.jgit.lib.StoredConfig;
-import org.eclipse.jgit.storage.file.FileBasedConfig;
-import org.eclipse.jgit.util.FS;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.gitblit.Constants.AccessPermission;
-import com.gitblit.models.TeamModel;
-import com.gitblit.models.UserModel;
-import com.gitblit.utils.ArrayUtils;
-import com.gitblit.utils.DeepCopier;
-import com.gitblit.utils.StringUtils;
-
-/**
- * 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 {
-
-	private static final String TEAM = "team";
-
-	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";
-
-	private static final String ROLE = "role";
-
-	private static final String MAILINGLIST = "mailingList";
-
-	private static final String PRERECEIVE = "preReceiveScript";
-
-	private static final String POSTRECEIVE = "postReceiveScript";
-
-	private final File realmFile;
-
-	private final Logger logger = LoggerFactory.getLogger(ConfigUserService.class);
-
-	private final Map<String, UserModel> users = new ConcurrentHashMap<String, UserModel>();
-
-	private final Map<String, UserModel> cookies = new ConcurrentHashMap<String, UserModel>();
-
-	private final Map<String, TeamModel> teams = new ConcurrentHashMap<String, TeamModel>();
-
-	private volatile long lastModified;
-	
-	private volatile boolean forceReload;
-
-	public ConfigUserService(File realmFile) {
-		this.realmFile = realmFile;
-	}
-
-	/**
-	 * Setup the user service.
-	 * 
-	 * @param settings
-	 * @since 0.7.0
-	 */
-	@Override
-	public void setup(IStoredSettings settings) {
-	}
-
-	/**
-	 * Does the user service support changes to credentials?
-	 * 
-	 * @return true or false
-	 * @since 1.0.0
-	 */
-	@Override
-	public boolean supportsCredentialChanges() {
-		return true;
-	}
-
-	/**
-	 * Does the user service support changes to user display name?
-	 * 
-	 * @return true or false
-	 * @since 1.0.0
-	 */
-	@Override
-	public boolean supportsDisplayNameChanges() {
-		return true;
-	}
-
-	/**
-	 * Does the user service support changes to user email address?
-	 * 
-	 * @return true or false
-	 * @since 1.0.0
-	 */
-	@Override
-	public boolean supportsEmailAddressChanges() {
-		return true;
-	}
-
-	/**
-	 * Does the user service support changes to team memberships?
-	 * 
-	 * @return true or false
-	 * @since 1.0.0
-	 */	
-	public boolean supportsTeamMembershipChanges() {
-		return true;
-	}
-	
-	/**
-	 * Does the user service support cookie authentication?
-	 * 
-	 * @return true or false
-	 */
-	@Override
-	public boolean supportsCookies() {
-		return true;
-	}
-
-	/**
-	 * Returns the cookie value for the specified user.
-	 * 
-	 * @param model
-	 * @return cookie value
-	 */
-	@Override
-	public String getCookie(UserModel model) {
-		if (!StringUtils.isEmpty(model.cookie)) {
-			return model.cookie;
-		}
-		read();
-		UserModel storedModel = users.get(model.username.toLowerCase());
-		return storedModel.cookie;
-	}
-
-	/**
-	 * Authenticate a user based on their cookie.
-	 * 
-	 * @param cookie
-	 * @return a user object or null
-	 */
-	@Override
-	public UserModel authenticate(char[] cookie) {
-		String hash = new String(cookie);
-		if (StringUtils.isEmpty(hash)) {
-			return null;
-		}
-		read();
-		UserModel model = null;
-		if (cookies.containsKey(hash)) {
-			model = cookies.get(hash);
-		}
-		return model;
-	}
-
-	/**
-	 * Authenticate a user based on a username and password.
-	 * 
-	 * @param username
-	 * @param password
-	 * @return a user object or null
-	 */
-	@Override
-	public UserModel authenticate(String username, char[] password) {
-		read();
-		UserModel returnedUser = null;
-		UserModel user = getUserModel(username);
-		if (user == null) {
-			return null;
-		}
-		if (user.password.startsWith(StringUtils.MD5_TYPE)) {
-			// password digest
-			String md5 = StringUtils.MD5_TYPE + StringUtils.getMD5(new String(password));
-			if (user.password.equalsIgnoreCase(md5)) {
-				returnedUser = user;
-			}
-		} else if (user.password.startsWith(StringUtils.COMBINED_MD5_TYPE)) {
-			// username+password digest
-			String md5 = StringUtils.COMBINED_MD5_TYPE
-					+ StringUtils.getMD5(username.toLowerCase() + new String(password));
-			if (user.password.equalsIgnoreCase(md5)) {
-				returnedUser = user;
-			}
-		} else if (user.password.equals(new String(password))) {
-			// plain-text password
-			returnedUser = user;
-		}
-		return returnedUser;
-	}
-
-	/**
-	 * Logout a user.
-	 * 
-	 * @param user
-	 */
-	@Override
-	public void logout(UserModel user) {	
-	}
-	
-	/**
-	 * Retrieve the user object for the specified username.
-	 * 
-	 * @param username
-	 * @return a user object or null
-	 */
-	@Override
-	public UserModel getUserModel(String username) {
-		read();
-		UserModel model = users.get(username.toLowerCase());
-		if (model != null) {
-			// clone the model, otherwise all changes to this object are
-			// live and unpersisted
-			model = DeepCopier.copy(model);
-		}
-		return model;
-	}
-
-	/**
-	 * Updates/writes a complete user object.
-	 * 
-	 * @param model
-	 * @return true if update is successful
-	 */
-	@Override
-	public boolean updateUserModel(UserModel model) {
-		return updateUserModel(model.username, model);
-	}
-
-	/**
-	 * Updates/writes all specified user objects.
-	 * 
-	 * @param models a list of user models
-	 * @return true if update is successful
-	 * @since 1.2.0
-	 */
-	@Override
-	public boolean updateUserModels(List<UserModel> models) {
-		try {
-			read();
-			for (UserModel model : models) {
-				UserModel originalUser = users.remove(model.username.toLowerCase());
-				users.put(model.username.toLowerCase(), model);
-				// null check on "final" teams because JSON-sourced UserModel
-				// can have a null teams object
-				if (model.teams != null) {
-					for (TeamModel team : model.teams) {
-						TeamModel t = teams.get(team.name.toLowerCase());
-						if (t == null) {
-							// new team
-							team.addUser(model.username);
-							teams.put(team.name.toLowerCase(), team);
-						} else {
-							// do not clobber existing team definition
-							// maybe because this is a federated user
-							t.addUser(model.username);							
-						}
-					}
-
-					// check for implicit team removal
-					if (originalUser != null) {
-						for (TeamModel team : originalUser.teams) {
-							if (!model.isTeamMember(team.name)) {
-								team.removeUser(model.username);
-							}
-						}
-					}
-				}
-			}
-			write();
-			return true;
-		} catch (Throwable t) {
-			logger.error(MessageFormat.format("Failed to update user {0} models!", models.size()),
-					t);
-		}
-		return false;
-	}
-
-	/**
-	 * 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
-	 *            the user object to use for username
-	 * @return true if update is successful
-	 */
-	@Override
-	public boolean updateUserModel(String username, UserModel model) {
-		UserModel originalUser = null;
-		try {
-			read();
-			originalUser = users.remove(username.toLowerCase());
-			users.put(model.username.toLowerCase(), model);
-			// null check on "final" teams because JSON-sourced UserModel
-			// can have a null teams object
-			if (model.teams != null) {
-				for (TeamModel team : model.teams) {
-					TeamModel t = teams.get(team.name.toLowerCase());
-					if (t == null) {
-						// new team
-						team.addUser(username);
-						teams.put(team.name.toLowerCase(), team);
-					} else {
-						// do not clobber existing team definition
-						// maybe because this is a federated user
-						t.removeUser(username);
-						t.addUser(model.username);
-					}
-				}
-
-				// check for implicit team removal
-				if (originalUser != null) {
-					for (TeamModel team : originalUser.teams) {
-						if (!model.isTeamMember(team.name)) {
-							team.removeUser(username);
-						}
-					}
-				}
-			}
-			write();
-			return true;
-		} catch (Throwable t) {
-			if (originalUser != null) {
-				// restore original user
-				users.put(originalUser.username.toLowerCase(), originalUser);
-			} else {
-				// drop attempted add
-				users.remove(model.username.toLowerCase());
-			}
-			logger.error(MessageFormat.format("Failed to update user model {0}!", model.username),
-					t);
-		}
-		return false;
-	}
-
-	/**
-	 * Deletes the user object from the user service.
-	 * 
-	 * @param model
-	 * @return true if successful
-	 */
-	@Override
-	public boolean deleteUserModel(UserModel model) {
-		return deleteUser(model.username);
-	}
-
-	/**
-	 * Delete the user object with the specified username
-	 * 
-	 * @param username
-	 * @return true if successful
-	 */
-	@Override
-	public boolean deleteUser(String username) {
-		try {
-			// Read realm file
-			read();
-			UserModel model = users.remove(username.toLowerCase());
-			if (model == null) {
-				// user does not exist
-				return false;
-			}
-			// remove user from team
-			for (TeamModel team : model.teams) {
-				TeamModel t = teams.get(team.name);
-				if (t == null) {
-					// new team
-					team.removeUser(username);
-					teams.put(team.name.toLowerCase(), team);
-				} else {
-					// existing team
-					t.removeUser(username);
-				}
-			}
-			write();
-			return true;
-		} catch (Throwable t) {
-			logger.error(MessageFormat.format("Failed to delete user {0}!", username), t);
-		}
-		return false;
-	}
-
-	/**
-	 * Returns the list of all teams available to the login service.
-	 * 
-	 * @return list of all teams
-	 * @since 0.8.0
-	 */
-	@Override
-	public List<String> getAllTeamNames() {
-		read();
-		List<String> list = new ArrayList<String>(teams.keySet());
-		Collections.sort(list);
-		return list;
-	}
-
-	/**
-	 * Returns the list of all teams available to the login service.
-	 * 
-	 * @return list of all teams
-	 * @since 0.8.0
-	 */
-	@Override
-	public List<TeamModel> getAllTeams() {
-		read();
-		List<TeamModel> list = new ArrayList<TeamModel>(teams.values());
-		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
-	 */
-	@Override
-	public List<String> getTeamnamesForRepositoryRole(String role) {
-		List<String> list = new ArrayList<String>();
-		try {
-			read();
-			for (Map.Entry<String, TeamModel> entry : teams.entrySet()) {
-				TeamModel model = entry.getValue();
-				if (model.hasRepositoryPermission(role)) {
-					list.add(model.name);
-				}
-			}
-		} catch (Throwable t) {
-			logger.error(MessageFormat.format("Failed to get teamnames for role {0}!", role), t);
-		}
-		Collections.sort(list);
-		return list;
-	}
-
-	/**
-	 * 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
-	 * @return true if successful
-	 */
-	@Override
-	public boolean setTeamnamesForRepositoryRole(String role, List<String> teamnames) {
-		try {
-			Set<String> specifiedTeams = new HashSet<String>();
-			for (String teamname : teamnames) {
-				specifiedTeams.add(teamname.toLowerCase());
-			}
-
-			read();
-
-			// identify teams which require add or remove role
-			for (TeamModel team : teams.values()) {
-				// team has role, check against revised team list
-				if (specifiedTeams.contains(team.name.toLowerCase())) {
-					team.addRepositoryPermission(role);
-				} else {
-					// remove role from team
-					team.removeRepositoryPermission(role);
-				}
-			}
-
-			// persist changes
-			write();
-			return true;
-		} catch (Throwable t) {
-			logger.error(MessageFormat.format("Failed to set teams for role {0}!", role), t);
-		}
-		return false;
-	}
-
-	/**
-	 * Retrieve the team object for the specified team name.
-	 * 
-	 * @param teamname
-	 * @return a team object or null
-	 * @since 0.8.0
-	 */
-	@Override
-	public TeamModel getTeamModel(String teamname) {
-		read();
-		TeamModel model = teams.get(teamname.toLowerCase());
-		if (model != null) {
-			// clone the model, otherwise all changes to this object are
-			// live and unpersisted
-			model = DeepCopier.copy(model);
-		}
-		return model;
-	}
-
-	/**
-	 * Updates/writes a complete team object.
-	 * 
-	 * @param model
-	 * @return true if update is successful
-	 * @since 0.8.0
-	 */
-	@Override
-	public boolean updateTeamModel(TeamModel model) {
-		return updateTeamModel(model.name, model);
-	}
-
-	/**
-	 * Updates/writes all specified team objects.
-	 * 
-	 * @param models a list of team models
-	 * @return true if update is successful
-	 * @since 1.2.0
-	 */
-	@Override
-	public boolean updateTeamModels(List<TeamModel> models) {
-		try {
-			read();
-			for (TeamModel team : models) {
-				teams.put(team.name.toLowerCase(), team);
-			}
-			write();
-			return true;
-		} catch (Throwable t) {
-			logger.error(MessageFormat.format("Failed to update team {0} models!", models.size()), t);
-		}
-		return false;
-	}
-
-	/**
-	 * 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
-	 *            the team object to use for teamname
-	 * @return true if update is successful
-	 * @since 0.8.0
-	 */
-	@Override
-	public boolean updateTeamModel(String teamname, TeamModel model) {
-		TeamModel original = null;
-		try {
-			read();
-			original = teams.remove(teamname.toLowerCase());
-			teams.put(model.name.toLowerCase(), model);
-			write();
-			return true;
-		} catch (Throwable t) {
-			if (original != null) {
-				// restore original team
-				teams.put(original.name.toLowerCase(), original);
-			} else {
-				// drop attempted add
-				teams.remove(model.name.toLowerCase());
-			}
-			logger.error(MessageFormat.format("Failed to update team model {0}!", model.name), t);
-		}
-		return false;
-	}
-
-	/**
-	 * Deletes the team object from the user service.
-	 * 
-	 * @param model
-	 * @return true if successful
-	 * @since 0.8.0
-	 */
-	@Override
-	public boolean deleteTeamModel(TeamModel model) {
-		return deleteTeam(model.name);
-	}
-
-	/**
-	 * Delete the team object with the specified teamname
-	 * 
-	 * @param teamname
-	 * @return true if successful
-	 * @since 0.8.0
-	 */
-	@Override
-	public boolean deleteTeam(String teamname) {
-		try {
-			// Read realm file
-			read();
-			teams.remove(teamname.toLowerCase());
-			write();
-			return true;
-		} catch (Throwable t) {
-			logger.error(MessageFormat.format("Failed to delete team {0}!", teamname), t);
-		}
-		return false;
-	}
-
-	/**
-	 * Returns the list of all users available to the login service.
-	 * 
-	 * @return list of all usernames
-	 */
-	@Override
-	public List<String> getAllUsernames() {
-		read();
-		List<String> list = new ArrayList<String>(users.keySet());
-		Collections.sort(list);
-		return list;
-	}
-	
-	/**
-	 * Returns the list of all users available to the login service.
-	 * 
-	 * @return list of all usernames
-	 */
-	@Override
-	public List<UserModel> getAllUsers() {
-		read();
-		List<UserModel> list = new ArrayList<UserModel>(users.values());
-		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
-	 */
-	@Override
-	public List<String> getUsernamesForRepositoryRole(String role) {
-		List<String> list = new ArrayList<String>();
-		try {
-			read();
-			for (Map.Entry<String, UserModel> entry : users.entrySet()) {
-				UserModel model = entry.getValue();
-				if (model.hasRepositoryPermission(role)) {
-					list.add(model.username);
-				}
-			}
-		} catch (Throwable t) {
-			logger.error(MessageFormat.format("Failed to get usernames for role {0}!", role), t);
-		}
-		Collections.sort(list);
-		return list;
-	}
-
-	/**
-	 * 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
-	 * @return true if successful
-	 */
-	@Override
-	@Deprecated
-	public boolean setUsernamesForRepositoryRole(String role, List<String> usernames) {
-		try {
-			Set<String> specifiedUsers = new HashSet<String>();
-			for (String username : usernames) {
-				specifiedUsers.add(username.toLowerCase());
-			}
-
-			read();
-
-			// identify users which require add or remove role
-			for (UserModel user : users.values()) {
-				// user has role, check against revised user list
-				if (specifiedUsers.contains(user.username.toLowerCase())) {
-					user.addRepositoryPermission(role);
-				} else {
-					// remove role from user
-					user.removeRepositoryPermission(role);
-				}
-			}
-
-			// persist changes
-			write();
-			return true;
-		} catch (Throwable t) {
-			logger.error(MessageFormat.format("Failed to set usernames for role {0}!", role), t);
-		}
-		return false;
-	}
-
-	/**
-	 * Renames a repository role.
-	 * 
-	 * @param oldRole
-	 * @param newRole
-	 * @return true if successful
-	 */
-	@Override
-	public boolean renameRepositoryRole(String oldRole, String newRole) {
-		try {
-			read();
-			// identify users which require role rename
-			for (UserModel model : users.values()) {
-				if (model.hasRepositoryPermission(oldRole)) {
-					AccessPermission permission = model.removeRepositoryPermission(oldRole);
-					model.setRepositoryPermission(newRole, permission);
-				}
-			}
-
-			// identify teams which require role rename
-			for (TeamModel model : teams.values()) {
-				if (model.hasRepositoryPermission(oldRole)) {
-					AccessPermission permission = model.removeRepositoryPermission(oldRole);
-					model.setRepositoryPermission(newRole, permission);
-				}
-			}
-			// persist changes
-			write();
-			return true;
-		} catch (Throwable t) {
-			logger.error(
-					MessageFormat.format("Failed to rename role {0} to {1}!", oldRole, newRole), t);
-		}
-		return false;
-	}
-
-	/**
-	 * Removes a repository role from all users.
-	 * 
-	 * @param role
-	 * @return true if successful
-	 */
-	@Override
-	public boolean deleteRepositoryRole(String role) {
-		try {
-			read();
-
-			// identify users which require role rename
-			for (UserModel user : users.values()) {
-				user.removeRepositoryPermission(role);
-			}
-
-			// identify teams which require role rename
-			for (TeamModel team : teams.values()) {
-				team.removeRepositoryPermission(role);
-			}
-
-			// persist changes
-			write();
-			return true;
-		} catch (Throwable t) {
-			logger.error(MessageFormat.format("Failed to delete role {0}!", role), t);
-		}
-		return false;
-	}
-
-	/**
-	 * Writes the properties file.
-	 * 
-	 * @param properties
-	 * @throws IOException
-	 */
-	private synchronized void write() throws IOException {
-		// Write a temporary copy of the users file
-		File realmFileCopy = new File(realmFile.getAbsolutePath() + ".tmp");
-
-		StoredConfig config = new FileBasedConfig(realmFileCopy, FS.detect());
-
-		// write users
-		for (UserModel model : users.values()) {
-			if (!StringUtils.isEmpty(model.password)) {
-				config.setString(USER, model.username, PASSWORD, model.password);
-			}
-			if (!StringUtils.isEmpty(model.cookie)) {
-				config.setString(USER, model.username, COOKIE, model.cookie);
-			}
-			if (!StringUtils.isEmpty(model.displayName)) {
-				config.setString(USER, model.username, DISPLAYNAME, model.displayName);
-			}
-			if (!StringUtils.isEmpty(model.emailAddress)) {
-				config.setString(USER, model.username, EMAILADDRESS, model.emailAddress);
-			}
-			if (!StringUtils.isEmpty(model.organizationalUnit)) {
-				config.setString(USER, model.username, ORGANIZATIONALUNIT, model.organizationalUnit);
-			}
-			if (!StringUtils.isEmpty(model.organization)) {
-				config.setString(USER, model.username, ORGANIZATION, model.organization);
-			}
-			if (!StringUtils.isEmpty(model.locality)) {
-				config.setString(USER, model.username, LOCALITY, model.locality);
-			}
-			if (!StringUtils.isEmpty(model.stateProvince)) {
-				config.setString(USER, model.username, STATEPROVINCE, model.stateProvince);
-			}
-			if (!StringUtils.isEmpty(model.countryCode)) {
-				config.setString(USER, model.username, COUNTRYCODE, model.countryCode);
-			}
-
-			// user roles
-			List<String> roles = new ArrayList<String>();
-			if (model.canAdmin) {
-				roles.add(Constants.ADMIN_ROLE);
-			}
-			if (model.canFork) {
-				roles.add(Constants.FORK_ROLE);
-			}
-			if (model.canCreate) {
-				roles.add(Constants.CREATE_ROLE);
-			}
-			if (model.excludeFromFederation) {
-				roles.add(Constants.NOT_FEDERATED_ROLE);
-			}
-			if (roles.size() == 0) {
-				// we do this to ensure that user record with no password
-				// is written.  otherwise, StoredConfig optimizes that account
-				// away. :(
-				roles.add(Constants.NO_ROLE);
-			}
-			config.setStringList(USER, model.username, ROLE, roles);
-
-			// discrete repository permissions
-			if (model.permissions != null && !model.canAdmin) {
-				List<String> permissions = new ArrayList<String>();
-				for (Map.Entry<String, AccessPermission> entry : model.permissions.entrySet()) {
-					if (entry.getValue().exceeds(AccessPermission.NONE)) {
-						permissions.add(entry.getValue().asRole(entry.getKey()));
-					}
-				}
-				config.setStringList(USER, model.username, REPOSITORY, permissions);
-			}
-		}
-
-		// write teams
-		for (TeamModel model : teams.values()) {
-			// team roles
-			List<String> roles = new ArrayList<String>();
-			if (model.canAdmin) {
-				roles.add(Constants.ADMIN_ROLE);
-			}
-			if (model.canFork) {
-				roles.add(Constants.FORK_ROLE);
-			}
-			if (model.canCreate) {
-				roles.add(Constants.CREATE_ROLE);
-			}
-			if (roles.size() == 0) {
-				// we do this to ensure that team record is written.
-				// Otherwise, StoredConfig might optimizes that record away.
-				roles.add(Constants.NO_ROLE);
-			}
-			config.setStringList(TEAM, model.name, ROLE, roles);
-			
-			if (!model.canAdmin) {
-				// write team permission for non-admin teams
-				if (model.permissions == null) {
-					// null check on "final" repositories because JSON-sourced TeamModel
-					// can have a null repositories object
-					if (!ArrayUtils.isEmpty(model.repositories)) {
-						config.setStringList(TEAM, model.name, REPOSITORY, new ArrayList<String>(
-								model.repositories));
-					}
-				} else {
-					// discrete repository permissions
-					List<String> permissions = new ArrayList<String>();
-					for (Map.Entry<String, AccessPermission> entry : model.permissions.entrySet()) {
-						if (entry.getValue().exceeds(AccessPermission.NONE)) {
-							// code:repository (e.g. RW+:~james/myrepo.git
-							permissions.add(entry.getValue().asRole(entry.getKey()));
-						}
-					}
-					config.setStringList(TEAM, model.name, REPOSITORY, permissions);
-				}
-			}
-
-			// null check on "final" users because JSON-sourced TeamModel
-			// can have a null users object
-			if (!ArrayUtils.isEmpty(model.users)) {
-				config.setStringList(TEAM, model.name, USER, new ArrayList<String>(model.users));
-			}
-
-			// null check on "final" mailing lists because JSON-sourced
-			// TeamModel can have a null users object
-			if (!ArrayUtils.isEmpty(model.mailingLists)) {
-				config.setStringList(TEAM, model.name, MAILINGLIST, new ArrayList<String>(
-						model.mailingLists));
-			}
-
-			// null check on "final" preReceiveScripts because JSON-sourced
-			// TeamModel can have a null preReceiveScripts object
-			if (!ArrayUtils.isEmpty(model.preReceiveScripts)) {
-				config.setStringList(TEAM, model.name, PRERECEIVE, model.preReceiveScripts);
-			}
-
-			// null check on "final" postReceiveScripts because JSON-sourced
-			// TeamModel can have a null postReceiveScripts object
-			if (!ArrayUtils.isEmpty(model.postReceiveScripts)) {
-				config.setStringList(TEAM, model.name, POSTRECEIVE, model.postReceiveScripts);
-			}
-		}
-
-		config.save();
-		// manually set the forceReload flag because not all JVMs support real
-		// millisecond resolution of lastModified. (issue-55)
-		forceReload = true;
-
-		// If the write is successful, delete the current file and rename
-		// the temporary copy to the original filename.
-		if (realmFileCopy.exists() && realmFileCopy.length() > 0) {
-			if (realmFile.exists()) {
-				if (!realmFile.delete()) {
-					throw new IOException(MessageFormat.format("Failed to delete {0}!",
-							realmFile.getAbsolutePath()));
-				}
-			}
-			if (!realmFileCopy.renameTo(realmFile)) {
-				throw new IOException(MessageFormat.format("Failed to rename {0} to {1}!",
-						realmFileCopy.getAbsolutePath(), realmFile.getAbsolutePath()));
-			}
-		} else {
-			throw new IOException(MessageFormat.format("Failed to save {0}!",
-					realmFileCopy.getAbsolutePath()));
-		}
-	}
-
-	/**
-	 * Reads the realm file and rebuilds the in-memory lookup tables.
-	 */
-	protected synchronized void read() {
-		if (realmFile.exists() && (forceReload || (realmFile.lastModified() != lastModified))) {
-			forceReload = false;
-			lastModified = realmFile.lastModified();
-			users.clear();
-			cookies.clear();
-			teams.clear();
-
-			try {
-				StoredConfig config = new FileBasedConfig(realmFile, FS.detect());
-				config.load();
-				Set<String> usernames = config.getSubsections(USER);
-				for (String username : usernames) {
-					UserModel user = new UserModel(username.toLowerCase());
-					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);
-					user.organization = config.getString(USER, username, ORGANIZATION);
-					user.locality = config.getString(USER, username, LOCALITY);
-					user.stateProvince = config.getString(USER, username, STATEPROVINCE);
-					user.countryCode = config.getString(USER, username, COUNTRYCODE);
-					user.cookie = config.getString(USER, username, COOKIE);
-					if (StringUtils.isEmpty(user.cookie) && !StringUtils.isEmpty(user.password)) {
-						user.cookie = StringUtils.getSHA1(user.username + user.password);
-					}
-
-					// user roles
-					Set<String> roles = new HashSet<String>(Arrays.asList(config.getStringList(
-							USER, username, ROLE)));
-					user.canAdmin = roles.contains(Constants.ADMIN_ROLE);
-					user.canFork = roles.contains(Constants.FORK_ROLE);
-					user.canCreate = roles.contains(Constants.CREATE_ROLE);
-					user.excludeFromFederation = roles.contains(Constants.NOT_FEDERATED_ROLE);
-
-					// repository memberships
-					if (!user.canAdmin) {
-						// non-admin, read permissions
-						Set<String> repositories = new HashSet<String>(Arrays.asList(config
-								.getStringList(USER, username, REPOSITORY)));
-						for (String repository : repositories) {
-							user.addRepositoryPermission(repository);
-						}
-					}
-
-					// update cache
-					users.put(user.username, user);
-					if (!StringUtils.isEmpty(user.cookie)) {
-						cookies.put(user.cookie, user);
-					}
-				}
-
-				// load the teams
-				Set<String> teamnames = config.getSubsections(TEAM);
-				for (String teamname : teamnames) {
-					TeamModel team = new TeamModel(teamname);
-					Set<String> roles = new HashSet<String>(Arrays.asList(config.getStringList(
-							TEAM, teamname, ROLE)));
-					team.canAdmin = roles.contains(Constants.ADMIN_ROLE);
-					team.canFork = roles.contains(Constants.FORK_ROLE);
-					team.canCreate = roles.contains(Constants.CREATE_ROLE);
-					
-					if (!team.canAdmin) {
-						// non-admin team, read permissions
-						team.addRepositoryPermissions(Arrays.asList(config.getStringList(TEAM, teamname,
-								REPOSITORY)));
-					}
-					team.addUsers(Arrays.asList(config.getStringList(TEAM, teamname, USER)));
-					team.addMailingLists(Arrays.asList(config.getStringList(TEAM, teamname,
-							MAILINGLIST)));
-					team.preReceiveScripts.addAll(Arrays.asList(config.getStringList(TEAM,
-							teamname, PRERECEIVE)));
-					team.postReceiveScripts.addAll(Arrays.asList(config.getStringList(TEAM,
-							teamname, POSTRECEIVE)));
-
-					teams.put(team.name.toLowerCase(), team);
-
-					// set the teams on the users
-					for (String user : team.users) {
-						UserModel model = users.get(user);
-						if (model != null) {
-							model.teams.add(team);
-						}
-					}
-				}
-			} catch (Exception e) {
-				logger.error(MessageFormat.format("Failed to read {0}", realmFile), e);
-			}
-		}
-	}
-
-	protected long lastModified() {
-		return lastModified;
-	}
-
-	@Override
-	public String toString() {
-		return getClass().getSimpleName() + "(" + realmFile.getAbsolutePath() + ")";
-	}
-}
diff --git a/src/com/gitblit/Constants.java b/src/com/gitblit/Constants.java
deleted file mode 100644
index bcca8c7..0000000
--- a/src/com/gitblit/Constants.java
+++ /dev/null
@@ -1,429 +0,0 @@
-/*
- * Copyright 2011 gitblit.com.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.gitblit;
-
-import java.lang.annotation.Documented;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-
-/**
- * Constant values used by Gitblit.
- * 
- * @author James Moger
- * 
- */
-public class Constants {
-
-	public static final String NAME = "Gitblit";
-
-	public static final String FULL_NAME = "Gitblit - a pure Java Git solution";
-
-	// The build script extracts this exact line so be careful editing it
-	// and only use A-Z a-z 0-9 .-_ in the string.
-	public static final String VERSION = "1.3.0-SNAPSHOT";
-
-	// The build script extracts this exact line so be careful editing it
-	// and only use A-Z a-z 0-9 .-_ in the string.
-	public static final String VERSION_DATE = "PENDING";
-
-	// The build script extracts this exact line so be careful editing it
-	// and only use A-Z a-z 0-9 .-_ in the string.
-	public static final String JGIT_VERSION = "JGit 2.2.0 (201212191850-r)";
-
-	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 PROPERTIES_FILE = "gitblit.properties";
-
-	public static final String GIT_PATH = "/git/";
-
-	public static final String ZIP_PATH = "/zip/";
-
-	public static final String SYNDICATION_PATH = "/feed/";
-
-	public static final String FEDERATION_PATH = "/federation/";
-
-	public static final String RPC_PATH = "/rpc/";
-	
-	public static final String PAGES= "/pages/";
-
-	public static final String BORDER = "***********************************************************";
-
-	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 R_GITBLIT = "refs/gitblit/";
-	
-	public static final String baseFolder = "baseFolder";
-	
-	public static final String baseFolder$ = "${" + baseFolder + "}";
-	
-	public static final String contextFolder$ = "${contextFolder}";
-	
-	public static String getGitBlitVersion() {
-		return NAME + " v" + VERSION;
-	}
-	
-	/**
-	 * Enumeration representing the four access restriction levels.
-	 */
-	public static enum AccessRestrictionType {
-		NONE, PUSH, CLONE, VIEW;
-
-		public static AccessRestrictionType fromName(String name) {
-			for (AccessRestrictionType type : values()) {
-				if (type.name().equalsIgnoreCase(name)) {
-					return type;
-				}
-			}
-			return NONE;
-		}
-
-		public boolean exceeds(AccessRestrictionType type) {
-			return this.ordinal() > type.ordinal();
-		}
-
-		public boolean atLeast(AccessRestrictionType type) {
-			return this.ordinal() >= type.ordinal();
-		}
-
-		public String toString() {
-			return name();
-		}
-	}
-	
-	/**
-	 * 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)) {
-					return type;
-				}
-			}
-			return NAMED;
-		}
-		
-		public String toString() {
-			return name();
-		}
-	}
-
-
-	/**
-	 * Enumeration representing the types of federation tokens.
-	 */
-	public static enum FederationToken {
-		ALL, USERS_AND_REPOSITORIES, REPOSITORIES;
-
-		public static FederationToken fromName(String name) {
-			for (FederationToken type : values()) {
-				if (type.name().equalsIgnoreCase(name)) {
-					return type;
-				}
-			}
-			return REPOSITORIES;
-		}
-
-		public String toString() {
-			return name();
-		}
-	}
-
-	/**
-	 * Enumeration representing the types of federation requests.
-	 */
-	public static enum FederationRequest {
-		POKE, PROPOSAL, PULL_REPOSITORIES, PULL_USERS, PULL_TEAMS, PULL_SETTINGS, PULL_SCRIPTS, STATUS;
-
-		public static FederationRequest fromName(String name) {
-			for (FederationRequest type : values()) {
-				if (type.name().equalsIgnoreCase(name)) {
-					return type;
-				}
-			}
-			return PULL_REPOSITORIES;
-		}
-
-		public String toString() {
-			return name();
-		}
-	}
-
-	/**
-	 * Enumeration representing the statii of federation requests.
-	 */
-	public static enum FederationPullStatus {
-		PENDING, FAILED, SKIPPED, PULLED, MIRRORED, NOCHANGE, EXCLUDED;
-
-		public static FederationPullStatus fromName(String name) {
-			for (FederationPullStatus type : values()) {
-				if (type.name().equalsIgnoreCase(name)) {
-					return type;
-				}
-			}
-			return PENDING;
-		}
-
-		@Override
-		public String toString() {
-			return name();
-		}
-	}
-
-	/**
-	 * Enumeration representing the federation types.
-	 */
-	public static enum FederationStrategy {
-		EXCLUDE, FEDERATE_THIS, FEDERATE_ORIGIN;
-
-		public static FederationStrategy fromName(String name) {
-			for (FederationStrategy type : values()) {
-				if (type.name().equalsIgnoreCase(name)) {
-					return type;
-				}
-			}
-			return FEDERATE_THIS;
-		}
-
-		public boolean exceeds(FederationStrategy type) {
-			return this.ordinal() > type.ordinal();
-		}
-
-		public boolean atLeast(FederationStrategy type) {
-			return this.ordinal() >= type.ordinal();
-		}
-
-		@Override
-		public String toString() {
-			return name();
-		}
-	}
-
-	/**
-	 * Enumeration representing the possible results of federation proposal
-	 * requests.
-	 */
-	public static enum FederationProposalResult {
-		ERROR, FEDERATION_DISABLED, MISSING_DATA, NO_PROPOSALS, NO_POKE, ACCEPTED;
-
-		@Override
-		public String toString() {
-			return name();
-		}
-	}
-
-	/**
-	 * Enumeration representing the possible remote procedure call requests from
-	 * a client.
-	 */
-	public static enum RpcRequest {
-		// Order is important here.  anything above LIST_SETTINGS requires
-		// administrator privileges and web.allowRpcManagement.
-		CLEAR_REPOSITORY_CACHE, GET_PROTOCOL, LIST_REPOSITORIES, LIST_BRANCHES, LIST_SETTINGS,
-		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_FEDERATION_REGISTRATIONS, LIST_FEDERATION_RESULTS, LIST_FEDERATION_PROPOSALS, LIST_FEDERATION_SETS,
-		EDIT_SETTINGS, LIST_STATUS;
-
-		public static RpcRequest fromName(String name) {
-			for (RpcRequest type : values()) {
-				if (type.name().equalsIgnoreCase(name)) {
-					return type;
-				}
-			}
-			return null;
-		}		
-
-		public boolean exceeds(RpcRequest type) {
-			return this.ordinal() > type.ordinal();
-		}
-
-		@Override
-		public String toString() {
-			return name();
-		}
-	}
-
-	/**
-	 * Enumeration of the search types.
-	 */
-	public static enum SearchType {
-		AUTHOR, COMMITTER, COMMIT;
-	
-		public static SearchType forName(String name) {
-			for (SearchType type : values()) {
-				if (type.name().equalsIgnoreCase(name)) {
-					return type;
-				}
-			}
-			return COMMIT;
-		}
-	
-		@Override
-		public String toString() {
-			return name().toLowerCase();
-		}
-	}
-	
-	/**
-	 * The types of objects that can be indexed and queried.
-	 */
-	public static enum SearchObjectType {
-		commit, blob, issue;
-
-		static SearchObjectType fromName(String name) {
-			for (SearchObjectType value : values()) {
-				if (value.name().equals(name)) {
-					return value;
-				}
-			}
-			return null;
-		}
-	}
-	
-	/**
-	 * 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;
-		}
-		
-		public boolean atLeast(AccessPermission perm) {
-			return ordinal() >= perm.ordinal();
-		}
-
-		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) {
-				// legacy/undefined assume full permissions
-				return AccessPermission.LEGACY;
-			} else {
-				// code:repository
-				return AccessPermission.fromCode(fields[0]);
-			}
-		}
-		
-		public static String repositoryFromRole(String role) {
-			String [] fields = role.split(":", 2);
-			if (fields.length == 1) {
-				// legacy/undefined assume full permissions
-				return role;
-			} else {
-				// code:repository
-				return fields[1];
-			}
-		}
-		
-		public static AccessPermission fromCode(String code) {
-			for (AccessPermission perm : values()) {
-				if (perm.code.equalsIgnoreCase(code)) {
-					return perm;
-				}
-			}
-			return AccessPermission.NONE;
-		}
-	}
-	
-	public static enum RegistrantType {
-		REPOSITORY, USER, TEAM;
-	}
-	
-	public static enum PermissionType {
-		MISSING, EXPLICIT, TEAM, REGEX, OWNER, ADMINISTRATOR;
-	}
-	
-	public static enum GCStatus {
-		READY, COLLECTING;
-		
-		public boolean exceeds(GCStatus s) {
-			return ordinal() > s.ordinal();
-		}
-	}
-
-	public static enum AuthenticationType {
-		CREDENTIALS, COOKIE, CERTIFICATE, CONTAINER;
-		
-		public boolean isStandard() {
-			return ordinal() <= COOKIE.ordinal();
-		}
-	}
-	
-	public static enum AccountType {
-		LOCAL, LDAP, REDMINE;
-		
-		public boolean isLocal() {
-			return this == LOCAL;
-		}
-	}
-
-	@Documented
-	@Retention(RetentionPolicy.RUNTIME)
-	public @interface Unused {
-	}
-}
diff --git a/src/com/gitblit/DownloadZipServlet.java b/src/com/gitblit/DownloadZipServlet.java
deleted file mode 100644
index 0feee87..0000000
--- a/src/com/gitblit/DownloadZipServlet.java
+++ /dev/null
@@ -1,210 +0,0 @@
-/*
- * Copyright 2011 gitblit.com.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.gitblit;
-
-import java.io.IOException;
-import java.text.MessageFormat;
-import java.text.ParseException;
-import java.util.Date;
-
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletResponse;
-
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.revwalk.RevCommit;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.gitblit.utils.CompressionUtils;
-import com.gitblit.utils.JGitUtils;
-import com.gitblit.utils.MarkdownUtils;
-import com.gitblit.utils.StringUtils;
-
-/**
- * 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)) {
-					return format;
-				}
-			}
-			return zip;
-		}
-	}
-
-	public DownloadZipServlet() {
-		super();
-	}
-
-	/**
-	 * Returns an url to this servlet for the specified parameters.
-	 * 
-	 * @param baseURL
-	 * @param repository
-	 * @param objectId
-	 * @param path
-	 * @param format
-	 * @return an url
-	 */
-	public static String asLink(String baseURL, String repository, String objectId, String path, Format format) {
-		if (baseURL.length() > 0 && baseURL.charAt(baseURL.length() - 1) == '/') {
-			baseURL = baseURL.substring(0, baseURL.length() - 1);
-		}
-		return baseURL + Constants.ZIP_PATH + "?r=" + repository
-				+ (path == null ? "" : ("&p=" + path))
-				+ (objectId == null ? "" : ("&h=" + objectId))
-				+ (format == null ? "" : ("&format=" + format.name()));
-	}
-
-	/**
-	 * Creates a zip stream from the repository of the requested data.
-	 * 
-	 * @param request
-	 * @param response
-	 * @throws javax.servlet.ServletException
-	 * @throws java.io.IOException
-	 */
-	private void processRequest(javax.servlet.http.HttpServletRequest request,
-			javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException,
-			java.io.IOException {
-		if (!GitBlit.getBoolean(Keys.web.allowZipDownloads, true)) {
-			logger.warn("Zip downloads are disabled");
-			response.sendError(HttpServletResponse.SC_FORBIDDEN);
-			return;
-		}
-		
-		Format format = Format.zip;
-		String repository = request.getParameter("r");
-		String basePath = request.getParameter("p");
-		String objectId = request.getParameter("h");
-		String f = request.getParameter("format");
-		if (!StringUtils.isEmpty(f)) {
-			format = Format.fromName(f);
-		}
-		
-		try {
-			String name = repository;
-			if (name.indexOf('/') > -1) {
-				name = name.substring(name.lastIndexOf('/') + 1);
-			}
-			name = StringUtils.stripDotGit(name);
-
-			if (!StringUtils.isEmpty(basePath)) {
-				name += "-" + basePath.replace('/', '_');
-			}
-			if (!StringUtils.isEmpty(objectId)) {
-				name += "-" + objectId;
-			}
-						
-			Repository r = GitBlit.self().getRepository(repository);
-			if (r == null) {
-				if (GitBlit.self().isCollectingGarbage(repository)) {
-					error(response, MessageFormat.format("# Error\nGitblit is busy collecting garbage in {0}", repository));
-					return;
-				} else {
-					error(response, MessageFormat.format("# Error\nFailed to find repository {0}", repository));
-					return;
-				}
-			}
-			RevCommit commit = JGitUtils.getCommit(r, objectId);
-			if (commit == null) {
-				error(response, MessageFormat.format("# Error\nFailed to find commit {0}", objectId));
-				r.close();
-				return;
-			}
-			Date date = JGitUtils.getCommitDate(commit);
-
-			String contentType = "application/octet-stream";
-			response.setContentType(contentType + "; charset=" + response.getCharacterEncoding());
-			response.setHeader("Content-Disposition", "attachment; filename=\"" + name + format.extension + "\"");
-			response.setDateHeader("Last-Modified", date.getTime());
-			response.setHeader("Cache-Control", "no-cache");
-			response.setHeader("Pragma", "no-cache");
-			response.setDateHeader("Expires", 0);
-
-			try {
-				switch (format) {
-				case zip:
-					CompressionUtils.zip(r, basePath, objectId, response.getOutputStream());
-					break;
-				case tar:
-					CompressionUtils.tar(r, basePath, objectId, response.getOutputStream());
-					break;
-				case gz:
-					CompressionUtils.gz(r, basePath, objectId, response.getOutputStream());
-					break;
-				case xz:
-					CompressionUtils.xz(r, basePath, objectId, response.getOutputStream());
-					break;
-				case bzip2:
-					CompressionUtils.bzip2(r, basePath, objectId, response.getOutputStream());
-					break;
-				}
-				
-				response.flushBuffer();
-			} catch (Throwable t) {
-				logger.error("Failed to write attachment to client", t);
-			}
-
-			// close the repository
-			r.close();
-		} catch (Throwable t) {
-			logger.error("Failed to write attachment to client", t);
-		}
-	}
-
-	private void error(HttpServletResponse response, String mkd) throws ServletException,
-			IOException, ParseException {
-		String content = MarkdownUtils.transformMarkdown(mkd);
-		response.setContentType("text/html; charset=" + Constants.ENCODING);
-		response.getWriter().write(content);
-	}
-
-	@Override
-	protected void doPost(javax.servlet.http.HttpServletRequest request,
-			javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException,
-			java.io.IOException {
-		processRequest(request, response);
-	}
-
-	@Override
-	protected void doGet(javax.servlet.http.HttpServletRequest request,
-			javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException,
-			java.io.IOException {
-		processRequest(request, response);
-	}
-}
diff --git a/src/com/gitblit/FederationClient.java b/src/com/gitblit/FederationClient.java
deleted file mode 100644
index f666139..0000000
--- a/src/com/gitblit/FederationClient.java
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * Copyright 2011 gitblit.com.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.gitblit;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.List;
-
-import com.beust.jcommander.JCommander;
-import com.beust.jcommander.Parameter;
-import com.beust.jcommander.ParameterException;
-import com.beust.jcommander.Parameters;
-import com.gitblit.models.FederationModel;
-import com.gitblit.utils.FederationUtils;
-import com.gitblit.utils.StringUtils;
-
-/**
- * Command-line client to pull federated Gitblit repositories.
- * 
- * @author James Moger
- * 
- */
-public class FederationClient {
-
-	public static void main(String[] args) {
-		Params params = new Params();
-		JCommander jc = new JCommander(params);
-		try {
-			jc.parse(args);
-		} catch (ParameterException t) {
-			usage(jc, t);
-		}
-
-		IStoredSettings settings = new FileSettings(params.registrationsFile);
-		List<FederationModel> registrations = new ArrayList<FederationModel>();
-		if (StringUtils.isEmpty(params.url)) {
-			registrations.addAll(FederationUtils.getFederationRegistrations(settings));
-		} else {
-			if (StringUtils.isEmpty(params.token)) {
-				System.out.println("Must specify --token parameter!");
-				System.exit(0);
-			}
-			FederationModel model = new FederationModel("Gitblit");
-			model.url = params.url;
-			model.token = params.token;
-			model.mirror = params.mirror;
-			model.bare = params.bare;
-			model.frequency = params.frequency;
-			model.folder = "";
-			registrations.add(model);
-		}
-		if (registrations.size() == 0) {
-			System.out.println("No Federation Registrations!  Nothing to do.");
-			System.exit(0);
-		}
-		
-		System.out.println("Gitblit Federation Client v" + Constants.VERSION + " (" + Constants.VERSION_DATE + ")");
-
-		// command-line specified repositories folder
-		if (!StringUtils.isEmpty(params.repositoriesFolder)) {
-			settings.overrideSetting(Keys.git.repositoriesFolder, new File(
-					params.repositoriesFolder).getAbsolutePath());
-		}
-
-		// configure the Gitblit singleton for minimal, non-server operation
-		GitBlit.self().configureContext(settings, null, false);
-		FederationPullExecutor executor = new FederationPullExecutor(registrations, params.isDaemon);
-		executor.run();
-		if (!params.isDaemon) {
-			System.out.println("Finished.");
-			System.exit(0);
-		}
-	}
-
-	private static void usage(JCommander jc, ParameterException t) {
-		System.out.println(Constants.getGitBlitVersion());
-		System.out.println();
-		if (t != null) {
-			System.out.println(t.getMessage());
-			System.out.println();
-		}
-
-		if (jc != null) {
-			jc.usage();
-		}
-		System.exit(0);
-	}
-
-	/**
-	 * JCommander Parameters class for FederationClient.
-	 */
-	@Parameters(separators = " ")
-	private static class Params {
-
-		@Parameter(names = { "--registrations" }, description = "Gitblit Federation Registrations File", required = false)
-		public String registrationsFile = "federation.properties";
-
-		@Parameter(names = { "--daemon" }, description = "Runs in daemon mode to schedule and pull repositories", required = false)
-		public boolean isDaemon;
-
-		@Parameter(names = { "--url" }, description = "URL of Gitblit instance to mirror from", required = false)
-		public String url;
-
-		@Parameter(names = { "--mirror" }, description = "Mirror repositories", required = false)
-		public boolean mirror;
-
-		@Parameter(names = { "--bare" }, description = "Create bare repositories", required = false)
-		public boolean bare;
-
-		@Parameter(names = { "--token" }, description = "Federation Token", required = false)
-		public String token;
-
-		@Parameter(names = { "--frequency" }, description = "Period to wait between pull attempts (requires --daemon)", required = false)
-		public String frequency = "60 mins";
-
-		@Parameter(names = { "--repositoriesFolder" }, description = "Destination folder for cloned repositories", required = false)
-		public String repositoriesFolder;
-
-	}
-}
diff --git a/src/com/gitblit/FederationClientLauncher.java b/src/com/gitblit/FederationClientLauncher.java
deleted file mode 100644
index 80f5a3d..0000000
--- a/src/com/gitblit/FederationClientLauncher.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright 2011 gitblit.com.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.gitblit;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.Collections;
-import java.util.List;
-
-import com.gitblit.build.Build;
-
-/**
- * Downloads dependencies and launches command-line Federation client.
- * 
- * @author James Moger
- * 
- */
-public class FederationClientLauncher {
-
-	public static void main(String[] args) {
-		// download federation client runtime dependencies
-		Build.federationClient();
-
-		File libFolder = new File("ext");
-		List<File> jars = Launcher.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);
-		Collections.reverse(jars);
-		for (File jar : jars) {
-			try {
-				Launcher.addJarFile(jar);
-			} catch (IOException e) {
-
-			}
-		}
-		
-		FederationClient.main(args);
-	}
-}
diff --git a/src/com/gitblit/FederationPullExecutor.java b/src/com/gitblit/FederationPullExecutor.java
deleted file mode 100644
index ad1022c..0000000
--- a/src/com/gitblit/FederationPullExecutor.java
+++ /dev/null
@@ -1,523 +0,0 @@
-/*
- * Copyright 2011 gitblit.com.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.gitblit;
-
-import static org.eclipse.jgit.lib.Constants.DOT_GIT_EXT;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.net.InetAddress;
-import java.text.MessageFormat;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Properties;
-import java.util.Set;
-import java.util.concurrent.TimeUnit;
-
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.lib.StoredConfig;
-import org.eclipse.jgit.revwalk.RevCommit;
-import org.eclipse.jgit.transport.CredentialsProvider;
-import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.gitblit.Constants.AccessPermission;
-import com.gitblit.Constants.FederationPullStatus;
-import com.gitblit.Constants.FederationStrategy;
-import com.gitblit.GitBlitException.ForbiddenException;
-import com.gitblit.models.FederationModel;
-import com.gitblit.models.RefModel;
-import com.gitblit.models.RepositoryModel;
-import com.gitblit.models.TeamModel;
-import com.gitblit.models.UserModel;
-import com.gitblit.utils.ArrayUtils;
-import com.gitblit.utils.FederationUtils;
-import com.gitblit.utils.FileUtils;
-import com.gitblit.utils.JGitUtils;
-import com.gitblit.utils.JGitUtils.CloneResult;
-import com.gitblit.utils.StringUtils;
-import com.gitblit.utils.TimeUtils;
-
-/**
- * FederationPullExecutor pulls repository updates and, optionally, user
- * accounts and server settings from registered Gitblit instances.
- */
-public class FederationPullExecutor implements Runnable {
-
-	private final Logger logger = LoggerFactory.getLogger(FederationPullExecutor.class);
-
-	private final List<FederationModel> registrations;
-
-	private final boolean isDaemon;
-
-	/**
-	 * Constructor for specifying a single federation registration. This
-	 * constructor is used to schedule the next pull execution.
-	 * 
-	 * @param registration
-	 */
-	private FederationPullExecutor(FederationModel registration) {
-		this(Arrays.asList(registration), true);
-	}
-
-	/**
-	 * 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
-	 *            false, the federation pull operation is executed once.
-	 */
-	public FederationPullExecutor(List<FederationModel> registrations, boolean isDaemon) {
-		this.registrations = registrations;
-		this.isDaemon = isDaemon;
-	}
-
-	/**
-	 * Run method for this pull executor.
-	 */
-	@Override
-	public void run() {
-		for (FederationModel registration : registrations) {
-			FederationPullStatus was = registration.getLowestStatus();
-			try {
-				Date now = new Date(System.currentTimeMillis());
-				pull(registration);
-				sendStatusAcknowledgment(registration);
-				registration.lastPull = now;
-				FederationPullStatus is = registration.getLowestStatus();
-				if (is.ordinal() < was.ordinal()) {
-					// the status for this registration has downgraded
-					logger.warn("Federation pull status of {0} is now {1}", registration.name,
-							is.name());
-					if (registration.notifyOnError) {
-						String message = "Federation pull of " + registration.name + " @ "
-								+ registration.url + " is now at " + is.name();
-						GitBlit.self()
-								.sendMailToAdministrators(
-										"Pull Status of " + registration.name + " is " + is.name(),
-										message);
-					}
-				}
-			} catch (Throwable t) {
-				logger.error(MessageFormat.format(
-						"Failed to pull from federated gitblit ({0} @ {1})", registration.name,
-						registration.url), t);
-			} finally {
-				if (isDaemon) {
-					schedule(registration);
-				}
-			}
-		}
-	}
-
-	/**
-	 * Mirrors a repository and, optionally, the server's users, and/or
-	 * configuration settings from a origin Gitblit instance.
-	 * 
-	 * @param registration
-	 * @throws Exception
-	 */
-	private void pull(FederationModel registration) throws Exception {
-		Map<String, RepositoryModel> repositories = FederationUtils.getRepositories(registration,
-				true);
-		String registrationFolder = registration.folder.toLowerCase().trim();
-		// confirm valid characters in server alias
-		Character c = StringUtils.findInvalidCharacter(registrationFolder);
-		if (c != null) {
-			logger.error(MessageFormat
-					.format("Illegal character ''{0}'' in folder name ''{1}'' of federation registration {2}!",
-							c, registrationFolder, registration.name));
-			return;
-		}
-		File repositoriesFolder = new File(GitBlit.getString(Keys.git.repositoriesFolder, "git"));
-		File registrationFolderFile = new File(repositoriesFolder, registrationFolder);
-		registrationFolderFile.mkdirs();
-
-		// Clone/Pull the repository
-		for (Map.Entry<String, RepositoryModel> entry : repositories.entrySet()) {
-			String cloneUrl = entry.getKey();
-			RepositoryModel repository = entry.getValue();
-			if (!repository.hasCommits) {
-				logger.warn(MessageFormat.format(
-						"Skipping federated repository {0} from {1} @ {2}. Repository is EMPTY.",
-						repository.name, registration.name, registration.url));
-				registration.updateStatus(repository, FederationPullStatus.SKIPPED);
-				continue;
-			}
-
-			// Determine local repository name
-			String repositoryName;
-			if (StringUtils.isEmpty(registrationFolder)) {
-				repositoryName = repository.name;
-			} else {
-				repositoryName = registrationFolder + "/" + repository.name;
-			}
-
-			if (registration.bare) {
-				// bare repository, ensure .git suffix
-				if (!repositoryName.toLowerCase().endsWith(DOT_GIT_EXT)) {
-					repositoryName += DOT_GIT_EXT;
-				}
-			} else {
-				// normal repository, strip .git suffix
-				if (repositoryName.toLowerCase().endsWith(DOT_GIT_EXT)) {
-					repositoryName = repositoryName.substring(0,
-							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;
-			}
-
-			if (existingRepository != null) {
-				StoredConfig config = existingRepository.getConfig();
-				config.load();
-				String origin = config.getString("remote", "origin", "url");
-				RevCommit commit = JGitUtils.getCommit(existingRepository,
-						org.eclipse.jgit.lib.Constants.FETCH_HEAD);
-				if (commit != null) {
-					fetchHead = commit.getName();
-				}
-				existingRepository.close();
-				if (!origin.startsWith(registration.url)) {
-					logger.warn(MessageFormat
-							.format("Skipping federated repository {0} from {1} @ {2}. Origin does not match, consider EXCLUDING.",
-									repository.name, registration.name, registration.url));
-					registration.updateStatus(repository, FederationPullStatus.SKIPPED);
-					continue;
-				}
-			}
-
-			// clone/pull this repository
-			CredentialsProvider credentials = new UsernamePasswordCredentialsProvider(
-					Constants.FEDERATION_USER, registration.token);
-			logger.info(MessageFormat.format("Pulling federated repository {0} from {1} @ {2}",
-					repository.name, registration.name, registration.url));
-
-			CloneResult result = JGitUtils.cloneRepository(registrationFolderFile, repository.name,
-					cloneUrl, registration.bare, credentials);
-			Repository r = GitBlit.self().getRepository(repositoryName);
-			RepositoryModel rm = GitBlit.self().getRepositoryModel(repositoryName);
-			repository.isFrozen = registration.mirror;
-			if (result.createdRepository) {
-				// default local settings
-				repository.federationStrategy = FederationStrategy.EXCLUDE;
-				repository.isFrozen = registration.mirror;
-				repository.showRemoteBranches = !registration.mirror;
-				logger.info(MessageFormat.format("     cloning {0}", repository.name));
-				registration.updateStatus(repository, FederationPullStatus.MIRRORED);
-			} else {
-				// fetch and update
-				boolean fetched = false;
-				RevCommit commit = JGitUtils.getCommit(r, org.eclipse.jgit.lib.Constants.FETCH_HEAD);
-				String newFetchHead = commit.getName();
-				fetched = fetchHead == null || !fetchHead.equals(newFetchHead);
-
-				if (registration.mirror) {
-					// mirror
-					if (fetched) {
-						// update local branches to match the remote tracking branches
-						for (RefModel ref : JGitUtils.getRemoteBranches(r, false, -1)) {
-							if (ref.displayName.startsWith("origin/")) {
-								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;
-						} else {
-							newHead = repository.HEAD;
-						}
-						JGitUtils.setHEADtoRef(r, newHead);
-						logger.info(MessageFormat.format("     resetting HEAD of {0} to {1}",
-								repository.name, newHead));
-						registration.updateStatus(repository, FederationPullStatus.MIRRORED);
-					} else {
-						// indicate no commits pulled
-						registration.updateStatus(repository, FederationPullStatus.NOCHANGE);
-					}
-				} else {
-					// non-mirror
-					if (fetched) {
-						// indicate commits pulled to origin/master
-						registration.updateStatus(repository, FederationPullStatus.PULLED);
-					} else {
-						// indicate no commits pulled
-						registration.updateStatus(repository, FederationPullStatus.NOCHANGE);
-					}
-				}
-
-				// preserve local settings
-				repository.isFrozen = rm.isFrozen;
-				repository.federationStrategy = rm.federationStrategy;
-
-				// merge federation sets
-				Set<String> federationSets = new HashSet<String>();
-				if (rm.federationSets != null) {
-					federationSets.addAll(rm.federationSets);
-				}
-				if (repository.federationSets != null) {
-					federationSets.addAll(repository.federationSets);
-				}
-				repository.federationSets = new ArrayList<String>(federationSets);
-				
-				// merge indexed branches
-				Set<String> indexedBranches = new HashSet<String>();
-				if (rm.indexedBranches != null) {
-					indexedBranches.addAll(rm.indexedBranches);
-				}
-				if (repository.indexedBranches != null) {
-					indexedBranches.addAll(repository.indexedBranches);
-				}
-				repository.indexedBranches = new ArrayList<String>(indexedBranches);
-
-			}
-			// only repositories that are actually _cloned_ from the origin
-			// Gitblit repository are marked as federated. If the origin
-			// is from somewhere else, these repositories are not considered
-			// "federated" repositories.
-			repository.isFederated = cloneUrl.startsWith(registration.url);
-
-			GitBlit.self().updateConfiguration(r, repository);
-			r.close();
-		}
-
-		IUserService userService = null;
-
-		try {
-			// Pull USERS
-			// TeamModels are automatically pulled because they are contained
-			// within the UserModel. The UserService creates unknown teams
-			// and updates existing teams.
-			Collection<UserModel> users = FederationUtils.getUsers(registration);
-			if (users != null && users.size() > 0) {
-				File realmFile = new File(registrationFolderFile, registration.name + "_users.conf");
-				realmFile.delete();
-				userService = new ConfigUserService(realmFile);
-				for (UserModel user : users) {
-					userService.updateUserModel(user.username, user);
-
-					// merge the origin permissions and origin accounts into
-					// the user accounts of this Gitblit instance
-					if (registration.mergeAccounts) {
-						// reparent all repository permissions if the local
-						// repositories are stored within subfolders
-						if (!StringUtils.isEmpty(registrationFolder)) {
-							if (user.permissions != null) {
-								// pulling from >= 1.2 version
-								Map<String, AccessPermission> copy = new HashMap<String, AccessPermission>(user.permissions);
-								user.permissions.clear();
-								for (Map.Entry<String, AccessPermission> entry : copy.entrySet()) {
-									user.setRepositoryPermission(registrationFolder + "/" + entry.getKey(), entry.getValue());
-								}
-							} else {
-								// pulling from <= 1.1 version
-								List<String> permissions = new ArrayList<String>(user.repositories);
-								user.repositories.clear();
-								for (String permission : permissions) {
-									user.addRepositoryPermission(registrationFolder + "/" + permission);
-								}
-							}
-						}
-
-						// insert new user or update local user
-						UserModel localUser = GitBlit.self().getUserModel(user.username);
-						if (localUser == null) {
-							// create new local user
-							GitBlit.self().updateUserModel(user.username, user, true);
-						} else {
-							// update repository permissions of local user
-							if (user.permissions != null) {
-								// pulling from >= 1.2 version
-								Map<String, AccessPermission> copy = new HashMap<String, AccessPermission>(user.permissions);
-								for (Map.Entry<String, AccessPermission> entry : copy.entrySet()) {
-									localUser.setRepositoryPermission(entry.getKey(), entry.getValue());
-								}
-							} else {
-								// pulling from <= 1.1 version
-								for (String repository : user.repositories) {
-									localUser.addRepositoryPermission(repository);
-								}
-							}
-							localUser.password = user.password;
-							localUser.canAdmin = user.canAdmin;
-							GitBlit.self().updateUserModel(localUser.username, localUser, false);
-						}
-
-						for (String teamname : GitBlit.self().getAllTeamnames()) {
-							TeamModel team = GitBlit.self().getTeamModel(teamname);
-							if (user.isTeamMember(teamname) && !team.hasUser(user.username)) {
-								// new team member
-								team.addUser(user.username);
-								GitBlit.self().updateTeamModel(teamname, team, false);
-							} else if (!user.isTeamMember(teamname) && team.hasUser(user.username)) {
-								// remove team member
-								team.removeUser(user.username);
-								GitBlit.self().updateTeamModel(teamname, team, false);
-							}
-
-							// update team repositories
-							TeamModel remoteTeam = user.getTeam(teamname);
-							if (remoteTeam != null) {
-								if (remoteTeam.permissions != null) {
-									// pulling from >= 1.2
-									for (Map.Entry<String, AccessPermission> entry : remoteTeam.permissions.entrySet()){
-										team.setRepositoryPermission(entry.getKey(), entry.getValue());
-									}
-									GitBlit.self().updateTeamModel(teamname, team, false);
-								} else if(!ArrayUtils.isEmpty(remoteTeam.repositories)) {
-									// pulling from <= 1.1
-									team.addRepositoryPermissions(remoteTeam.repositories);
-									GitBlit.self().updateTeamModel(teamname, team, false);
-								}
-							}
-						}
-					}
-				}
-			}
-		} catch (ForbiddenException e) {
-			// ignore forbidden exceptions
-		} catch (IOException e) {
-			logger.warn(MessageFormat.format(
-					"Failed to retrieve USERS from federated gitblit ({0} @ {1})",
-					registration.name, registration.url), e);
-		}
-
-		try {
-			// Pull TEAMS
-			// We explicitly pull these even though they are embedded in
-			// UserModels because it is possible to use teams to specify
-			// mailing lists or push scripts without specifying users.
-			if (userService != null) {
-				Collection<TeamModel> teams = FederationUtils.getTeams(registration);
-				if (teams != null && teams.size() > 0) {
-					for (TeamModel team : teams) {
-						userService.updateTeamModel(team);
-					}
-				}
-			}
-		} catch (ForbiddenException e) {
-			// ignore forbidden exceptions
-		} catch (IOException e) {
-			logger.warn(MessageFormat.format(
-					"Failed to retrieve TEAMS from federated gitblit ({0} @ {1})",
-					registration.name, registration.url), e);
-		}
-
-		try {
-			// Pull SETTINGS
-			Map<String, String> settings = FederationUtils.getSettings(registration);
-			if (settings != null && settings.size() > 0) {
-				Properties properties = new Properties();
-				properties.putAll(settings);
-				FileOutputStream os = new FileOutputStream(new File(registrationFolderFile,
-						registration.name + "_" + Constants.PROPERTIES_FILE));
-				properties.store(os, null);
-				os.close();
-			}
-		} catch (ForbiddenException e) {
-			// ignore forbidden exceptions
-		} catch (IOException e) {
-			logger.warn(MessageFormat.format(
-					"Failed to retrieve SETTINGS from federated gitblit ({0} @ {1})",
-					registration.name, registration.url), e);
-		}
-
-		try {
-			// Pull SCRIPTS
-			Map<String, String> scripts = FederationUtils.getScripts(registration);
-			if (scripts != null && scripts.size() > 0) {
-				for (Map.Entry<String, String> script : scripts.entrySet()) {
-					String scriptName = script.getKey();
-					if (scriptName.endsWith(".groovy")) {
-						scriptName = scriptName.substring(0, scriptName.indexOf(".groovy"));
-					}
-					File file = new File(registrationFolderFile, registration.name + "_"
-							+ scriptName + ".groovy");
-					FileUtils.writeContent(file, script.getValue());
-				}
-			}
-		} catch (ForbiddenException e) {
-			// ignore forbidden exceptions
-		} catch (IOException e) {
-			logger.warn(MessageFormat.format(
-					"Failed to retrieve SCRIPTS from federated gitblit ({0} @ {1})",
-					registration.name, registration.url), e);
-		}
-	}
-
-	/**
-	 * Sends a status acknowledgment to the origin Gitblit instance. This
-	 * includes the results of the federated pull.
-	 * 
-	 * @param registration
-	 * @throws Exception
-	 */
-	private void sendStatusAcknowledgment(FederationModel registration) throws Exception {
-		if (!registration.sendStatus) {
-			// skip status acknowledgment
-			return;
-		}
-		InetAddress addr = InetAddress.getLocalHost();
-		String federationName = GitBlit.getString(Keys.federation.name, null);
-		if (StringUtils.isEmpty(federationName)) {
-			federationName = addr.getHostName();
-		}
-		FederationUtils.acknowledgeStatus(addr.getHostAddress(), registration);
-		logger.info(MessageFormat.format("Pull status sent to {0}", registration.url));
-	}
-
-	/**
-	 * Schedules the next check of the federated Gitblit instance.
-	 * 
-	 * @param registration
-	 */
-	private void schedule(FederationModel registration) {
-		// schedule the next pull
-		int mins = TimeUtils.convertFrequencyToMinutes(registration.frequency);
-		registration.nextPull = new Date(System.currentTimeMillis() + (mins * 60 * 1000L));
-		GitBlit.self().executor()
-				.schedule(new FederationPullExecutor(registration), mins, TimeUnit.MINUTES);
-		logger.info(MessageFormat.format(
-				"Next pull of {0} @ {1} scheduled for {2,date,yyyy-MM-dd HH:mm}",
-				registration.name, registration.url, registration.nextPull));
-	}
-}
diff --git a/src/com/gitblit/FileUserService.java b/src/com/gitblit/FileUserService.java
deleted file mode 100644
index 056df82..0000000
--- a/src/com/gitblit/FileUserService.java
+++ /dev/null
@@ -1,1145 +0,0 @@
-/*
- * Copyright 2011 gitblit.com.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.gitblit;
-
-import java.io.File;
-import java.io.FileWriter;
-import java.io.IOException;
-import java.text.MessageFormat;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Properties;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.gitblit.Constants.AccessPermission;
-import com.gitblit.models.TeamModel;
-import com.gitblit.models.UserModel;
-import com.gitblit.utils.ArrayUtils;
-import com.gitblit.utils.DeepCopier;
-import com.gitblit.utils.StringUtils;
-
-/**
- * FileUserService is Gitblit's original default user service implementation.
- * 
- * Users and their repository memberships are stored in a simple properties file
- * which is cached and dynamically reloaded when modified.
- * 
- * This class was deprecated in Gitblit 0.8.0 in favor of ConfigUserService
- * which is still a human-readable, editable, plain-text file but it is more
- * flexible for storing additional fields.
- * 
- * @author James Moger
- * 
- */
-@Deprecated
-public class FileUserService extends FileSettings implements IUserService {
-
-	private final Logger logger = LoggerFactory.getLogger(FileUserService.class);
-
-	private final Map<String, String> cookies = new ConcurrentHashMap<String, String>();
-
-	private final Map<String, TeamModel> teams = new ConcurrentHashMap<String, TeamModel>();
-
-	public FileUserService(File realmFile) {
-		super(realmFile.getAbsolutePath());
-	}
-
-	/**
-	 * Setup the user service.
-	 * 
-	 * @param settings
-	 * @since 0.7.0
-	 */
-	@Override
-	public void setup(IStoredSettings settings) {
-	}
-
-	/**
-	 * Does the user service support changes to credentials?
-	 * 
-	 * @return true or false
-	 * @since 1.0.0
-	 */
-	@Override
-	public boolean supportsCredentialChanges() {
-		return true;
-	}
-
-	/**
-	 * Does the user service support changes to user display name?
-	 * 
-	 * @return true or false
-	 * @since 1.0.0
-	 */
-	@Override
-	public boolean supportsDisplayNameChanges() {
-		return false;
-	}
-
-	/**
-	 * Does the user service support changes to user email address?
-	 * 
-	 * @return true or false
-	 * @since 1.0.0
-	 */
-	@Override
-	public boolean supportsEmailAddressChanges() {
-		return false;
-	}
-
-	/**
-	 * Does the user service support changes to team memberships?
-	 * 
-	 * @return true or false
-	 * @since 1.0.0
-	 */	
-	public boolean supportsTeamMembershipChanges() {
-		return true;
-	}
-
-	/**
-	 * Does the user service support cookie authentication?
-	 * 
-	 * @return true or false
-	 */
-	@Override
-	public boolean supportsCookies() {
-		return true;
-	}
-
-	/**
-	 * Returns the cookie value for the specified user.
-	 * 
-	 * @param model
-	 * @return cookie value
-	 */
-	@Override
-	public String getCookie(UserModel model) {
-		if (!StringUtils.isEmpty(model.cookie)) {
-			return model.cookie;
-		}
-		Properties allUsers = super.read();
-		String value = allUsers.getProperty(model.username);
-		String[] roles = value.split(",");
-		String password = roles[0];
-		String cookie = StringUtils.getSHA1(model.username + password);
-		return cookie;
-	}
-
-	/**
-	 * Authenticate a user based on their cookie.
-	 * 
-	 * @param cookie
-	 * @return a user object or null
-	 */
-	@Override
-	public UserModel authenticate(char[] cookie) {
-		String hash = new String(cookie);
-		if (StringUtils.isEmpty(hash)) {
-			return null;
-		}
-		read();
-		UserModel model = null;
-		if (cookies.containsKey(hash)) {
-			String username = cookies.get(hash);
-			model = getUserModel(username);
-		}
-		return model;
-	}
-
-	/**
-	 * Authenticate a user based on a username and password.
-	 * 
-	 * @param username
-	 * @param password
-	 * @return a user object or null
-	 */
-	@Override
-	public UserModel authenticate(String username, char[] password) {
-		Properties allUsers = read();
-		String userInfo = allUsers.getProperty(username);
-		if (StringUtils.isEmpty(userInfo)) {
-			return null;
-		}
-		UserModel returnedUser = null;
-		UserModel user = getUserModel(username);
-		if (user.password.startsWith(StringUtils.MD5_TYPE)) {
-			// password digest
-			String md5 = StringUtils.MD5_TYPE + StringUtils.getMD5(new String(password));
-			if (user.password.equalsIgnoreCase(md5)) {
-				returnedUser = user;
-			}
-		} else if (user.password.startsWith(StringUtils.COMBINED_MD5_TYPE)) {
-			// username+password digest
-			String md5 = StringUtils.COMBINED_MD5_TYPE
-					+ StringUtils.getMD5(username.toLowerCase() + new String(password));
-			if (user.password.equalsIgnoreCase(md5)) {
-				returnedUser = user;
-			}
-		} else if (user.password.equals(new String(password))) {
-			// plain-text password
-			returnedUser = user;
-		}
-		return returnedUser;
-	}
-
-	/**
-	 * Logout a user.
-	 * 
-	 * @param user
-	 */
-	@Override
-	public void logout(UserModel user) {	
-	}
-
-	/**
-	 * Retrieve the user object for the specified username.
-	 * 
-	 * @param username
-	 * @return a user object or null
-	 */
-	@Override
-	public UserModel getUserModel(String username) {
-		Properties allUsers = read();
-		String userInfo = allUsers.getProperty(username.toLowerCase());
-		if (userInfo == null) {
-			return null;
-		}
-		UserModel model = new UserModel(username.toLowerCase());
-		String[] userValues = userInfo.split(",");
-		model.password = userValues[0];
-		for (int i = 1; i < userValues.length; i++) {
-			String role = userValues[i];
-			switch (role.charAt(0)) {
-			case '#':
-				// Permissions
-				if (role.equalsIgnoreCase(Constants.ADMIN_ROLE)) {
-					model.canAdmin = true;
-				} else if (role.equalsIgnoreCase(Constants.FORK_ROLE)) {
-					model.canFork = true;
-				} else if (role.equalsIgnoreCase(Constants.CREATE_ROLE)) {
-					model.canCreate = true;
-				} else if (role.equalsIgnoreCase(Constants.NOT_FEDERATED_ROLE)) {
-					model.excludeFromFederation = true;
-				}
-				break;
-			default:
-				model.addRepositoryPermission(role);
-			}
-		}
-		// set the teams for the user
-		for (TeamModel team : teams.values()) {
-			if (team.hasUser(username)) {
-				model.teams.add(DeepCopier.copy(team));
-			}
-		}
-		return model;
-	}
-
-	/**
-	 * Updates/writes a complete user object.
-	 * 
-	 * @param model
-	 * @return true if update is successful
-	 */
-	@Override
-	public boolean updateUserModel(UserModel model) {
-		return updateUserModel(model.username, model);
-	}
-
-	/**
-	 * Updates/writes all specified user objects.
-	 * 
-	 * @param model a list of user models
-	 * @return true if update is successful
-	 * @since 1.2.0
-	 */
-	@Override
-	public boolean updateUserModels(List<UserModel> models) {
-		try {			
-			Properties allUsers = read();
-			for (UserModel model : models) {
-				updateUserCache(allUsers, model.username, model);
-			}
-			write(allUsers);
-			return true;
-		} catch (Throwable t) {
-			logger.error(MessageFormat.format("Failed to update {0} user models!", models.size()),
-					t);
-		}
-		return false;
-	}
-
-	/**
-	 * 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
-	 *            the user object to use for username
-	 * @return true if update is successful
-	 */
-	@Override
-	public boolean updateUserModel(String username, UserModel model) {
-		try {			
-			Properties allUsers = read();
-			updateUserCache(allUsers, username, model);
-			write(allUsers);
-			return true;
-		} catch (Throwable t) {
-			logger.error(MessageFormat.format("Failed to update user model {0}!", model.username),
-					t);
-		}
-		return false;
-	}
-	
-	/**
-	 * 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
-	 *            the user object to use for username
-	 * @return true if update is successful
-	 */
-	private boolean updateUserCache(Properties allUsers, String username, UserModel model) {
-		try {			
-			UserModel oldUser = getUserModel(username);
-			List<String> roles;
-			if (model.permissions == null) {
-				roles = new ArrayList<String>();
-			} else {
-				// discrete repository permissions
-				roles = new ArrayList<String>();
-				for (Map.Entry<String, AccessPermission> entry : model.permissions.entrySet()) {
-					if (entry.getValue().exceeds(AccessPermission.NONE)) {
-						// code:repository (e.g. RW+:~james/myrepo.git
-						roles.add(entry.getValue().asRole(entry.getKey()));
-					}
-				}
-			}
-
-			// Permissions
-			if (model.canAdmin) {
-				roles.add(Constants.ADMIN_ROLE);
-			}
-			if (model.canFork) {
-				roles.add(Constants.FORK_ROLE);
-			}
-			if (model.canCreate) {
-				roles.add(Constants.CREATE_ROLE);
-			}
-			if (model.excludeFromFederation) {
-				roles.add(Constants.NOT_FEDERATED_ROLE);
-			}
-
-			StringBuilder sb = new StringBuilder();
-			if (!StringUtils.isEmpty(model.password)) {
-				sb.append(model.password);
-			}
-			sb.append(',');
-			for (String role : roles) {
-				sb.append(role);
-				sb.append(',');
-			}
-			// trim trailing comma
-			sb.setLength(sb.length() - 1);
-			allUsers.remove(username.toLowerCase());
-			allUsers.put(model.username.toLowerCase(), sb.toString());
-
-			// null check on "final" teams because JSON-sourced UserModel
-			// can have a null teams object
-			if (model.teams != null) {
-				// update team cache
-				for (TeamModel team : model.teams) {
-					TeamModel t = getTeamModel(team.name);
-					if (t == null) {
-						// new team
-						t = team;
-					}
-					t.removeUser(username);
-					t.addUser(model.username);
-					updateTeamCache(allUsers, t.name, t);
-				}
-
-				// check for implicit team removal
-				if (oldUser != null) {
-					for (TeamModel team : oldUser.teams) {
-						if (!model.isTeamMember(team.name)) {
-							team.removeUser(username);
-							updateTeamCache(allUsers, team.name, team);
-						}
-					}
-				}
-			}
-			return true;
-		} catch (Throwable t) {
-			logger.error(MessageFormat.format("Failed to update user model {0}!", model.username),
-					t);
-		}
-		return false;
-	}
-
-	/**
-	 * Deletes the user object from the user service.
-	 * 
-	 * @param model
-	 * @return true if successful
-	 */
-	@Override
-	public boolean deleteUserModel(UserModel model) {
-		return deleteUser(model.username);
-	}
-
-	/**
-	 * Delete the user object with the specified username
-	 * 
-	 * @param username
-	 * @return true if successful
-	 */
-	@Override
-	public boolean deleteUser(String username) {
-		try {
-			// Read realm file
-			Properties allUsers = read();
-			UserModel user = getUserModel(username);
-			allUsers.remove(username);
-			for (TeamModel team : user.teams) {
-				TeamModel t = getTeamModel(team.name);
-				if (t == null) {
-					// new team
-					t = team;
-				}
-				t.removeUser(username);
-				updateTeamCache(allUsers, t.name, t);
-			}
-			write(allUsers);
-			return true;
-		} catch (Throwable t) {
-			logger.error(MessageFormat.format("Failed to delete user {0}!", username), t);
-		}
-		return false;
-	}
-
-	/**
-	 * Returns the list of all users available to the login service.
-	 * 
-	 * @return list of all usernames
-	 */
-	@Override
-	public List<String> getAllUsernames() {
-		Properties allUsers = read();
-		List<String> list = new ArrayList<String>();
-		for (String user : allUsers.stringPropertyNames()) {
-			if (user.charAt(0) == '@') {
-				// skip team user definitions
-				continue;
-			}
-			list.add(user);
-		}
-		Collections.sort(list);
-		return list;
-	}
-
-	/**
-	 * Returns the list of all users available to the login service.
-	 * 
-	 * @return list of all usernames
-	 */
-	@Override
-	public List<UserModel> getAllUsers() {
-		read();
-		List<UserModel> list = new ArrayList<UserModel>();
-		for (String username : getAllUsernames()) {
-			list.add(getUserModel(username));
-		}
-		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
-	 */
-	@Override
-	public List<String> getUsernamesForRepositoryRole(String role) {
-		List<String> list = new ArrayList<String>();
-		try {
-			Properties allUsers = read();
-			for (String username : allUsers.stringPropertyNames()) {
-				if (username.charAt(0) == '@') {
-					continue;
-				}
-				String value = allUsers.getProperty(username);
-				String[] values = value.split(",");
-				// skip first value (password)
-				for (int i = 1; i < values.length; i++) {
-					String r = values[i];
-					if (r.equalsIgnoreCase(role)) {
-						list.add(username);
-						break;
-					}
-				}
-			}
-		} catch (Throwable t) {
-			logger.error(MessageFormat.format("Failed to get usernames for role {0}!", role), t);
-		}
-		Collections.sort(list);
-		return list;
-	}
-
-	/**
-	 * Sets the list of all users who are allowed to bypass the access
-	 * restriction placed on the specified repository.
-	 * 
-	 * @param role
-	 *            the repository name
-	 * @param usernames
-	 * @return true if successful
-	 */
-	@Override
-	public boolean setUsernamesForRepositoryRole(String role, List<String> usernames) {
-		try {
-			Set<String> specifiedUsers = new HashSet<String>(usernames);
-			Set<String> needsAddRole = new HashSet<String>(specifiedUsers);
-			Set<String> needsRemoveRole = new HashSet<String>();
-
-			// identify users which require add and remove role
-			Properties allUsers = read();
-			for (String username : allUsers.stringPropertyNames()) {
-				String value = allUsers.getProperty(username);
-				String[] values = value.split(",");
-				// skip first value (password)
-				for (int i = 1; i < values.length; i++) {
-					String r = values[i];
-					if (r.equalsIgnoreCase(role)) {
-						// user has role, check against revised user list
-						if (specifiedUsers.contains(username)) {
-							needsAddRole.remove(username);
-						} else {
-							// remove role from user
-							needsRemoveRole.add(username);
-						}
-						break;
-					}
-				}
-			}
-
-			// add roles to users
-			for (String user : needsAddRole) {
-				String userValues = allUsers.getProperty(user);
-				userValues += "," + role;
-				allUsers.put(user, userValues);
-			}
-
-			// remove role from user
-			for (String user : needsRemoveRole) {
-				String[] values = allUsers.getProperty(user).split(",");
-				String password = values[0];
-				StringBuilder sb = new StringBuilder();
-				sb.append(password);
-				sb.append(',');
-
-				// skip first value (password)
-				for (int i = 1; i < values.length; i++) {
-					String value = values[i];
-					if (!value.equalsIgnoreCase(role)) {
-						sb.append(value);
-						sb.append(',');
-					}
-				}
-				sb.setLength(sb.length() - 1);
-
-				// update properties
-				allUsers.put(user, sb.toString());
-			}
-
-			// persist changes
-			write(allUsers);
-			return true;
-		} catch (Throwable t) {
-			logger.error(MessageFormat.format("Failed to set usernames for role {0}!", role), t);
-		}
-		return false;
-	}
-
-	/**
-	 * Renames a repository role.
-	 * 
-	 * @param oldRole
-	 * @param newRole
-	 * @return true if successful
-	 */
-	@Override
-	public boolean renameRepositoryRole(String oldRole, String newRole) {
-		try {
-			Properties allUsers = read();
-			Set<String> needsRenameRole = new HashSet<String>();
-
-			// identify users which require role rename
-			for (String username : allUsers.stringPropertyNames()) {
-				String value = allUsers.getProperty(username);
-				String[] roles = value.split(",");
-				// skip first value (password)
-				for (int i = 1; i < roles.length; i++) {
-					String repository = AccessPermission.repositoryFromRole(roles[i]);
-					if (repository.equalsIgnoreCase(oldRole)) {
-						needsRenameRole.add(username);
-						break;
-					}
-				}
-			}
-
-			// rename role for identified users
-			for (String user : needsRenameRole) {
-				String userValues = allUsers.getProperty(user);
-				String[] values = userValues.split(",");
-				String password = values[0];
-				StringBuilder sb = new StringBuilder();
-				sb.append(password);
-				sb.append(',');
-				sb.append(newRole);
-				sb.append(',');
-
-				// skip first value (password)
-				for (int i = 1; i < values.length; i++) {
-					String repository = AccessPermission.repositoryFromRole(values[i]);
-					if (repository.equalsIgnoreCase(oldRole)) {
-						AccessPermission permission = AccessPermission.permissionFromRole(values[i]);
-						sb.append(permission.asRole(newRole));
-						sb.append(',');
-					} else {
-						sb.append(values[i]);
-						sb.append(',');
-					}
-				}
-				sb.setLength(sb.length() - 1);
-
-				// update properties
-				allUsers.put(user, sb.toString());
-			}
-
-			// persist changes
-			write(allUsers);
-			return true;
-		} catch (Throwable t) {
-			logger.error(
-					MessageFormat.format("Failed to rename role {0} to {1}!", oldRole, newRole), t);
-		}
-		return false;
-	}
-
-	/**
-	 * Removes a repository role from all users.
-	 * 
-	 * @param role
-	 * @return true if successful
-	 */
-	@Override
-	public boolean deleteRepositoryRole(String role) {
-		try {
-			Properties allUsers = read();
-			Set<String> needsDeleteRole = new HashSet<String>();
-
-			// identify users which require role rename
-			for (String username : allUsers.stringPropertyNames()) {
-				String value = allUsers.getProperty(username);
-				String[] roles = value.split(",");
-				// skip first value (password)
-				for (int i = 1; i < roles.length; i++) {					
-					String repository = AccessPermission.repositoryFromRole(roles[i]);
-					if (repository.equalsIgnoreCase(role)) {
-						needsDeleteRole.add(username);
-						break;
-					}
-				}
-			}
-
-			// delete role for identified users
-			for (String user : needsDeleteRole) {
-				String userValues = allUsers.getProperty(user);
-				String[] values = userValues.split(",");
-				String password = values[0];
-				StringBuilder sb = new StringBuilder();
-				sb.append(password);
-				sb.append(',');
-				// skip first value (password)
-				for (int i = 1; i < values.length; i++) {					
-					String repository = AccessPermission.repositoryFromRole(values[i]);
-					if (!repository.equalsIgnoreCase(role)) {
-						sb.append(values[i]);
-						sb.append(',');
-					}
-				}
-				sb.setLength(sb.length() - 1);
-
-				// update properties
-				allUsers.put(user, sb.toString());
-			}
-
-			// persist changes
-			write(allUsers);
-			return true;
-		} catch (Throwable t) {
-			logger.error(MessageFormat.format("Failed to delete role {0}!", role), t);
-		}
-		return false;
-	}
-
-	/**
-	 * Writes the properties file.
-	 * 
-	 * @param properties
-	 * @throws IOException
-	 */
-	private void write(Properties properties) throws IOException {
-		// Write a temporary copy of the users file
-		File realmFileCopy = new File(propertiesFile.getAbsolutePath() + ".tmp");
-		FileWriter writer = new FileWriter(realmFileCopy);
-		properties
-				.store(writer,
-						" Gitblit realm file format:\n   username=password,\\#permission,repository1,repository2...\n   @teamname=!username1,!username2,!username3,repository1,repository2...");
-		writer.close();
-		// If the write is successful, delete the current file and rename
-		// the temporary copy to the original filename.
-		if (realmFileCopy.exists() && realmFileCopy.length() > 0) {
-			if (propertiesFile.exists()) {
-				if (!propertiesFile.delete()) {
-					throw new IOException(MessageFormat.format("Failed to delete {0}!",
-							propertiesFile.getAbsolutePath()));
-				}
-			}
-			if (!realmFileCopy.renameTo(propertiesFile)) {
-				throw new IOException(MessageFormat.format("Failed to rename {0} to {1}!",
-						realmFileCopy.getAbsolutePath(), propertiesFile.getAbsolutePath()));
-			}
-		} else {
-			throw new IOException(MessageFormat.format("Failed to save {0}!",
-					realmFileCopy.getAbsolutePath()));
-		}
-	}
-
-	/**
-	 * Reads the properties file and rebuilds the in-memory cookie lookup table.
-	 */
-	@Override
-	protected synchronized Properties read() {
-		long lastRead = lastModified();
-		boolean reload = forceReload();
-		Properties allUsers = super.read();
-		if (reload || (lastRead != lastModified())) {
-			// reload hash cache
-			cookies.clear();
-			teams.clear();
-
-			for (String username : allUsers.stringPropertyNames()) {
-				String value = allUsers.getProperty(username);
-				String[] roles = value.split(",");
-				if (username.charAt(0) == '@') {
-					// team definition
-					TeamModel team = new TeamModel(username.substring(1));
-					List<String> repositories = new ArrayList<String>();
-					List<String> users = new ArrayList<String>();
-					List<String> mailingLists = new ArrayList<String>();
-					List<String> preReceive = new ArrayList<String>();
-					List<String> postReceive = new ArrayList<String>();
-					for (String role : roles) {
-						if (role.charAt(0) == '!') {
-							users.add(role.substring(1));
-						} else if (role.charAt(0) == '&') {
-							mailingLists.add(role.substring(1));
-						} else if (role.charAt(0) == '^') {
-							preReceive.add(role.substring(1));
-						} else if (role.charAt(0) == '%') {
-							postReceive.add(role.substring(1));
-						} else {
-							switch (role.charAt(0)) {
-							case '#':
-								// Permissions
-								if (role.equalsIgnoreCase(Constants.ADMIN_ROLE)) {
-									team.canAdmin = true;
-								} else if (role.equalsIgnoreCase(Constants.FORK_ROLE)) {
-									team.canFork = true;
-								} else if (role.equalsIgnoreCase(Constants.CREATE_ROLE)) {
-									team.canCreate = true;
-								}
-								break;
-							default:
-								repositories.add(role);
-							}
-							repositories.add(role);
-						}
-					}
-					if (!team.canAdmin) {
-						// only read permissions for non-admin teams
-						team.addRepositoryPermissions(repositories);
-					}
-					team.addUsers(users);
-					team.addMailingLists(mailingLists);
-					team.preReceiveScripts.addAll(preReceive);
-					team.postReceiveScripts.addAll(postReceive);
-					teams.put(team.name.toLowerCase(), team);
-				} else {
-					// user definition
-					String password = roles[0];
-					cookies.put(StringUtils.getSHA1(username.toLowerCase() + password), username.toLowerCase());
-				}
-			}
-		}
-		return allUsers;
-	}
-
-	@Override
-	public String toString() {
-		return getClass().getSimpleName() + "(" + propertiesFile.getAbsolutePath() + ")";
-	}
-
-	/**
-	 * Returns the list of all teams available to the login service.
-	 * 
-	 * @return list of all teams
-	 * @since 0.8.0
-	 */
-	@Override
-	public List<String> getAllTeamNames() {
-		List<String> list = new ArrayList<String>(teams.keySet());
-		Collections.sort(list);
-		return list;
-	}
-
-	/**
-	 * Returns the list of all teams available to the login service.
-	 * 
-	 * @return list of all teams
-	 * @since 0.8.0
-	 */
-	@Override
-	public List<TeamModel> getAllTeams() {
-		List<TeamModel> list = new ArrayList<TeamModel>(teams.values());
-		list = DeepCopier.copy(list);
-		Collections.sort(list);
-		return list;
-	}
-
-	/**
-	 * Returns the list of all teams who are allowed to bypass the access
-	 * restriction placed on the specified repository.
-	 * 
-	 * @param role
-	 *            the repository name
-	 * @return list of all teamnames that can bypass the access restriction
-	 */
-	@Override
-	public List<String> getTeamnamesForRepositoryRole(String role) {
-		List<String> list = new ArrayList<String>();
-		try {
-			Properties allUsers = read();
-			for (String team : allUsers.stringPropertyNames()) {
-				if (team.charAt(0) != '@') {
-					// skip users
-					continue;
-				}
-				String value = allUsers.getProperty(team);
-				String[] values = value.split(",");
-				for (int i = 0; i < values.length; i++) {
-					String r = values[i];
-					if (r.equalsIgnoreCase(role)) {
-						// strip leading @
-						list.add(team.substring(1));
-						break;
-					}
-				}
-			}
-		} catch (Throwable t) {
-			logger.error(MessageFormat.format("Failed to get teamnames for role {0}!", role), t);
-		}
-		Collections.sort(list);
-		return list;
-	}
-
-	/**
-	 * 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
-	 * @return true if successful
-	 */
-	@Override
-	public boolean setTeamnamesForRepositoryRole(String role, List<String> teamnames) {
-		try {
-			Set<String> specifiedTeams = new HashSet<String>(teamnames);
-			Set<String> needsAddRole = new HashSet<String>(specifiedTeams);
-			Set<String> needsRemoveRole = new HashSet<String>();
-
-			// identify teams which require add and remove role
-			Properties allUsers = read();
-			for (String team : allUsers.stringPropertyNames()) {
-				if (team.charAt(0) != '@') {
-					// skip users
-					continue;
-				}
-				String name = team.substring(1);
-				String value = allUsers.getProperty(team);
-				String[] values = value.split(",");
-				for (int i = 0; i < values.length; i++) {
-					String r = values[i];
-					if (r.equalsIgnoreCase(role)) {
-						// team has role, check against revised team list
-						if (specifiedTeams.contains(name)) {
-							needsAddRole.remove(name);
-						} else {
-							// remove role from team
-							needsRemoveRole.add(name);
-						}
-						break;
-					}
-				}
-			}
-
-			// add roles to teams
-			for (String name : needsAddRole) {
-				String team = "@" + name;
-				String teamValues = allUsers.getProperty(team);
-				teamValues += "," + role;
-				allUsers.put(team, teamValues);
-			}
-
-			// remove role from team
-			for (String name : needsRemoveRole) {
-				String team = "@" + name;
-				String[] values = allUsers.getProperty(team).split(",");
-				StringBuilder sb = new StringBuilder();
-				for (int i = 0; i < values.length; i++) {
-					String value = values[i];
-					if (!value.equalsIgnoreCase(role)) {
-						sb.append(value);
-						sb.append(',');
-					}
-				}
-				sb.setLength(sb.length() - 1);
-
-				// update properties
-				allUsers.put(team, sb.toString());
-			}
-
-			// persist changes
-			write(allUsers);
-			return true;
-		} catch (Throwable t) {
-			logger.error(MessageFormat.format("Failed to set teamnames for role {0}!", role), t);
-		}
-		return false;
-	}
-
-	/**
-	 * Retrieve the team object for the specified team name.
-	 * 
-	 * @param teamname
-	 * @return a team object or null
-	 * @since 0.8.0
-	 */
-	@Override
-	public TeamModel getTeamModel(String teamname) {
-		read();
-		TeamModel team = teams.get(teamname.toLowerCase());
-		if (team != null) {
-			// clone the model, otherwise all changes to this object are
-			// live and unpersisted
-			team = DeepCopier.copy(team);
-		}
-		return team;
-	}
-
-	/**
-	 * Updates/writes a complete team object.
-	 * 
-	 * @param model
-	 * @return true if update is successful
-	 * @since 0.8.0
-	 */
-	@Override
-	public boolean updateTeamModel(TeamModel model) {
-		return updateTeamModel(model.name, model);
-	}
-	
-	/**
-	 * Updates/writes all specified team objects.
-	 * 
-	 * @param models a list of team models
-	 * @return true if update is successful
-	 * @since 1.2.0
-	 */
-	public boolean updateTeamModels(List<TeamModel> models) {
-		try {
-			Properties allUsers = read();
-			for (TeamModel model : models) {
-				updateTeamCache(allUsers, model.name, model);
-			}
-			write(allUsers);
-			return true;
-		} catch (Throwable t) {
-			logger.error(MessageFormat.format("Failed to update {0} team models!", models.size()), t);
-		}
-		return false;
-	}
-
-	/**
-	 * 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
-	 *            the team object to use for teamname
-	 * @return true if update is successful
-	 * @since 0.8.0
-	 */
-	@Override
-	public boolean updateTeamModel(String teamname, TeamModel model) {
-		try {
-			Properties allUsers = read();
-			updateTeamCache(allUsers, teamname, model);
-			write(allUsers);
-			return true;
-		} catch (Throwable t) {
-			logger.error(MessageFormat.format("Failed to update team model {0}!", model.name), t);
-		}
-		return false;
-	}
-
-	private void updateTeamCache(Properties allUsers, String teamname, TeamModel model) {
-		StringBuilder sb = new StringBuilder();
-		List<String> roles;
-		if (model.permissions == null) {
-			// legacy, use repository list
-			if (model.repositories != null) {
-				roles = new ArrayList<String>(model.repositories);
-			} else {
-				roles = new ArrayList<String>();
-			}
-		} else {
-			// discrete repository permissions
-			roles = new ArrayList<String>();
-			for (Map.Entry<String, AccessPermission> entry : model.permissions.entrySet()) {
-				if (entry.getValue().exceeds(AccessPermission.NONE)) {
-					// code:repository (e.g. RW+:~james/myrepo.git
-					roles.add(entry.getValue().asRole(entry.getKey()));
-				}
-			}
-		}
-		
-		// Permissions
-		if (model.canAdmin) {
-			roles.add(Constants.ADMIN_ROLE);
-		}
-		if (model.canFork) {
-			roles.add(Constants.FORK_ROLE);
-		}
-		if (model.canCreate) {
-			roles.add(Constants.CREATE_ROLE);
-		}
-
-		for (String role : roles) {
-				sb.append(role);
-				sb.append(',');
-		}
-		
-		if (!ArrayUtils.isEmpty(model.users)) {
-			for (String user : model.users) {
-				sb.append('!');
-				sb.append(user);
-				sb.append(',');
-			}
-		}
-		if (!ArrayUtils.isEmpty(model.mailingLists)) {
-			for (String address : model.mailingLists) {
-				sb.append('&');
-				sb.append(address);
-				sb.append(',');
-			}
-		}
-		if (!ArrayUtils.isEmpty(model.preReceiveScripts)) {
-			for (String script : model.preReceiveScripts) {
-				sb.append('^');
-				sb.append(script);
-				sb.append(',');
-			}
-		}
-		if (!ArrayUtils.isEmpty(model.postReceiveScripts)) {
-			for (String script : model.postReceiveScripts) {
-				sb.append('%');
-				sb.append(script);
-				sb.append(',');
-			}
-		}
-		// trim trailing comma
-		sb.setLength(sb.length() - 1);
-		allUsers.remove("@" + teamname);
-		allUsers.put("@" + model.name, sb.toString());
-
-		// update team cache
-		teams.remove(teamname.toLowerCase());
-		teams.put(model.name.toLowerCase(), model);
-	}
-
-	/**
-	 * Deletes the team object from the user service.
-	 * 
-	 * @param model
-	 * @return true if successful
-	 * @since 0.8.0
-	 */
-	@Override
-	public boolean deleteTeamModel(TeamModel model) {
-		return deleteTeam(model.name);
-	}
-
-	/**
-	 * Delete the team object with the specified teamname
-	 * 
-	 * @param teamname
-	 * @return true if successful
-	 * @since 0.8.0
-	 */
-	@Override
-	public boolean deleteTeam(String teamname) {
-		Properties allUsers = read();
-		teams.remove(teamname.toLowerCase());
-		allUsers.remove("@" + teamname);
-		try {
-			write(allUsers);
-			return true;
-		} catch (Throwable t) {
-			logger.error(MessageFormat.format("Failed to delete team {0}!", teamname), t);
-		}
-		return false;
-	}
-}
diff --git a/src/com/gitblit/GCExecutor.java b/src/com/gitblit/GCExecutor.java
deleted file mode 100644
index 312baf5..0000000
--- a/src/com/gitblit/GCExecutor.java
+++ /dev/null
@@ -1,237 +0,0 @@
-/*
- * 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.lang.reflect.Field;
-import java.text.MessageFormat;
-import java.util.Calendar;
-import java.util.Date;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicInteger;
-
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.storage.file.FileRepository;
-import org.eclipse.jgit.storage.file.GC;
-import org.eclipse.jgit.storage.file.GC.RepoStatistics;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.gitblit.models.RepositoryModel;
-import com.gitblit.utils.FileUtils;
-
-/**
- * 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();
-		}
-	}
-	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) {
-		this.settings = settings;
-	}
-
-	/**
-	 * 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
-	 */
-	private boolean setGCStatus(String repositoryName, GCStatus status) {
-		String key = repositoryName.toLowerCase();
-		if (gcCache.containsKey(key)) {
-			if (gcCache.get(key).exceeds(GCStatus.READY)) {
-				// already collecting or blocked
-				return false;
-			}
-		}
-		gcCache.put(key, status);
-		return true;
-	}
-
-	/**
-	 * Returns true if Gitblit is actively collecting garbage in this repository.
-	 * 
-	 * @param repositoryName
-	 * @return true if actively collecting garbage
-	 */
-	public boolean isCollectingGarbage(String repositoryName) {
-		String key = repositoryName.toLowerCase();
-		return gcCache.containsKey(key) && GCStatus.COLLECTING.equals(gcCache.get(key));
-	}
-
-	/**
-	 * 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);
-	}
-
-	@Override
-	public void run() {
-		if (!isReady()) {
-			return;
-		}
-		
-		running.set(true);		
-		Date now = new Date();
-
-		for (String repositoryName : GitBlit.self().getRepositoryList()) {
-			if (forceClose.get()) {
-				break;
-			}
-			if (isCollectingGarbage(repositoryName)) {
-				logger.warn(MessageFormat.format("Already collecting garbage from {0}?!?", repositoryName));
-				continue;
-			}
-			boolean garbageCollected = false;
-			RepositoryModel model = null;
-			FileRepository repository = null;
-			try {
-				model = GitBlit.self().getRepositoryModel(repositoryName);
-				repository = (FileRepository) GitBlit.self().getRepository(repositoryName);
-				if (repository == null) {
-					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;
-				}
-
-				// By setting the GCStatus to COLLECTING we are
-				// disabling *all* access to this repository from Gitblit.
-				// Think of this as a clutch in a manual transmission vehicle.
-				if (!setGCStatus(repositoryName, GCStatus.COLLECTING)) {
-					logger.warn(MessageFormat.format("Can not acquire GC lock for {0}, skipping", repositoryName));
-					continue;
-				}
-				
-				logger.debug(MessageFormat.format("GCExecutor locked idle repository {0}", repositoryName));
-				
-				GC gc = new GC(repository);
-				RepoStatistics stats = gc.getStatistics();
-				
-				// determine if this is a scheduled GC
-				Calendar cal = Calendar.getInstance();
-				cal.setTime(model.lastGC);
-				cal.set(Calendar.HOUR_OF_DAY, 0);
-				cal.set(Calendar.MINUTE, 0);
-				cal.set(Calendar.SECOND, 0);
-				cal.set(Calendar.MILLISECOND, 0);
-				cal.add(Calendar.DATE, model.gcPeriod);
-				Date gcDate = cal.getTime();
-				boolean shouldCollectGarbage = now.after(gcDate);
-
-				// determine if filesize triggered GC
-				long gcThreshold = FileUtils.convertSizeToLong(model.gcThreshold, 500*1024L);
-				boolean hasEnoughGarbage = stats.sizeOfLooseObjects >= gcThreshold;
-
-				// if we satisfy one of the requirements, GC
-				boolean hasGarbage = stats.sizeOfLooseObjects > 0;
-				if (hasGarbage && (hasEnoughGarbage || shouldCollectGarbage)) {
-					long looseKB = stats.sizeOfLooseObjects/1024L;
-					logger.info(MessageFormat.format("Collecting {1} KB of loose objects from {0}", repositoryName, looseKB));
-					
-					// do the deed
-					gc.gc();
-					
-					garbageCollected = true;
-				}
-			} catch (Exception e) {
-				logger.error("Error collecting garbage in " + repositoryName, e);
-			} finally {
-				// cleanup
-				if (repository != null) {
-					if (garbageCollected) {
-						// update the last GC date
-						model.lastGC = new Date();
-						GitBlit.self().updateConfiguration(repository, model);
-					}
-				
-					repository.close();
-				}
-				
-				// reset the GC lock 
-				releaseLock(repositoryName);
-				logger.debug(MessageFormat.format("GCExecutor released GC lock for {0}", repositoryName));
-			}
-		}
-		
-		running.set(false);
-	}
-	
-	private boolean isRepositoryIdle(FileRepository repository) {
-		try {
-			// Read the use count.
-			// An idle use count is 2:
-			// +1 for being in the cache
-			// +1 for the repository parameter in this method
-			Field useCnt = Repository.class.getDeclaredField("useCnt");
-			useCnt.setAccessible(true);
-			int useCount = ((AtomicInteger) useCnt.get(repository)).get();
-			return useCount == 2;
-		} catch (Exception e) {
-			logger.warn(MessageFormat
-					.format("Failed to reflectively determine use count for repository {0}",
-							repository.getDirectory().getPath()), e);
-		}
-		return false;
-	}
-}
diff --git a/src/com/gitblit/GitBlit.java b/src/com/gitblit/GitBlit.java
deleted file mode 100644
index 6bf75d7..0000000
--- a/src/com/gitblit/GitBlit.java
+++ /dev/null
@@ -1,3379 +0,0 @@
-/*
- * Copyright 2011 gitblit.com.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.gitblit;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileFilter;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.lang.reflect.Field;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.nio.charset.Charset;
-import java.security.Principal;
-import java.text.MessageFormat;
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Calendar;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.LinkedHashMap;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Set;
-import java.util.TimeZone;
-import java.util.TreeMap;
-import java.util.TreeSet;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.atomic.AtomicReference;
-
-import javax.mail.Message;
-import javax.mail.MessagingException;
-import javax.servlet.ServletContext;
-import javax.servlet.ServletContextEvent;
-import javax.servlet.ServletContextListener;
-import javax.servlet.http.Cookie;
-import javax.servlet.http.HttpServletRequest;
-
-import org.apache.wicket.RequestCycle;
-import org.apache.wicket.protocol.http.WebResponse;
-import org.apache.wicket.resource.ContextRelativeResource;
-import org.apache.wicket.util.resource.ResourceStreamNotFoundException;
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.lib.RepositoryCache;
-import org.eclipse.jgit.lib.RepositoryCache.FileKey;
-import org.eclipse.jgit.lib.StoredConfig;
-import org.eclipse.jgit.storage.file.FileBasedConfig;
-import org.eclipse.jgit.storage.file.WindowCache;
-import org.eclipse.jgit.storage.file.WindowCacheConfig;
-import org.eclipse.jgit.util.FS;
-import org.eclipse.jgit.util.FileUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.gitblit.Constants.AccessPermission;
-import com.gitblit.Constants.AccessRestrictionType;
-import com.gitblit.Constants.AuthenticationType;
-import com.gitblit.Constants.AuthorizationControl;
-import com.gitblit.Constants.FederationRequest;
-import com.gitblit.Constants.FederationStrategy;
-import com.gitblit.Constants.FederationToken;
-import com.gitblit.Constants.PermissionType;
-import com.gitblit.Constants.RegistrantType;
-import com.gitblit.fanout.FanoutNioService;
-import com.gitblit.fanout.FanoutService;
-import com.gitblit.fanout.FanoutSocketService;
-import com.gitblit.models.FederationModel;
-import com.gitblit.models.FederationProposal;
-import com.gitblit.models.FederationSet;
-import com.gitblit.models.ForkModel;
-import com.gitblit.models.Metric;
-import com.gitblit.models.ProjectModel;
-import com.gitblit.models.RegistrantAccessPermission;
-import com.gitblit.models.RepositoryModel;
-import com.gitblit.models.SearchResult;
-import com.gitblit.models.ServerSettings;
-import com.gitblit.models.ServerStatus;
-import com.gitblit.models.SettingModel;
-import com.gitblit.models.TeamModel;
-import com.gitblit.models.UserModel;
-import com.gitblit.utils.ArrayUtils;
-import com.gitblit.utils.Base64;
-import com.gitblit.utils.ByteFormat;
-import com.gitblit.utils.ContainerUtils;
-import com.gitblit.utils.DeepCopier;
-import com.gitblit.utils.FederationUtils;
-import com.gitblit.utils.HttpUtils;
-import com.gitblit.utils.JGitUtils;
-import com.gitblit.utils.JsonUtils;
-import com.gitblit.utils.MetricUtils;
-import com.gitblit.utils.ObjectCache;
-import com.gitblit.utils.StringUtils;
-import com.gitblit.utils.TimeUtils;
-import com.gitblit.utils.X509Utils.X509Metadata;
-import com.gitblit.wicket.GitBlitWebSession;
-import com.gitblit.wicket.WicketUtils;
-
-/**
- * GitBlit is the servlet context listener singleton that acts as the core for
- * 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 Map<String, FederationModel> federationPullResults = new ConcurrentHashMap<String, FederationModel>();
-
-	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;
-
-	private IUserService userService;
-
-	private IStoredSettings settings;
-
-	private ServerSettings settingsModel;
-
-	private ServerStatus serverStatus;
-
-	private MailExecutor mailExecutor;
-	
-	private LuceneExecutor luceneExecutor;
-	
-	private GCExecutor gcExecutor;
-	
-	private TimeZone timezone;
-	
-	private FileBasedConfig projectConfigs;
-	
-	private FanoutService fanoutService;
-
-	public GitBlit() {
-		if (gitblit == null) {
-			// set the static singleton reference
-			gitblit = this;
-		}
-	}
-
-	public GitBlit(final IUserService userService) {
-		this.userService = userService;
-		gitblit = this;
-	}
-
-	/**
-	 * Returns the Gitblit singleton.
-	 * 
-	 * @return gitblit singleton
-	 */
-	public static GitBlit self() {
-		if (gitblit == null) {
-			new GitBlit();
-		}
-		return gitblit;
-	}
-
-	/**
-	 * 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;
-	}
-	
-	/**
-	 * Returns the preferred timezone for the Gitblit instance.
-	 * 
-	 * @return a timezone
-	 */
-	public static TimeZone getTimezone() {
-		if (self().timezone == null) {
-			String tzid = getString("web.timezone", null);
-			if (StringUtils.isEmpty(tzid)) {
-				self().timezone = TimeZone.getDefault();
-				return self().timezone;
-			}
-			self().timezone = TimeZone.getTimeZone(tzid);
-		}
-		return self().timezone;
-	}
-	
-	/**
-	 * 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
-	 * @return key value or defaultValue
-	 */
-	public static boolean getBoolean(String key, boolean defaultValue) {
-		return self().settings.getBoolean(key, defaultValue);
-	}
-
-	/**
-	 * 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
-	 * @return key value or defaultValue
-	 */
-	public static int getInteger(String key, int defaultValue) {
-		return self().settings.getInteger(key, defaultValue);
-	}
-	
-	/**
-	 * 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
-	 * @return key value or defaultValue
-	 */
-	public static int getFilesize(String key, int defaultValue) {
-		return self().settings.getFilesize(key, defaultValue);
-	}
-
-	/**
-	 * 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
-	 * @return key value or defaultValue
-	 */
-	public static long getFilesize(String key, long defaultValue) {
-		return self().settings.getFilesize(key, defaultValue);
-	}
-
-	/**
-	 * 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
-	 * @return key value or defaultValue
-	 */
-	public static char getChar(String key, char defaultValue) {
-		return self().settings.getChar(key, defaultValue);
-	}
-
-	/**
-	 * 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
-	 * @return key value or defaultValue
-	 */
-	public static String getString(String key, String defaultValue) {
-		return self().settings.getString(key, defaultValue);
-	}
-
-	/**
-	 * Returns a list of space-separated strings from the specified key.
-	 * 
-	 * @see IStoredSettings.getStrings(String key)
-	 * @param name
-	 * @return list of strings
-	 */
-	public static List<String> getStrings(String key) {
-		return self().settings.getStrings(key);
-	}
-
-	/**
-	 * Returns a map of space-separated key-value pairs from the specified key.
-	 * 
-	 * @see IStoredSettings.getStrings(String key)
-	 * @param name
-	 * @return map of string, string
-	 */
-	public static Map<String, String> getMap(String key) {
-		return self().settings.getMap(key);
-	}
-
-	/**
-	 * 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
-	 */
-
-	public static List<String> getAllKeys(String startingWith) {
-		return self().settings.getAllKeys(startingWith);
-	}
-
-	/**
-	 * Is Gitblit running in debug mode?
-	 * 
-	 * @return true if Gitblit is running in debug mode
-	 */
-	public static boolean isDebugMode() {
-		return self().settings.getBoolean(Keys.web.debugMode, false);
-	}
-
-	/**
-	 * Returns the file object for the specified configuration key.
-	 * 
-	 * @return the file
-	 */
-	public static File getFileOrFolder(String key, String defaultFileOrFolder) {
-		String fileOrFolder = GitBlit.getString(key, defaultFileOrFolder);
-		return getFileOrFolder(fileOrFolder);
-	}
-
-	/**
-	 * Returns the file object which may have it's base-path determined by
-	 * environment variables for running on a cloud hosting service. All Gitblit
-	 * 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) {
-		return com.gitblit.utils.FileUtils.resolveParameter(Constants.baseFolder$,
-				self().baseFolder, fileOrFolder);
-	}
-
-	/**
-	 * 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() {
-		return getFileOrFolder(Keys.git.repositoriesFolder, "${baseFolder}/git");
-	}
-
-	/**
-	 * 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() {
-		return getFileOrFolder(Keys.federation.proposalsFolder, "${baseFolder}/proposals");
-	}
-
-	/**
-	 * 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
-	 */
-	public boolean updateSettings(Map<String, String> updatedSettings) {
-		return settings.saveSettings(updatedSettings);
-	}
-
-	public ServerStatus getStatus() {
-		// update heap memory status
-		serverStatus.heapAllocated = Runtime.getRuntime().totalMemory();
-		serverStatus.heapFree = Runtime.getRuntime().freeMemory();
-		return serverStatus;
-	}
-
-	/**
-	 * Returns the list of non-Gitblit clone urls. This allows Gitblit to
-	 * advertise alternative urls for Git client repository access.
-	 * 
-	 * @param repositoryName
-	 * @return list of non-gitblit clone urls
-	 */
-	public List<String> getOtherCloneUrls(String repositoryName) {
-		List<String> cloneUrls = new ArrayList<String>();
-		for (String url : settings.getStrings(Keys.web.otherUrls)) {
-			cloneUrls.add(MessageFormat.format(url, repositoryName));
-		}
-		return cloneUrls;
-	}
-
-	/**
-	 * Set the user service. The user service authenticates all users and is
-	 * responsible for managing user permissions.
-	 * 
-	 * @param userService
-	 */
-	public void setUserService(IUserService userService) {
-		logger.info("Setting up user service " + userService.toString());
-		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
-	 */
-	public boolean supportsCredentialChanges(UserModel user) {
-		return (user != null && user.isLocalAccount()) || userService.supportsCredentialChanges();
-	}
-
-	/**
-	 * Returns true if the user's display name can be changed.
-	 * 
-	 * @param user
-	 * @return true if the user service supports display name changes
-	 */
-	public boolean supportsDisplayNameChanges(UserModel user) {
-		return (user != null && user.isLocalAccount()) || userService.supportsDisplayNameChanges();
-	}
-
-	/**
-	 * Returns true if the user's email address can be changed.
-	 * 
-	 * @param user
-	 * @return true if the user service supports email address changes
-	 */
-	public boolean supportsEmailAddressChanges(UserModel user) {
-		return (user != null && user.isLocalAccount()) || userService.supportsEmailAddressChanges();
-	}
-
-	/**
-	 * Returns true if the user's team memberships can be changed.
-	 * 
-	 * @param user
-	 * @return true if the user service supports team membership changes
-	 */
-	public boolean supportsTeamMembershipChanges(UserModel user) {
-		return (user != null && user.isLocalAccount()) || userService.supportsTeamMembershipChanges();
-	}
-
-	/**
-	 * Authenticate a user based on a username and password.
-	 * 
-	 * @see IUserService.authenticate(String, char[])
-	 * @param username
-	 * @param password
-	 * @return a user object or null
-	 */
-	public UserModel authenticate(String username, char[] password) {
-		if (StringUtils.isEmpty(username)) {
-			// can not authenticate empty username
-			return null;
-		}
-		String pw = new String(password);
-		if (StringUtils.isEmpty(pw)) {
-			// can not authenticate empty password
-			return null;
-		}
-
-		// check to see if this is the federation user
-		if (canFederate()) {
-			if (username.equalsIgnoreCase(Constants.FEDERATION_USER)) {
-				List<String> tokens = getFederationTokens();
-				if (tokens.contains(pw)) {
-					// the federation user is an administrator
-					UserModel federationUser = new UserModel(Constants.FEDERATION_USER);
-					federationUser.canAdmin = true;
-					return federationUser;
-				}
-			}
-		}
-
-		// delegate authentication to the user service
-		if (userService == null) {
-			return null;
-		}
-		return userService.authenticate(username, password);
-	}
-
-	/**
-	 * Authenticate a user based on their cookie.
-	 * 
-	 * @param cookies
-	 * @return a user object or null
-	 */
-	protected UserModel authenticate(Cookie[] cookies) {
-		if (userService == null) {
-			return null;
-		}
-		if (userService.supportsCookies()) {
-			if (cookies != null && cookies.length > 0) {
-				for (Cookie cookie : cookies) {
-					if (cookie.getName().equals(Constants.NAME)) {
-						String value = cookie.getValue();
-						return userService.authenticate(value.toCharArray());
-					}
-				}
-			}
-		}
-		return null;
-	}
-
-	/**
-	 * 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
-	 */
-	public UserModel authenticate(HttpServletRequest httpRequest, boolean requiresCertificate) {
-		// try to authenticate by certificate
-		boolean checkValidity = settings.getBoolean(Keys.git.enforceCertificateValidity, true);
-		String [] oids = getStrings(Keys.git.certificateUsernameOIDs).toArray(new String[0]);
-		UserModel model = HttpUtils.getUserModelFromCertificate(httpRequest, checkValidity, oids);
-		if (model != null) {
-			// grab real user model and preserve certificate serial number
-			UserModel user = getUserModel(model.username);
-			X509Metadata metadata = HttpUtils.getCertificateMetadata(httpRequest);
-			if (user != null) {
-				flagWicketSession(AuthenticationType.CERTIFICATE);
-				logger.info(MessageFormat.format("{0} authenticated by client certificate {1} from {2}",
-						user.username, metadata.serialNumber, httpRequest.getRemoteAddr()));
-				return user;
-			} else {
-				logger.warn(MessageFormat.format("Failed to find UserModel for {0}, attempted client certificate ({1}) authentication from {2}",
-						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) {
-			UserModel user = getUserModel(principal.getName());
-			if (user != null) {
-				flagWicketSession(AuthenticationType.CONTAINER);
-				logger.info(MessageFormat.format("{0} authenticated by servlet container principal from {1}",
-						user.username, httpRequest.getRemoteAddr()));
-				return user;
-			} else {
-				logger.warn(MessageFormat.format("Failed to find UserModel for {0}, attempted servlet container authentication from {1}",
-						principal.getName(), httpRequest.getRemoteAddr()));
-			}
-		}
-		
-		// try to authenticate by cookie
-		if (allowCookieAuthentication()) {
-			UserModel user = authenticate(httpRequest.getCookies());
-			if (user != null) {
-				flagWicketSession(AuthenticationType.COOKIE);
-				logger.info(MessageFormat.format("{0} authenticated by cookie from {1}",
-						user.username, httpRequest.getRemoteAddr()));
-				return user;
-			}
-		}
-		
-		// try to authenticate by BASIC
-		final String authorization = httpRequest.getHeader("Authorization");
-		if (authorization != null && authorization.startsWith("Basic")) {
-			// Authorization: Basic base64credentials
-			String base64Credentials = authorization.substring("Basic".length()).trim();
-			String credentials = new String(Base64.decode(base64Credentials),
-					Charset.forName("UTF-8"));
-			// credentials = username:password
-			final String[] values = credentials.split(":",2);
-
-			if (values.length == 2) {
-				String username = values[0];
-				char[] password = values[1].toCharArray();
-				UserModel user = authenticate(username, password);
-				if (user != null) {
-					flagWicketSession(AuthenticationType.CREDENTIALS);
-					logger.info(MessageFormat.format("{0} authenticated by BASIC request header from {1}",
-							user.username, httpRequest.getRemoteAddr()));
-					return user;
-				} else {
-					logger.warn(MessageFormat.format("Failed login attempt for {0}, invalid credentials ({1}) from {2}", 
-							username, credentials, httpRequest.getRemoteAddr()));
-				}
-			}
-		}
-		return null;
-	}
-	
-	protected void flagWicketSession(AuthenticationType authenticationType) {
-		RequestCycle requestCycle = RequestCycle.get();
-		if (requestCycle != null) {
-			// flag the Wicket session, if this is a Wicket request
-			GitBlitWebSession session = GitBlitWebSession.get();
-			session.authenticationType = authenticationType;
-		}
-	}
-
-	/**
-	 * Open a file resource using the Servlet container.
-	 * @param file to open
-	 * @return InputStream of the opened file
-	 * @throws ResourceStreamNotFoundException
-	 */
-	public InputStream getResourceAsStream(String file) throws ResourceStreamNotFoundException {
-		ContextRelativeResource res = WicketUtils.getResource(file);
-		return res.getResourceStream().getInputStream();
-	}
-
-	/**
-	 * Sets a cookie for the specified user.
-	 * 
-	 * @param response
-	 * @param user
-	 */
-	public void setCookie(WebResponse response, UserModel user) {
-		if (userService == null) {
-			return;
-		}
-		if (userService.supportsCookies()) {
-			Cookie userCookie;
-			if (user == null) {
-				// clear cookie for logout
-				userCookie = new Cookie(Constants.NAME, "");
-			} else {
-				// set cookie for login
-				String cookie = userService.getCookie(user);
-				if (StringUtils.isEmpty(cookie)) {
-					// create empty cookie
-					userCookie = new Cookie(Constants.NAME, "");
-				} else {
-					// create real cookie
-					userCookie = new Cookie(Constants.NAME, cookie);
-					userCookie.setMaxAge(Integer.MAX_VALUE);
-				}
-			}
-			userCookie.setPath("/");
-			response.addCookie(userCookie);
-		}
-	}
-	
-	/**
-	 * Logout a user.
-	 * 
-	 * @param user
-	 */
-	public void logout(UserModel user) {
-		if (userService == null) {
-			return;
-		}
-		userService.logout(user);
-	}
-
-	/**
-	 * Returns the list of all users available to the login service.
-	 * 
-	 * @see IUserService.getAllUsernames()
-	 * @return list of all usernames
-	 */
-	public List<String> getAllUsernames() {
-		List<String> names = new ArrayList<String>(userService.getAllUsernames());
-		return names;
-	}
-
-	/**
-	 * Returns the list of all users available to the login service.
-	 * 
-	 * @see IUserService.getAllUsernames()
-	 * @return list of all usernames
-	 */
-	public List<UserModel> getAllUsers() {
-		List<UserModel> users = userService.getAllUsers();
-		return users;
-	}
-
-	/**
-	 * Delete the user object with the specified username
-	 * 
-	 * @see IUserService.deleteUser(String)
-	 * @param username
-	 * @return true if successful
-	 */
-	public boolean deleteUser(String username) {
-		if (StringUtils.isEmpty(username)) {
-			return false;
-		}
-		return userService.deleteUser(username);
-	}
-
-	/**
-	 * Retrieve the user object for the specified username.
-	 * 
-	 * @see IUserService.getUserModel(String)
-	 * @param username
-	 * @return a user object or null
-	 */
-	public UserModel getUserModel(String username) {
-		if (StringUtils.isEmpty(username)) {
-			return null;
-		}
-		UserModel user = userService.getUserModel(username);		
-		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
-	 */
-	public List<RegistrantAccessPermission> getUserAccessPermissions(UserModel user) {
-		if (StringUtils.isEmpty(user.username)) {
-			// new user
-			return new ArrayList<RegistrantAccessPermission>();
-		}
-		Set<RegistrantAccessPermission> set = new LinkedHashSet<RegistrantAccessPermission>();
-		set.addAll(user.getRepositoryPermissions());
-		// Flag missing repositories
-		for (RegistrantAccessPermission permission : set) {
-			if (permission.mutable && PermissionType.EXPLICIT.equals(permission.permissionType)) {
-				RepositoryModel rm = GitBlit.self().getRepositoryModel(permission.registrant);
-				if (rm == null) {
-					permission.permissionType = PermissionType.MISSING;
-					permission.mutable = false;
-					continue;
-				}
-			}
-		}
-
-		// TODO reconsider ownership as a user property
-		// manually specify personal repository ownerships
-		for (RepositoryModel rm : repositoryListCache.values()) {
-			if (rm.isUsersPersonalRepository(user.username) || rm.isOwner(user.username)) {
-				RegistrantAccessPermission rp = new RegistrantAccessPermission(rm.name, AccessPermission.REWIND,
-						PermissionType.OWNER, RegistrantType.REPOSITORY, null, false);
-				// user may be owner of a repository to which they've inherited
-				// a team permission, replace any existing perm with owner perm
-				set.remove(rp);
-				set.add(rp);
-			}
-		}
-		
-		List<RegistrantAccessPermission> list = new ArrayList<RegistrantAccessPermission>(set);
-		Collections.sort(list);
-		return list;
-	}
-
-	/**
-	 * 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
-	 */
-	public List<RegistrantAccessPermission> getUserAccessPermissions(RepositoryModel repository) {
-		List<RegistrantAccessPermission> list = new ArrayList<RegistrantAccessPermission>();
-		if (AccessRestrictionType.NONE.equals(repository.accessRestriction)) {
-			// no permissions needed, REWIND for everyone!
-			return list;
-		}
-		if (AuthorizationControl.AUTHENTICATED.equals(repository.authorizationControl)) {
-			// no permissions needed, REWIND for authenticated!
-			return list;
-		}
-		// NAMED users and teams
-		for (UserModel user : userService.getAllUsers()) {
-			RegistrantAccessPermission ap = user.getRepositoryPermission(repository);
-			if (ap.permission.exceeds(AccessPermission.NONE)) {
-				list.add(ap);
-			}
-		}
-		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
-	 */
-	public boolean setUserAccessPermissions(RepositoryModel repository, Collection<RegistrantAccessPermission> permissions) {
-		List<UserModel> users = new ArrayList<UserModel>();
-		for (RegistrantAccessPermission up : permissions) {
-			if (up.mutable) {
-				// only set editable defined permissions
-				UserModel user = userService.getUserModel(up.registrant);
-				user.setRepositoryPermission(repository.name, up.permission);
-				users.add(user);
-			}
-		}
-		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
-	 */
-	public List<String> getRepositoryUsers(RepositoryModel repository) {
-		return userService.getUsernamesForRepositoryRole(repository.name);
-	}
-
-	/**
-	 * 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
-	 * @return true if successful
-	 */
-	@Deprecated
-	public boolean setRepositoryUsers(RepositoryModel repository, List<String> repositoryUsers) {
-		// rejects all changes since 1.2.0 because this would elevate
-		// all discrete access permissions to RW+
-		return false;
-	}
-
-	/**
-	 * 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
-	 * @param isCreate
-	 * @throws GitBlitException
-	 */
-	public void updateUserModel(String username, UserModel user, boolean isCreate)
-			throws GitBlitException {
-		if (!username.equalsIgnoreCase(user.username)) {
-			if (userService.getUserModel(user.username) != null) {
-				throw new GitBlitException(MessageFormat.format(
-						"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)) {
-					// personal repository
-					model.addOwner(user.username);
-					String oldRepositoryName = model.name;
-					model.name = "~" + user.username + model.name.substring(model.projectPath.length());
-					model.projectPath = "~" + user.username;
-					updateRepositoryModel(oldRepositoryName, model, false);
-				} else if (model.isOwner(username)) {
-					// common/shared repo
-					model.addOwner(user.username);
-					updateRepositoryModel(model.name, model, false);
-				}
-			}
-		}
-		if (!userService.updateUserModel(username, user)) {
-			throw new GitBlitException(isCreate ? "Failed to add user!" : "Failed to update user!");
-		}
-	}
-
-	/**
-	 * Returns the list of available teams that a user or repository may be
-	 * assigned to.
-	 * 
-	 * @return the list of teams
-	 */
-	public List<String> getAllTeamnames() {
-		List<String> teams = new ArrayList<String>(userService.getAllTeamNames());
-		return teams;
-	}
-
-	/**
-	 * Returns the list of available teams that a user or repository may be
-	 * assigned to.
-	 * 
-	 * @return the list of teams
-	 */
-	public List<TeamModel> getAllTeams() {
-		List<TeamModel> teams = userService.getAllTeams();
-		return teams;
-	}
-
-	/**
-	 * 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
-	 */
-	public List<RegistrantAccessPermission> getTeamAccessPermissions(RepositoryModel repository) {
-		List<RegistrantAccessPermission> list = new ArrayList<RegistrantAccessPermission>();
-		for (TeamModel team : userService.getAllTeams()) {
-			RegistrantAccessPermission ap = team.getRepositoryPermission(repository);
-			if (ap.permission.exceeds(AccessPermission.NONE)) {
-				list.add(ap);
-			}
-		}
-		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
-	 */
-	public boolean setTeamAccessPermissions(RepositoryModel repository, Collection<RegistrantAccessPermission> permissions) {
-		List<TeamModel> teams = new ArrayList<TeamModel>();
-		for (RegistrantAccessPermission tp : permissions) {
-			if (tp.mutable) {
-				// only set explicitly defined access permissions
-				TeamModel team = userService.getTeamModel(tp.registrant);
-				team.setRepositoryPermission(repository.name, tp.permission);
-				teams.add(team);
-			}
-		}
-		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
-	 */
-	public List<String> getRepositoryTeams(RepositoryModel repository) {
-		return userService.getTeamnamesForRepositoryRole(repository.name);
-	}
-
-	/**
-	 * 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
-	 * @return true if successful
-	 */
-	@Deprecated
-	public boolean setRepositoryTeams(RepositoryModel repository, List<String> repositoryTeams) {
-		// rejects all changes since 1.2.0 because this would elevate
-		// all discrete access permissions to RW+
-		return false;
-	}
-
-	/**
-	 * Updates the TeamModel object for the specified name.
-	 * 
-	 * @param teamname
-	 * @param team
-	 * @param isCreate
-	 */
-	public void updateTeamModel(String teamname, TeamModel team, boolean isCreate)
-			throws GitBlitException {
-		if (!teamname.equalsIgnoreCase(team.name)) {
-			if (userService.getTeamModel(team.name) != null) {
-				throw new GitBlitException(MessageFormat.format(
-						"Failed to rename ''{0}'' because ''{1}'' already exists.", teamname,
-						team.name));
-			}
-		}
-		if (!userService.updateTeamModel(teamname, team)) {
-			throw new GitBlitException(isCreate ? "Failed to add team!" : "Failed to update team!");
-		}
-	}
-
-	/**
-	 * Delete the team object with the specified teamname
-	 * 
-	 * @see IUserService.deleteTeam(String)
-	 * @param teamname
-	 * @return true if successful
-	 */
-	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)) {
-					RepositoryModel origin = repositoryListCache.get(model.originRepository);
-					origin.addFork(model.name);
-				}
-			}
-		}
-	}
-	
-	/**
-	 * Removes the repository from the list of cached repositories.
-	 * 
-	 * @param name
-	 * @return the model being removed
-	 */
-	private RepositoryModel removeFromCachedRepositoryList(String name) {
-		if (StringUtils.isEmpty(name)) {
-			return null;
-		}
-		return repositoryListCache.remove(name.toLowerCase());
-	}
-
-	/**
-	 * 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
-	 */
-	private String getRepositoryListSettingsChecksum() {
-		StringBuilder ns = new StringBuilder();
-		ns.append(settings.getString(Keys.git.cacheRepositoryList, "")).append('\n');
-		ns.append(settings.getString(Keys.git.onlyAccessBareRepositories, "")).append('\n');
-		ns.append(settings.getString(Keys.git.searchRepositoriesSubfolders, "")).append('\n');
-		ns.append(settings.getString(Keys.git.searchRecursionDepth, "")).append('\n');
-		ns.append(settings.getString(Keys.git.searchExclusions, "")).append('\n');
-		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() {
-		String newChecksum = getRepositoryListSettingsChecksum();
-		boolean valid = newChecksum.equals(repositoryListSettingsChecksum.get());
-		repositoryListSettingsChecksum.set(newChecksum);
-		if (!valid && settings.getBoolean(Keys.git.cacheRepositoryList,  true)) {
-			logger.info("Repository list settings have changed. Clearing repository list cache.");
-			repositoryListCache.clear();
-		}
-		return valid;
-	}
-
-	/**
-	 * 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, 
-					settings.getBoolean(Keys.git.onlyAccessBareRepositories, false),
-					settings.getBoolean(Keys.git.searchRepositoriesSubfolders, true),
-					settings.getInteger(Keys.git.searchRecursionDepth, -1),
-					settings.getStrings(Keys.git.searchExclusions));
-
-			if (!settings.getBoolean(Keys.git.cacheRepositoryList,  true)) {
-				// we are not caching
-				StringUtils.sortRepositorynames(repositories);
-				return repositories;
-			} else {
-				// we are caching this list
-				String msg = "{0} repositories identified in {1} msecs";
-
-				// optionally (re)calculate repository sizes
-				if (getBoolean(Keys.web.showRepositorySizes, true)) {
-					msg = "{0} repositories identified with calculated folder sizes in {1} msecs";
-					for (String repository : repositories) {
-						RepositoryModel model = getRepositoryModel(repository);
-						if (!model.skipSizeCalculation) {
-							calculateSize(model);
-						}
-					}
-				} else {
-					// update cache
-					for (String repository : repositories) {
-						getRepositoryModel(repository);
-					}
-				}
-				
-				// rebuild fork networks
-				for (RepositoryModel model : repositoryListCache.values()) {
-					if (!StringUtils.isEmpty(model.originRepository)) {
-						if (repositoryListCache.containsKey(model.originRepository)) {
-							RepositoryModel origin = repositoryListCache.get(model.originRepository);
-							origin.addFork(model.name);
-						}
-					}
-				}
-				
-				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>(repositoryListCache.keySet());		
-		StringUtils.sortRepositorynames(list);
-		return list;
-	}
-
-	/**
-	 * Returns the JGit repository for the specified name.
-	 * 
-	 * @param repositoryName
-	 * @return repository or null
-	 */
-	public Repository getRepository(String repositoryName) {
-		return getRepository(repositoryName, true);
-	}
-
-	/**
-	 * Returns the JGit repository for the specified name.
-	 * 
-	 * @param repositoryName
-	 * @param logError
-	 * @return repository or null
-	 */
-	public Repository getRepository(String repositoryName, boolean logError) {
-		if (isCollectingGarbage(repositoryName)) {
-			logger.warn(MessageFormat.format("Rejecting request for {0}, busy collecting garbage!", repositoryName));
-			return null;
-		}
-
-		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);
-			r = RepositoryCache.open(key, true);
-		} catch (IOException e) {
-			if (logError) {
-				logger.error("GitBlit.getRepository(String) failed to find "
-						+ new File(repositoriesFolder, repositoryName).getAbsolutePath());
-			}
-		}
-		return r;
-	}
-
-	/**
-	 * Returns the list of repository models that are accessible to the user.
-	 * 
-	 * @param user
-	 * @return list of repository models accessible to user
-	 */
-	public List<RepositoryModel> getRepositoryModels(UserModel user) {
-		long methodStart = System.currentTimeMillis();
-		List<String> list = getRepositoryList();
-		List<RepositoryModel> repositories = new ArrayList<RepositoryModel>();
-		for (String repo : list) {
-			RepositoryModel model = getRepositoryModel(user, repo);
-			if (model != null) {
-				repositories.add(model);
-			}
-		}
-		if (getBoolean(Keys.web.showRepositorySizes, true)) {
-			int repoCount = 0;
-			long startTime = System.currentTimeMillis();
-			ByteFormat byteFormat = new ByteFormat();
-			for (RepositoryModel model : repositories) {
-				if (!model.skipSizeCalculation) {
-					repoCount++;
-					model.size = byteFormat.format(calculateSize(model));
-				}
-			}
-			long duration = System.currentTimeMillis() - startTime;
-			if (duration > 250) {
-				// only log calcualtion time if > 250 msecs
-				logger.info(MessageFormat.format("{0} repository sizes calculated in {1} msecs",
-					repoCount, duration));
-			}
-		}
-		long duration = System.currentTimeMillis() - methodStart;
-		logger.info(MessageFormat.format("{0} repository models loaded for {1} in {2} msecs",
-				repositories.size(), user == null ? "anonymous" : user.username, duration));
-		return repositories;
-	}
-
-	/**
-	 * Returns a repository model if the repository exists and the user may
-	 * access the repository.
-	 * 
-	 * @param user
-	 * @param repositoryName
-	 * @return repository model or null
-	 */
-	public RepositoryModel getRepositoryModel(UserModel user, String repositoryName) {
-		RepositoryModel model = getRepositoryModel(repositoryName);
-		if (model == null) {
-			return null;
-		}
-		if (user == null) {
-			user = UserModel.ANONYMOUS;
-		}
-		if (user.canView(model)) {
-			return model;
-		}
-		return null;
-	}
-
-	/**
-	 * Returns the repository model for the specified repository. This method
-	 * does not consider user access permissions.
-	 * 
-	 * @param repositoryName
-	 * @return repository model or null
-	 */
-	public RepositoryModel getRepositoryModel(String repositoryName) {
-		if (!repositoryListCache.containsKey(repositoryName)) {
-			RepositoryModel model = loadRepositoryModel(repositoryName);
-			if (model == null) {
-				return null;
-			}
-			addToCachedRepositoryList(model);
-			return model;
-		}
-		
-		// cached model
-		RepositoryModel model = repositoryListCache.get(repositoryName.toLowerCase());
-
-		if (gcExecutor.isCollectingGarbage(model.name)) {
-			// Gitblit is busy collecting garbage, use our cached model
-			RepositoryModel rm = DeepCopier.copy(model);
-			rm.isCollectingGarbage = true;
-			return rm;
-		}
-
-		// check for updates
-		Repository r = getRepository(model.name);
-		if (r == null) {
-			// repository is missing
-			removeFromCachedRepositoryList(repositoryName);
-			logger.error(MessageFormat.format("Repository \"{0}\" is missing! Removing from cache.", repositoryName));
-			return null;
-		}
-		
-		FileBasedConfig config = (FileBasedConfig) getRepositoryConfig(r);
-		if (config.isOutdated()) {
-			// reload model
-			logger.info(MessageFormat.format("Config for \"{0}\" has changed. Reloading model and updating cache.", repositoryName));
-			model = loadRepositoryModel(model.name);
-			removeFromCachedRepositoryList(model.name);
-			addToCachedRepositoryList(model);
-		} else {
-			// update a few repository parameters 
-			if (!model.hasCommits) {
-				// update hasCommits, assume a repository only gains commits :)
-				model.hasCommits = JGitUtils.hasCommits(r);
-			}
-
-			model.lastChange = JGitUtils.getLastChange(r);
-		}
-		r.close();
-		
-		// return a copy of the cached model
-		return DeepCopier.copy(model);
-	}
-	
-	
-	/**
-	 * 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) {
-			}
-
-			// project configs
-			String rootName = GitBlit.getString(Keys.web.repositoryRootGroupName, "main");
-			ProjectModel rootProject = new ProjectModel(rootName, true);
-
-			Map<String, ProjectModel> configs = new HashMap<String, ProjectModel>();
-			// cache the root project under its alias and an empty path
-			configs.put("", rootProject);
-			configs.put(rootProject.name.toLowerCase(), rootProject);
-
-			for (String name : projectConfigs.getSubsections("project")) {
-				ProjectModel project;
-				if (name.equalsIgnoreCase(rootName)) {
-					project = rootProject;
-				} else {
-					project = new ProjectModel(name);
-				}
-				project.title = projectConfigs.getString("project", name, "title");
-				project.description = projectConfigs.getString("project", name, "description");
-				
-				// project markdown
-				File pmkd = new File(getRepositoriesFolder(), (project.isRoot ? "" : name) + "/project.mkd");
-				if (pmkd.exists()) {
-					Date lm = new Date(pmkd.lastModified());
-					if (!projectMarkdownCache.hasCurrent(name, lm)) {
-						String mkd = com.gitblit.utils.FileUtils.readContent(pmkd,  "\n");
-						projectMarkdownCache.updateObject(name, lm, mkd);
-					}
-					project.projectMarkdown = projectMarkdownCache.getObject(name);
-				}
-				
-				// project repositories markdown
-				File rmkd = new File(getRepositoriesFolder(), (project.isRoot ? "" : name) + "/repositories.mkd");
-				if (rmkd.exists()) {
-					Date lm = new Date(rmkd.lastModified());
-					if (!projectRepositoriesMarkdownCache.hasCurrent(name, lm)) {
-						String mkd = com.gitblit.utils.FileUtils.readContent(rmkd,  "\n");
-						projectRepositoriesMarkdownCache.updateObject(name, lm, mkd);
-					}
-					project.repositoriesMarkdown = projectRepositoriesMarkdownCache.getObject(name);
-				}
-				
-				configs.put(name.toLowerCase(), project);
-			}
-			projectCache.clear();
-			projectCache.putAll(configs);
-		}
-		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
-	 */
-	public List<ProjectModel> getProjectModels(UserModel user, boolean includeUsers) {
-		Map<String, ProjectModel> configs = getProjectConfigs();
-
-		// per-user project lists, this accounts for security and visibility
-		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();			
-			if (!map.containsKey(rootPath)) {
-				ProjectModel project;
-				if (configs.containsKey(rootPath)) {
-					// clone the project model because it's repository list will
-					// be tailored for the requesting user
-					project = DeepCopier.copy(configs.get(rootPath));
-				} else {
-					project = new ProjectModel(rootPath);
-				}
-				map.put(rootPath, project);
-			}
-			map.get(rootPath).addRepository(model);
-		}
-		
-		// sort projects, root project first
-		List<ProjectModel> projects;
-		if (includeUsers) {
-			// all projects
-			projects = new ArrayList<ProjectModel>(map.values());
-			Collections.sort(projects);
-			projects.remove(map.get(""));
-			projects.add(0, map.get(""));
-		} else {
-			// all non-user projects
-			projects = new ArrayList<ProjectModel>();
-			ProjectModel root = map.remove("");
-			for (ProjectModel model : map.values()) {
-				if (!model.isUserProject()) {
-					projects.add(model);
-				}
-			}
-			Collections.sort(projects);
-			projects.add(0, root);
-		}
-		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
-	 */
-	public ProjectModel getProjectModel(String name, UserModel user) {
-		for (ProjectModel project : getProjectModels(user, true)) {
-			if (project.name.equalsIgnoreCase(name)) {
-				return project;
-			}
-		}
-		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
-	 */
-	public ProjectModel getProjectModel(String name) {
-		Map<String, ProjectModel> configs = getProjectConfigs();
-		ProjectModel project = configs.get(name.toLowerCase());
-		if (project == null) {
-			project = new ProjectModel(name);
-			if (name.length() > 0 && name.charAt(0) == '~') {
-				UserModel user = getUserModel(name.substring(1));
-				if (user != null) {
-					project.title = user.getDisplayName();
-					project.description = "personal repositories";
-				}
-			}
-		} else {
-			// clone the object
-			project = DeepCopier.copy(project);
-		}
-		if (StringUtils.isEmpty(name)) {
-			// get root repositories
-			for (String repository : getRepositoryList()) {
-				if (repository.indexOf('/') == -1) {
-					project.addRepository(repository);
-				}
-			}
-		} else {
-			// get repositories in subfolder
-			String folder = name.toLowerCase() + "/";
-			for (String repository : getRepositoryList()) {
-				if (repository.toLowerCase().startsWith(folder)) {
-					project.addRepository(repository);
-				}
-			}
-		}
-		if (project.repositories.size() == 0) {
-			// no repositories == no project
-			return null;
-		}
-		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
-	 */
-	public List<ProjectModel> getProjectModels(List<RepositoryModel> repositoryModels, boolean includeUsers) {
-		Map<String, ProjectModel> projects = new LinkedHashMap<String, ProjectModel>();
-		for (RepositoryModel repository : repositoryModels) {
-			if (!includeUsers && repository.isPersonalRepository()) {
-				// exclude personal repositories
-				continue;
-			}
-			if (!projects.containsKey(repository.projectPath)) {
-				ProjectModel project = getProjectModel(repository.projectPath);
-				if (project == null) {
-					logger.warn(MessageFormat.format("excluding project \"{0}\" from project list because it is empty!",
-							repository.projectPath));
-					continue;
-				}
-				projects.put(repository.projectPath, project);
-				// clear the repo list in the project because that is the system
-				// list, not the user-accessible list and start building the
-				// user-accessible list
-				project.repositories.clear();
-				project.repositories.add(repository.name);
-				project.lastChange = repository.lastChange;
-			} else {
-				// update the user-accessible list
-				// this is used for repository count
-				ProjectModel project = projects.get(repository.projectPath);
-				project.repositories.add(repository.name);
-				if (project.lastChange.before(repository.lastChange)) {
-					project.lastChange = repository.lastChange;
-				}
-			}
-		}
-		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.
-	 * If I use the stock JGit method to get the config it already reloads the
-	 * 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
-	 */
-	private StoredConfig getRepositoryConfig(Repository r) {
-		try {
-			Field f = r.getClass().getDeclaredField("repoConfig");
-			f.setAccessible(true);
-			StoredConfig config = (StoredConfig) f.get(r);
-			return config;
-		} catch (Exception e) {
-			logger.error("Failed to retrieve \"repoConfig\" via reflection", e);
-		}
-		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
-	 */
-	private RepositoryModel loadRepositoryModel(String repositoryName) {
-		Repository r = getRepository(repositoryName);
-		if (r == null) {
-			return null;
-		}
-		RepositoryModel model = new RepositoryModel();
-		model.isBare = r.isBare();
-		File basePath = getFileOrFolder(Keys.git.repositoriesFolder, "${baseFolder}/git");
-		if (model.isBare) {
-			model.name = com.gitblit.utils.FileUtils.getRelativePath(basePath, r.getDirectory());
-		} else {
-			model.name = com.gitblit.utils.FileUtils.getRelativePath(basePath, r.getDirectory().getParentFile());
-		}
-		model.hasCommits = JGitUtils.hasCommits(r);
-		model.lastChange = JGitUtils.getLastChange(r);
-		model.projectPath = StringUtils.getFirstPathElement(repositoryName);
-		
-		StoredConfig config = r.getConfig();
-		boolean hasOrigin = !StringUtils.isEmpty(config.getString("remote", "origin", "url"));
-		
-		if (config != null) {
-			model.description = getConfig(config, "description", "");
-			model.addOwners(ArrayUtils.fromString(getConfig(config, "owner", "")));
-			model.useTickets = getConfig(config, "useTickets", false);
-			model.useDocs = getConfig(config, "useDocs", false);
-			model.allowForks = getConfig(config, "allowForks", true);
-			model.accessRestriction = AccessRestrictionType.fromName(getConfig(config,
-					"accessRestriction", settings.getString(Keys.git.defaultAccessRestriction, null)));
-			model.authorizationControl = AuthorizationControl.fromName(getConfig(config,
-					"authorizationControl", settings.getString(Keys.git.defaultAuthorizationControl, null)));
-			model.verifyCommitter = getConfig(config, "verifyCommitter", false);
-			model.showRemoteBranches = getConfig(config, "showRemoteBranches", hasOrigin);
-			model.isFrozen = getConfig(config, "isFrozen", false);
-			model.showReadme = getConfig(config, "showReadme", false);
-			model.skipSizeCalculation = getConfig(config, "skipSizeCalculation", false);
-			model.skipSummaryMetrics = getConfig(config, "skipSummaryMetrics", false);
-			model.federationStrategy = FederationStrategy.fromName(getConfig(config,
-					"federationStrategy", null));
-			model.federationSets = new ArrayList<String>(Arrays.asList(config.getStringList(
-					Constants.CONFIG_GITBLIT, null, "federationSets")));
-			model.isFederated = getConfig(config, "isFederated", false);
-			model.gcThreshold = getConfig(config, "gcThreshold", settings.getString(Keys.git.defaultGarbageCollectionThreshold, "500KB"));
-			model.gcPeriod = getConfig(config, "gcPeriod", settings.getInteger(Keys.git.defaultGarbageCollectionPeriod, 7));
-			try {
-				model.lastGC = new SimpleDateFormat(Constants.ISO8601).parse(getConfig(config, "lastGC", "1970-01-01'T'00:00:00Z"));
-			} catch (Exception e) {
-				model.lastGC = new Date(0);
-			}
-			model.maxActivityCommits = getConfig(config, "maxActivityCommits", settings.getInteger(Keys.web.maxActivityCommits, 0));
-			model.origin = config.getString("remote", "origin", "url");
-			if (model.origin != null) {
-				model.origin = model.origin.replace('\\', '/');
-			}
-			model.preReceiveScripts = new ArrayList<String>(Arrays.asList(config.getStringList(
-					Constants.CONFIG_GITBLIT, null, "preReceiveScript")));
-			model.postReceiveScripts = new ArrayList<String>(Arrays.asList(config.getStringList(
-					Constants.CONFIG_GITBLIT, null, "postReceiveScript")));
-			model.mailingLists = new ArrayList<String>(Arrays.asList(config.getStringList(
-					Constants.CONFIG_GITBLIT, null, "mailingList")));
-			model.indexedBranches = new ArrayList<String>(Arrays.asList(config.getStringList(
-					Constants.CONFIG_GITBLIT, null, "indexBranch")));
-			
-			// Custom defined properties
-			model.customFields = new LinkedHashMap<String, String>();
-			for (String aProperty : config.getNames(Constants.CONFIG_GITBLIT, Constants.CONFIG_CUSTOM_FIELDS)) {
-				model.customFields.put(aProperty, config.getString(Constants.CONFIG_GITBLIT, Constants.CONFIG_CUSTOM_FIELDS, aProperty));
-			}
-		}
-		model.HEAD = JGitUtils.getHEADRef(r);
-		model.availableRefs = JGitUtils.getAvailableHeadTargets(r);
-		model.sparkleshareId = JGitUtils.getSparkleshareId(r);
-		r.close();
-		
-		if (model.origin != null && model.origin.startsWith("file://")) {
-			// repository was cloned locally... perhaps as a fork
-			try {
-				File folder = new File(new URI(model.origin));
-				String originRepo = com.gitblit.utils.FileUtils.getRelativePath(getRepositoriesFolder(), folder);
-				if (!StringUtils.isEmpty(originRepo)) {
-					// ensure origin still exists
-					File repoFolder = new File(getRepositoriesFolder(), originRepo);
-					if (repoFolder.exists()) {
-						model.originRepository = originRepo.toLowerCase();
-					}
-				}
-			} catch (URISyntaxException e) {
-				logger.error("Failed to determine fork for " + model, e);
-			}
-		}
-		return model;
-	}
-	
-	/**
-	 * Determines if this server has the requested repository.
-	 * 
-	 * @param name
-	 * @return true if the repository exists
-	 */
-	public boolean hasRepository(String repositoryName) {
-		return hasRepository(repositoryName, false);
-	}
-	
-	/**
-	 * Determines if this server has the requested repository.
-	 * 
-	 * @param name
-	 * @param caseInsensitive
-	 * @return true if the repository exists
-	 */
-	public boolean hasRepository(String repositoryName, boolean caseSensitiveCheck) {
-		if (!caseSensitiveCheck && settings.getBoolean(Keys.git.cacheRepositoryList, true)) {
-			// if we are caching use the cache to determine availability
-			// otherwise we end up adding a phantom repository to the cache
-			return repositoryListCache.containsKey(repositoryName.toLowerCase());
-		}		
-		Repository r = getRepository(repositoryName, false);
-		if (r == null) {
-			return false;
-		}
-		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
-	 */
-	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
-	 */
-	public String getFork(String username, String origin) {
-		String userProject = "~" + username.toLowerCase();
-		if (settings.getBoolean(Keys.git.cacheRepositoryList, true)) {
-			String userPath = userProject + "/";
-
-			// collect all origin nodes in fork network
-			Set<String> roots = new HashSet<String>();
-			roots.add(origin);
-			RepositoryModel originModel = repositoryListCache.get(origin);
-			while (originModel != null) {
-				if (!ArrayUtils.isEmpty(originModel.forks)) {
-					for (String fork : originModel.forks) {
-						if (!fork.startsWith(userPath)) {
-							roots.add(fork);
-						}
-					}
-				}
-				
-				if (originModel.originRepository != null) {
-					roots.add(originModel.originRepository);
-					originModel = repositoryListCache.get(originModel.originRepository);
-				} else {
-					// break
-					originModel = null;
-				}
-			}
-			
-			for (String repository : repositoryListCache.keySet()) {
-				if (repository.startsWith(userPath)) {
-					RepositoryModel model = repositoryListCache.get(repository);
-					if (!StringUtils.isEmpty(model.originRepository)) {
-						if (roots.contains(model.originRepository)) {
-							// user has a fork in this graph
-							return model.name;
-						}
-					}
-				}
-			}
-		} else {
-			// not caching
-			ProjectModel project = getProjectModel(userProject);
-			for (String repository : project.repositories) {
-				if (repository.startsWith(userProject)) {
-					RepositoryModel model = getRepositoryModel(repository);
-					if (model.originRepository.equalsIgnoreCase(origin)) {
-						// user has a fork
-						return model.name;
-					}
-				}
-			}
-		}
-		// 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
-	 */
-	public ForkModel getForkNetwork(String repository) {
-		if (settings.getBoolean(Keys.git.cacheRepositoryList, true)) {
-			// find the root, cached
-			RepositoryModel model = repositoryListCache.get(repository.toLowerCase());
-			while (model.originRepository != null) {
-				model = repositoryListCache.get(model.originRepository);
-			}
-			ForkModel root = getForkModelFromCache(model.name);
-			return root;
-		} else {
-			// find the root, non-cached
-			RepositoryModel model = getRepositoryModel(repository.toLowerCase());
-			while (model.originRepository != null) {
-				model = getRepositoryModel(model.originRepository);
-			}
-			ForkModel root = getForkModel(model.name);
-			return root;
-		}
-	}
-	
-	private ForkModel getForkModelFromCache(String repository) {
-		RepositoryModel model = repositoryListCache.get(repository.toLowerCase());
-		if (model == null) {
-			return null;
-		}
-		ForkModel fork = new ForkModel(model);
-		if (!ArrayUtils.isEmpty(model.forks)) {
-			for (String aFork : model.forks) {
-				ForkModel fm = getForkModelFromCache(aFork);
-				if (fm != null) {
-					fork.forks.add(fm);
-				}
-			}
-		}
-		return fork;
-	}
-	
-	private ForkModel getForkModel(String repository) {
-		RepositoryModel model = getRepositoryModel(repository.toLowerCase());
-		if (model == null) {
-			return null;
-		}
-		ForkModel fork = new ForkModel(model);
-		if (!ArrayUtils.isEmpty(model.forks)) {
-			for (String aFork : model.forks) {
-				ForkModel fm = getForkModel(aFork);
-				if (fm != null) {
-					fork.forks.add(fm);
-				}
-			}
-		}
-		return fork;
-	}
-
-	/**
-	 * Returns the size in bytes of the 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
-	 */
-	public long calculateSize(RepositoryModel model) {
-		if (repositorySizeCache.hasCurrent(model.name, model.lastChange)) {
-			return repositorySizeCache.getObject(model.name);
-		}
-		File gitDir = FileKey.resolve(new File(repositoriesFolder, model.name), FS.DETECTED);
-		long size = com.gitblit.utils.FileUtils.folderSize(gitDir);
-		repositorySizeCache.updateObject(model.name, model.lastChange, size);
-		return size;
-	}
-
-	/**
-	 * Ensure that a cached repository is completely closed and its resources
-	 * are properly released.
-	 * 
-	 * @param repositoryName
-	 */
-	private void closeRepository(String repositoryName) {
-		Repository repository = getRepository(repositoryName);
-		if (repository == null) {
-			return;
-		}
-		RepositoryCache.close(repository);
-
-		// assume 2 uses in case reflection fails
-		int uses = 2;
-		try {
-			// The FileResolver caches repositories which is very useful
-			// for performance until you want to delete a repository.
-			// I have to use reflection to call close() the correct
-			// number of times to ensure that the object and ref databases
-			// are properly closed before I can delete the repository from
-			// the filesystem.
-			Field useCnt = Repository.class.getDeclaredField("useCnt");
-			useCnt.setAccessible(true);
-			uses = ((AtomicInteger) useCnt.get(repository)).get();
-		} catch (Exception e) {
-			logger.warn(MessageFormat
-					.format("Failed to reflectively determine use count for repository {0}",
-							repositoryName), e);
-		}
-		if (uses > 0) {
-			logger.info(MessageFormat
-					.format("{0}.useCnt={1}, calling close() {2} time(s) to close object and ref databases",
-							repositoryName, uses, uses));
-			for (int i = 0; i < uses; i++) {
-				repository.close();
-			}
-		}
-		
-		// close any open index writer/searcher in the Lucene executor
-		luceneExecutor.close(repositoryName);
-	}
-
-	/**
-	 * Returns the metrics for the default branch of the specified repository.
-	 * 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
-	 */
-	public List<Metric> getRepositoryDefaultMetrics(RepositoryModel model, Repository repository) {
-		if (repositoryMetricsCache.hasCurrent(model.name, model.lastChange)) {
-			return new ArrayList<Metric>(repositoryMetricsCache.getObject(model.name));
-		}
-		List<Metric> metrics = MetricUtils.getDateMetrics(repository, null, true, null, getTimezone());
-		repositoryMetricsCache.updateObject(model.name, model.lastChange, metrics);
-		return new ArrayList<Metric>(metrics);
-	}
-
-	/**
-	 * Returns the gitblit string value for the specified key. If key is not
-	 * set, returns defaultValue.
-	 * 
-	 * @param config
-	 * @param field
-	 * @param defaultValue
-	 * @return field value or defaultValue
-	 */
-	private String getConfig(StoredConfig config, String field, String defaultValue) {
-		String value = config.getString(Constants.CONFIG_GITBLIT, null, field);
-		if (StringUtils.isEmpty(value)) {
-			return defaultValue;
-		}
-		return value;
-	}
-
-	/**
-	 * Returns the gitblit boolean value for the specified key. If key is not
-	 * set, returns defaultValue.
-	 * 
-	 * @param config
-	 * @param field
-	 * @param defaultValue
-	 * @return field value or defaultValue
-	 */
-	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
-	 * @return field value or defaultValue
-	 */
-	private int getConfig(StoredConfig config, String field, int defaultValue) {
-		String value = config.getString(Constants.CONFIG_GITBLIT, null, field);
-		if (StringUtils.isEmpty(value)) {
-			return defaultValue;
-		}
-		try {
-			return Integer.parseInt(value);
-		} catch (Exception e) {
-		}
-		return defaultValue;
-	}
-
-	/**
-	 * 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
-	 * @throws GitBlitException
-	 */
-	public void updateRepositoryModel(String repositoryName, RepositoryModel repository,
-			boolean isCreate) throws GitBlitException {
-		if (gcExecutor.isCollectingGarbage(repositoryName)) {
-			throw new GitBlitException(MessageFormat.format("sorry, Gitblit is busy collecting garbage in {0}",
-					repositoryName));
-		}
-		Repository r = null;
-		String projectPath = StringUtils.getFirstPathElement(repository.name);
-		if (!StringUtils.isEmpty(projectPath)) {
-			if (projectPath.equalsIgnoreCase(getString(Keys.web.repositoryRootGroupName, "main"))) {
-				// strip leading group name
-				repository.name = repository.name.substring(projectPath.length() + 1);
-			}
-		}
-		if (isCreate) {
-			// ensure created repository name ends with .git
-			if (!repository.name.toLowerCase().endsWith(org.eclipse.jgit.lib.Constants.DOT_GIT_EXT)) {
-				repository.name += org.eclipse.jgit.lib.Constants.DOT_GIT_EXT;
-			}
-			if (hasRepository(repository.name)) {
-				throw new GitBlitException(MessageFormat.format(
-						"Can not create repository ''{0}'' because it already exists.",
-						repository.name));
-			}
-			// create repository
-			logger.info("create repository " + repository.name);
-			r = JGitUtils.createRepository(repositoriesFolder, repository.name);
-		} else {
-			// rename repository
-			if (!repositoryName.equalsIgnoreCase(repository.name)) {
-				if (!repository.name.toLowerCase().endsWith(
-						org.eclipse.jgit.lib.Constants.DOT_GIT_EXT)) {
-					repository.name += org.eclipse.jgit.lib.Constants.DOT_GIT_EXT;
-				}
-				if (new File(repositoriesFolder, repository.name).exists()) {
-					throw new GitBlitException(MessageFormat.format(
-							"Failed to rename ''{0}'' because ''{1}'' already exists.",
-							repositoryName, repository.name));
-				}
-				closeRepository(repositoryName);
-				File folder = new File(repositoriesFolder, repositoryName);
-				File destFolder = new File(repositoriesFolder, repository.name);
-				if (destFolder.exists()) {
-					throw new GitBlitException(
-							MessageFormat
-									.format("Can not rename repository ''{0}'' to ''{1}'' because ''{1}'' already exists.",
-											repositoryName, repository.name));
-				}
-				File parentFile = destFolder.getParentFile();
-				if (!parentFile.exists() && !parentFile.mkdirs()) {
-					throw new GitBlitException(MessageFormat.format(
-							"Failed to create folder ''{0}''", parentFile.getAbsolutePath()));
-				}
-				if (!folder.renameTo(destFolder)) {
-					throw new GitBlitException(MessageFormat.format(
-							"Failed to rename repository ''{0}'' to ''{1}''.", repositoryName,
-							repository.name));
-				}
-				// rename the roles
-				if (!userService.renameRepositoryRole(repositoryName, repository.name)) {
-					throw new GitBlitException(MessageFormat.format(
-							"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) {
-						Repository rf = getRepository(fork);
-						try {
-							StoredConfig config = rf.getConfig();
-							String origin = config.getString("remote", "origin", "url");
-							origin = origin.replace(repositoryName, repository.name);
-							config.setString("remote", "origin", "url", origin);
-							config.save();
-						} catch (Exception e) {
-							logger.error("Failed to update repository fork config for " + fork, e);
-						}
-						rf.close();
-					}
-				}
-				
-				// remove this repository from any origin model's fork list
-				if (!StringUtils.isEmpty(repository.originRepository)) {
-					RepositoryModel origin = repositoryListCache.get(repository.originRepository);
-					if (origin != null && !ArrayUtils.isEmpty(origin.forks)) {
-						origin.forks.remove(repositoryName);
-					}
-				}
-
-				// clear the cache
-				clearRepositoryMetadataCache(repositoryName);
-				repository.resetDisplayName();
-			}
-
-			// load repository
-			logger.info("edit repository " + repository.name);
-			r = getRepository(repository.name);
-		}
-
-		// update settings
-		if (r != null) {
-			updateConfiguration(r, repository);
-			// 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}", 
-						repository.name, currentRef, repository.HEAD));
-				if (JGitUtils.setHEADtoRef(r, repository.HEAD)) {
-					// clear the cache
-					clearRepositoryMetadataCache(repository.name);
-				}
-			}
-
-			// close the repository object
-			r.close();
-		}
-		
-		// update repository cache
-		removeFromCachedRepositoryList(repositoryName);
-		// 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
-	 *            the Gitblit repository model
-	 */
-	public void updateConfiguration(Repository r, RepositoryModel repository) {
-		StoredConfig config = r.getConfig();
-		config.setString(Constants.CONFIG_GITBLIT, null, "description", repository.description);
-		config.setString(Constants.CONFIG_GITBLIT, null, "owner", ArrayUtils.toString(repository.owners));
-		config.setBoolean(Constants.CONFIG_GITBLIT, null, "useTickets", repository.useTickets);
-		config.setBoolean(Constants.CONFIG_GITBLIT, null, "useDocs", repository.useDocs);
-		config.setBoolean(Constants.CONFIG_GITBLIT, null, "allowForks", repository.allowForks);
-		config.setString(Constants.CONFIG_GITBLIT, null, "accessRestriction", repository.accessRestriction.name());
-		config.setString(Constants.CONFIG_GITBLIT, null, "authorizationControl", repository.authorizationControl.name());
-		config.setBoolean(Constants.CONFIG_GITBLIT, null, "verifyCommitter", repository.verifyCommitter);
-		config.setBoolean(Constants.CONFIG_GITBLIT, null, "showRemoteBranches", repository.showRemoteBranches);
-		config.setBoolean(Constants.CONFIG_GITBLIT, null, "isFrozen", repository.isFrozen);
-		config.setBoolean(Constants.CONFIG_GITBLIT, null, "showReadme", repository.showReadme);
-		config.setBoolean(Constants.CONFIG_GITBLIT, null, "skipSizeCalculation", repository.skipSizeCalculation);
-		config.setBoolean(Constants.CONFIG_GITBLIT, null, "skipSummaryMetrics", repository.skipSummaryMetrics);
-		config.setString(Constants.CONFIG_GITBLIT, null, "federationStrategy",
-				repository.federationStrategy.name());
-		config.setBoolean(Constants.CONFIG_GITBLIT, null, "isFederated", repository.isFederated);
-		config.setString(Constants.CONFIG_GITBLIT, null, "gcThreshold", repository.gcThreshold);
-		if (repository.gcPeriod == settings.getInteger(Keys.git.defaultGarbageCollectionPeriod, 7)) {
-			// use default from config
-			config.unset(Constants.CONFIG_GITBLIT, null, "gcPeriod");
-		} else {
-			config.setInt(Constants.CONFIG_GITBLIT, null, "gcPeriod", repository.gcPeriod);
-		}
-		if (repository.lastGC != null) {
-			config.setString(Constants.CONFIG_GITBLIT, null, "lastGC", new SimpleDateFormat(Constants.ISO8601).format(repository.lastGC));
-		}
-		if (repository.maxActivityCommits == settings.getInteger(Keys.web.maxActivityCommits, 0)) {
-			// use default from config
-			config.unset(Constants.CONFIG_GITBLIT, null, "maxActivityCommits");
-		} else {
-			config.setInt(Constants.CONFIG_GITBLIT, null, "maxActivityCommits", repository.maxActivityCommits);
-		}
-
-		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);
-		
-		// User Defined Properties
-		if (repository.customFields != null) {
-			if (repository.customFields.size() == 0) {
-				// clear section
-				config.unsetSection(Constants.CONFIG_GITBLIT, Constants.CONFIG_CUSTOM_FIELDS);
-			} else {
-				for (Entry<String, String> property : repository.customFields.entrySet()) {
-					// set field
-					String key = property.getKey();
-					String value = property.getValue();
-					config.setString(Constants.CONFIG_GITBLIT, Constants.CONFIG_CUSTOM_FIELDS, key, value);
-				}
-			}
-		}
-
-		try {
-			config.save();
-		} catch (IOException e) {
-			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
-		if (list == null) {
-			return;
-		}
-		if (ArrayUtils.isEmpty(list)) {
-			config.unset(Constants.CONFIG_GITBLIT, null, field);
-		} else {
-			config.setStringList(Constants.CONFIG_GITBLIT, null, field, list);
-		}
-	}
-
-	/**
-	 * Deletes the repository from the file system and removes the repository
-	 * permission from all repository users.
-	 * 
-	 * @param model
-	 * @return true if successful
-	 */
-	public boolean deleteRepositoryModel(RepositoryModel model) {
-		return deleteRepository(model.name);
-	}
-
-	/**
-	 * Deletes the repository from the file system and removes the repository
-	 * permission from all repository users.
-	 * 
-	 * @param repositoryName
-	 * @return true if successful
-	 */
-	public boolean deleteRepository(String repositoryName) {
-		try {
-			closeRepository(repositoryName);
-			// clear the repository cache
-			clearRepositoryMetadataCache(repositoryName);
-			
-			RepositoryModel model = removeFromCachedRepositoryList(repositoryName);
-			if (model != null && !ArrayUtils.isEmpty(model.forks)) {
-				resetRepositoryListCache();
-			}
-
-			File folder = new File(repositoriesFolder, repositoryName);
-			if (folder.exists() && folder.isDirectory()) {
-				FileUtils.delete(folder, FileUtils.RECURSIVE | FileUtils.RETRY);
-				if (userService.deleteRepositoryRole(repositoryName)) {
-					logger.info(MessageFormat.format("Repository \"{0}\" deleted", repositoryName));
-					return true;
-				}
-			}
-		} catch (Throwable t) {
-			logger.error(MessageFormat.format("Failed to delete repository {0}", repositoryName), t);
-		}
-		return false;
-	}
-
-	/**
-	 * Returns an html version of the commit message with any global or
-	 * repository-specific regular expression substitution applied.
-	 * 
-	 * @param repositoryName
-	 * @param text
-	 * @return html version of the commit message
-	 */
-	public String processCommitMessage(String repositoryName, String text) {
-		String html = StringUtils.breakLinesForHtml(text);
-		Map<String, String> map = new HashMap<String, String>();
-		// global regex keys
-		if (settings.getBoolean(Keys.regex.global, false)) {
-			for (String key : settings.getAllKeys(Keys.regex.global)) {
-				if (!key.equals(Keys.regex.global)) {
-					String subKey = key.substring(key.lastIndexOf('.') + 1);
-					map.put(subKey, settings.getString(key, ""));
-				}
-			}
-		}
-
-		// repository-specific regex keys
-		List<String> keys = settings.getAllKeys(Keys.regex._ROOT + "."
-				+ repositoryName.toLowerCase());
-		for (String key : keys) {
-			String subKey = key.substring(key.lastIndexOf('.') + 1);
-			map.put(subKey, settings.getString(key, ""));
-		}
-
-		for (Entry<String, String> entry : map.entrySet()) {
-			String definition = entry.getValue().trim();
-			String[] chunks = definition.split("!!!");
-			if (chunks.length == 2) {
-				html = html.replaceAll(chunks[0], chunks[1]);
-			} else {
-				logger.warn(entry.getKey()
-						+ " improperly formatted.  Use !!! to separate match from replacement: "
-						+ definition);
-			}
-		}
-		return html;
-	}
-
-	/**
-	 * Returns Gitblit's scheduled executor service for scheduling tasks.
-	 * 
-	 * @return scheduledExecutor
-	 */
-	public ScheduledExecutorService executor() {
-		return scheduledExecutor;
-	}
-
-	public static boolean canFederate() {
-		String passphrase = getString(Keys.federation.passphrase, "");
-		return !StringUtils.isEmpty(passphrase);
-	}
-
-	/**
-	 * Configures this Gitblit instance to pull any registered federated gitblit
-	 * instances.
-	 */
-	private void configureFederation() {
-		boolean validPassphrase = true;
-		String passphrase = settings.getString(Keys.federation.passphrase, "");
-		if (StringUtils.isEmpty(passphrase)) {
-			logger.warn("Federation passphrase is blank! This server can not be PULLED from.");
-			validPassphrase = false;
-		}
-		if (validPassphrase) {
-			// standard tokens
-			for (FederationToken tokenType : FederationToken.values()) {
-				logger.info(MessageFormat.format("Federation {0} token = {1}", tokenType.name(),
-						getFederationToken(tokenType)));
-			}
-
-			// federation set tokens
-			for (String set : settings.getStrings(Keys.federation.sets)) {
-				logger.info(MessageFormat.format("Federation Set {0} token = {1}", set,
-						getFederationToken(set)));
-			}
-		}
-
-		// Schedule the federation executor
-		List<FederationModel> registrations = getFederationRegistrations();
-		if (registrations.size() > 0) {
-			FederationPullExecutor executor = new FederationPullExecutor(registrations, true);
-			scheduledExecutor.schedule(executor, 1, TimeUnit.MINUTES);
-		}
-	}
-
-	/**
-	 * Returns the list of federated gitblit instances that this instance will
-	 * try to pull.
-	 * 
-	 * @return list of registered gitblit instances
-	 */
-	public List<FederationModel> getFederationRegistrations() {
-		if (federationRegistrations.isEmpty()) {
-			federationRegistrations.addAll(FederationUtils.getFederationRegistrations(settings));
-		}
-		return federationRegistrations;
-	}
-
-	/**
-	 * Retrieve the specified federation registration.
-	 * 
-	 * @param name
-	 *            the name of the registration
-	 * @return a federation registration
-	 */
-	public FederationModel getFederationRegistration(String url, String name) {
-		// check registrations
-		for (FederationModel r : getFederationRegistrations()) {
-			if (r.name.equals(name) && r.url.equals(url)) {
-				return r;
-			}
-		}
-
-		// check the results
-		for (FederationModel r : getFederationResultRegistrations()) {
-			if (r.name.equals(name) && r.url.equals(url)) {
-				return r;
-			}
-		}
-		return null;
-	}
-
-	/**
-	 * Returns the list of federation sets.
-	 * 
-	 * @return list of federation sets
-	 */
-	public List<FederationSet> getFederationSets(String gitblitUrl) {
-		List<FederationSet> list = new ArrayList<FederationSet>();
-		// generate standard tokens
-		for (FederationToken type : FederationToken.values()) {
-			FederationSet fset = new FederationSet(type.toString(), type, getFederationToken(type));
-			fset.repositories = getRepositories(gitblitUrl, fset.token);
-			list.add(fset);
-		}
-		// generate tokens for federation sets
-		for (String set : settings.getStrings(Keys.federation.sets)) {
-			FederationSet fset = new FederationSet(set, FederationToken.REPOSITORIES,
-					getFederationToken(set));
-			fset.repositories = getRepositories(gitblitUrl, fset.token);
-			list.add(fset);
-		}
-		return list;
-	}
-
-	/**
-	 * Returns the list of possible federation tokens for this Gitblit instance.
-	 * 
-	 * @return list of federation tokens
-	 */
-	public List<String> getFederationTokens() {
-		List<String> tokens = new ArrayList<String>();
-		// generate standard tokens
-		for (FederationToken type : FederationToken.values()) {
-			tokens.add(getFederationToken(type));
-		}
-		// generate tokens for federation sets
-		for (String set : settings.getStrings(Keys.federation.sets)) {
-			tokens.add(getFederationToken(set));
-		}
-		return tokens;
-	}
-
-	/**
-	 * Returns the specified federation token for this Gitblit instance.
-	 * 
-	 * @param type
-	 * @return a federation token
-	 */
-	public String getFederationToken(FederationToken type) {
-		return getFederationToken(type.name());
-	}
-
-	/**
-	 * Returns the specified federation token for this Gitblit instance.
-	 * 
-	 * @param value
-	 * @return a federation token
-	 */
-	public String getFederationToken(String value) {
-		String passphrase = settings.getString(Keys.federation.passphrase, "");
-		return StringUtils.getSHA1(passphrase + "-" + value);
-	}
-
-	/**
-	 * 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
-	 */
-	public boolean validateFederationRequest(FederationRequest req, String token) {
-		String all = getFederationToken(FederationToken.ALL);
-		String unr = getFederationToken(FederationToken.USERS_AND_REPOSITORIES);
-		String jur = getFederationToken(FederationToken.REPOSITORIES);
-		switch (req) {
-		case PULL_REPOSITORIES:
-			return token.equals(all) || token.equals(unr) || token.equals(jur);
-		case PULL_USERS:
-		case PULL_TEAMS:
-			return token.equals(all) || token.equals(unr);
-		case PULL_SETTINGS:
-		case PULL_SCRIPTS:
-			return token.equals(all);
-		default:
-			break;
-		}
-		return false;
-	}
-
-	/**
-	 * Acknowledge and cache the status of a remote Gitblit instance.
-	 * 
-	 * @param identification
-	 *            the identification of the pulling Gitblit instance
-	 * @param registration
-	 *            the registration from the pulling Gitblit instance
-	 * @return true if acknowledged
-	 */
-	public boolean acknowledgeFederationStatus(String identification, FederationModel registration) {
-		// reset the url to the identification of the pulling Gitblit instance
-		registration.url = identification;
-		String id = identification;
-		if (!StringUtils.isEmpty(registration.folder)) {
-			id += "-" + registration.folder;
-		}
-		federationPullResults.put(id, registration);
-		return true;
-	}
-
-	/**
-	 * Returns the list of registration results.
-	 * 
-	 * @return the list of registration results
-	 */
-	public List<FederationModel> getFederationResultRegistrations() {
-		return new ArrayList<FederationModel>(federationPullResults.values());
-	}
-
-	/**
-	 * Submit a federation proposal. The proposal is cached locally and the
-	 * Gitblit administrator(s) are notified via email.
-	 * 
-	 * @param proposal
-	 *            the proposal
-	 * @param gitblitUrl
-	 *            the url of your gitblit instance to send an email to
-	 *            administrators
-	 * @return true if the proposal was submitted
-	 */
-	public boolean submitFederationProposal(FederationProposal proposal, String gitblitUrl) {
-		// convert proposal to json
-		String json = JsonUtils.toJsonString(proposal);
-
-		try {
-			// make the proposals folder
-			File proposalsFolder = getProposalsFolder();
-			proposalsFolder.mkdirs();
-
-			// cache json to a file
-			File file = new File(proposalsFolder, proposal.token + Constants.PROPOSAL_EXT);
-			com.gitblit.utils.FileUtils.writeContent(file, json);
-		} catch (Exception e) {
-			logger.error(MessageFormat.format("Failed to cache proposal from {0}", proposal.url), e);
-		}
-
-		// send an email, if possible
-		try {
-			Message message = mailExecutor.createMessageForAdministrators();
-			if (message != null) {
-				message.setSubject("Federation proposal from " + proposal.url);
-				message.setText("Please review the proposal @ " + gitblitUrl + "/proposal/"
-						+ proposal.token);
-				mailExecutor.queue(message);
-			}
-		} catch (Throwable t) {
-			logger.error("Failed to notify administrators of proposal", t);
-		}
-		return true;
-	}
-
-	/**
-	 * Returns the list of pending federation proposals
-	 * 
-	 * @return list of federation proposals
-	 */
-	public List<FederationProposal> getPendingFederationProposals() {
-		List<FederationProposal> list = new ArrayList<FederationProposal>();
-		File folder = getProposalsFolder();
-		if (folder.exists()) {
-			File[] files = folder.listFiles(new FileFilter() {
-				@Override
-				public boolean accept(File file) {
-					return file.isFile()
-							&& file.getName().toLowerCase().endsWith(Constants.PROPOSAL_EXT);
-				}
-			});
-			for (File file : files) {
-				String json = com.gitblit.utils.FileUtils.readContent(file, null);
-				FederationProposal proposal = JsonUtils.fromJsonString(json,
-						FederationProposal.class);
-				list.add(proposal);
-			}
-		}
-		return list;
-	}
-
-	/**
-	 * Get repositories for the specified token.
-	 * 
-	 * @param gitblitUrl
-	 *            the base url of this gitblit instance
-	 * @param token
-	 *            the federation token
-	 * @return a map of <cloneurl, RepositoryModel>
-	 */
-	public Map<String, RepositoryModel> getRepositories(String gitblitUrl, String token) {
-		Map<String, String> federationSets = new HashMap<String, String>();
-		for (String set : getStrings(Keys.federation.sets)) {
-			federationSets.put(getFederationToken(set), set);
-		}
-
-		// Determine the Gitblit clone url
-		StringBuilder sb = new StringBuilder();
-		sb.append(gitblitUrl);
-		sb.append(Constants.GIT_PATH);
-		sb.append("{0}");
-		String cloneUrl = sb.toString();
-
-		// Retrieve all available repositories
-		UserModel user = new UserModel(Constants.FEDERATION_USER);
-		user.canAdmin = true;
-		List<RepositoryModel> list = getRepositoryModels(user);
-
-		// create the [cloneurl, repositoryModel] map
-		Map<String, RepositoryModel> repositories = new HashMap<String, RepositoryModel>();
-		for (RepositoryModel model : list) {
-			// by default, setup the url for THIS repository
-			String url = MessageFormat.format(cloneUrl, model.name);
-			switch (model.federationStrategy) {
-			case EXCLUDE:
-				// skip this repository
-				continue;
-			case FEDERATE_ORIGIN:
-				// federate the origin, if it is defined
-				if (!StringUtils.isEmpty(model.origin)) {
-					url = model.origin;
-				}
-				break;
-			default:
-				break;
-			}
-
-			if (federationSets.containsKey(token)) {
-				// include repositories only for federation set
-				String set = federationSets.get(token);
-				if (model.federationSets.contains(set)) {
-					repositories.put(url, model);
-				}
-			} else {
-				// standard federation token for ALL
-				repositories.put(url, model);
-			}
-		}
-		return repositories;
-	}
-
-	/**
-	 * Creates a proposal from the token.
-	 * 
-	 * @param gitblitUrl
-	 *            the url of this Gitblit instance
-	 * @param token
-	 * @return a potential proposal
-	 */
-	public FederationProposal createFederationProposal(String gitblitUrl, String token) {
-		FederationToken tokenType = FederationToken.REPOSITORIES;
-		for (FederationToken type : FederationToken.values()) {
-			if (token.equals(getFederationToken(type))) {
-				tokenType = type;
-				break;
-			}
-		}
-		Map<String, RepositoryModel> repositories = getRepositories(gitblitUrl, token);
-		FederationProposal proposal = new FederationProposal(gitblitUrl, tokenType, token,
-				repositories);
-		return proposal;
-	}
-
-	/**
-	 * Returns the proposal identified by the supplied token.
-	 * 
-	 * @param token
-	 * @return the specified proposal or null
-	 */
-	public FederationProposal getPendingFederationProposal(String token) {
-		List<FederationProposal> list = getPendingFederationProposals();
-		for (FederationProposal proposal : list) {
-			if (proposal.token.equals(token)) {
-				return proposal;
-			}
-		}
-		return null;
-	}
-
-	/**
-	 * Deletes a pending federation proposal.
-	 * 
-	 * @param a
-	 *            proposal
-	 * @return true if the proposal was deleted
-	 */
-	public boolean deletePendingFederationProposal(FederationProposal proposal) {
-		File folder = getProposalsFolder();
-		File file = new File(folder, proposal.token + Constants.PROPOSAL_EXT);
-		return file.delete();
-	}
-
-	/**
-	 * 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() {
-		File groovyFolder = getGroovyScriptsFolder();
-		File[] files = groovyFolder.listFiles(new FileFilter() {
-			@Override
-			public boolean accept(File pathname) {
-				return pathname.isFile() && pathname.getName().endsWith(".groovy");
-			}
-		});
-		List<String> scripts = new ArrayList<String>();
-		if (files != null) {
-			for (File file : files) {
-				String script = file.getName().substring(0, file.getName().lastIndexOf('.'));
-				scripts.add(script);
-			}
-		}
-		return scripts;
-	}
-
-	/**
-	 * 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
-	 */
-	public List<String> getPreReceiveScriptsInherited(RepositoryModel repository) {
-		Set<String> scripts = new LinkedHashSet<String>();
-		// Globals
-		for (String script : getStrings(Keys.groovy.preReceiveScripts)) {
-			if (script.endsWith(".groovy")) {
-				scripts.add(script.substring(0, script.lastIndexOf('.')));
-			} else {
-				scripts.add(script);
-			}
-		}
-
-		// Team Scripts
-		if (repository != null) {
-			for (String teamname : userService.getTeamnamesForRepositoryRole(repository.name)) {
-				TeamModel team = userService.getTeamModel(teamname);
-				scripts.addAll(team.preReceiveScripts);
-			}
-		}
-		return new ArrayList<String>(scripts);
-	}
-
-	/**
-	 * 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
-	 */
-	public List<String> getPreReceiveScriptsUnused(RepositoryModel repository) {
-		Set<String> inherited = new TreeSet<String>(getPreReceiveScriptsInherited(repository));
-
-		// create list of available scripts by excluding inherited scripts
-		List<String> scripts = new ArrayList<String>();
-		for (String script : getAllScripts()) {
-			if (!inherited.contains(script)) {
-				scripts.add(script);
-			}
-		}
-		return scripts;
-	}
-
-	/**
-	 * 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
-	 */
-	public List<String> getPostReceiveScriptsInherited(RepositoryModel repository) {
-		Set<String> scripts = new LinkedHashSet<String>();
-		// Global Scripts
-		for (String script : getStrings(Keys.groovy.postReceiveScripts)) {
-			if (script.endsWith(".groovy")) {
-				scripts.add(script.substring(0, script.lastIndexOf('.')));
-			} else {
-				scripts.add(script);
-			}
-		}
-		// Team Scripts
-		if (repository != null) {
-			for (String teamname : userService.getTeamnamesForRepositoryRole(repository.name)) {
-				TeamModel team = userService.getTeamModel(teamname);
-				scripts.addAll(team.postReceiveScripts);
-			}
-		}
-		return new ArrayList<String>(scripts);
-	}
-
-	/**
-	 * 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
-	 */
-	public List<String> getPostReceiveScriptsUnused(RepositoryModel repository) {
-		Set<String> inherited = new TreeSet<String>(getPostReceiveScriptsInherited(repository));
-
-		// create list of available scripts by excluding inherited scripts
-		List<String> scripts = new ArrayList<String>();
-		for (String script : getAllScripts()) {
-			if (!inherited.contains(script)) {
-				scripts.add(script);
-			}
-		}
-		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) {		
-		List<SearchResult> srs = luceneExecutor.search(query, page, pageSize, repositories);
-		return srs;
-	}
-
-	/**
-	 * Notify the administrators by email.
-	 * 
-	 * @param subject
-	 * @param message
-	 */
-	public void sendMailToAdministrators(String subject, String message) {
-		try {
-			Message mail = mailExecutor.createMessageForAdministrators();
-			if (mail != null) {
-				mail.setSubject(subject);
-				mail.setText(message);
-				mailExecutor.queue(mail);
-			}
-		} catch (MessagingException e) {
-			logger.error("Messaging error", e);
-		}
-	}
-
-	/**
-	 * Notify users by email of something.
-	 * 
-	 * @param subject
-	 * @param message
-	 * @param toAddresses
-	 */
-	public void sendMail(String subject, String message, Collection<String> toAddresses) {
-		this.sendMail(subject, message, toAddresses.toArray(new String[0]));
-	}
-
-	/**
-	 * Notify users by email of something.
-	 * 
-	 * @param subject
-	 * @param message
-	 * @param toAddresses
-	 */
-	public void sendMail(String subject, String message, String... toAddresses) {
-		try {
-			Message mail = mailExecutor.createMessage(toAddresses);
-			if (mail != null) {
-				mail.setSubject(subject);
-				mail.setText(message);
-				mailExecutor.queue(mail);
-			}
-		} catch (MessagingException e) {
-			logger.error("Messaging error", e);
-		}
-	}
-
-	/**
-	 * Notify users by email of something.
-	 * 
-	 * @param subject
-	 * @param message
-	 * @param toAddresses
-	 */
-	public void sendHtmlMail(String subject, String message, Collection<String> toAddresses) {
-		this.sendHtmlMail(subject, message, toAddresses.toArray(new String[0]));
-	}
-
-	/**
-	 * Notify users by email of something.
-	 * 
-	 * @param subject
-	 * @param message
-	 * @param toAddresses
-	 */
-	public void sendHtmlMail(String subject, String message, String... toAddresses) {
-		try {
-			Message mail = mailExecutor.createMessage(toAddresses);
-			if (mail != null) {
-				mail.setSubject(subject);
-				mail.setContent(message, "text/html");
-				mailExecutor.queue(mail);
-			}
-		} catch (MessagingException e) {
-			logger.error("Messaging error", e);
-		}
-	}
-
-	/**
-	 * Returns the descriptions/comments of the Gitblit config settings.
-	 * 
-	 * @return SettingsModel
-	 */
-	public ServerSettings getSettingsModel() {
-		// ensure that the current values are updated in the setting models
-		for (String key : settings.getAllKeys(null)) {
-			SettingModel setting = settingsModel.get(key);
-			if (setting == null) {
-				// unreferenced setting, create a setting model
-				setting = new SettingModel();
-				setting.name = key;
-				settingsModel.add(setting);
-			}
-			setting.currentValue = settings.getString(key, "");			
-		}
-		settingsModel.pushScripts = getAllScripts();
-		return settingsModel;
-	}
-
-	/**
-	 * 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.
-	 * @param referencePropertiesInputStream
-	 * 
-	 * @return Map<String, SettingModel>
-	 */
-	private ServerSettings loadSettingModels(InputStream referencePropertiesInputStream) {
-		ServerSettings settingsModel = new ServerSettings();
-		settingsModel.supportsCredentialChanges = userService.supportsCredentialChanges();
-		settingsModel.supportsDisplayNameChanges = userService.supportsDisplayNameChanges();
-		settingsModel.supportsEmailAddressChanges = userService.supportsEmailAddressChanges();
-		settingsModel.supportsTeamMembershipChanges = userService.supportsTeamMembershipChanges();
-		try {
-			// Read bundled Gitblit properties to extract setting descriptions.
-			// This copy is pristine and only used for populating the setting
-			// models map.
-			InputStream is = referencePropertiesInputStream;
-			BufferedReader propertiesReader = new BufferedReader(new InputStreamReader(is));
-			StringBuilder description = new StringBuilder();
-			SettingModel setting = new SettingModel();
-			String line = null;
-			while ((line = propertiesReader.readLine()) != null) {
-				if (line.length() == 0) {
-					description.setLength(0);
-					setting = new SettingModel();
-				} else {
-					if (line.charAt(0) == '#') {
-						if (line.length() > 1) {
-							String text = line.substring(1).trim();
-							if (SettingModel.CASE_SENSITIVE.equals(text)) {
-								setting.caseSensitive = true;
-							} else if (SettingModel.RESTART_REQUIRED.equals(text)) {
-								setting.restartRequired = true;
-							} else if (SettingModel.SPACE_DELIMITED.equals(text)) {
-								setting.spaceDelimited = true;
-							} else if (text.startsWith(SettingModel.SINCE)) {
-								try {
-									setting.since = text.split(" ")[1];
-								} catch (Exception e) {
-									setting.since = text;
-								}
-							} else {
-								description.append(text);
-								description.append('\n');
-							}
-						}
-					} else {
-						String[] kvp = line.split("=", 2);
-						String key = kvp[0].trim();
-						setting.name = key;
-						setting.defaultValue = kvp[1].trim();
-						setting.currentValue = setting.defaultValue;
-						setting.description = description.toString().trim();
-						settingsModel.add(setting);
-						description.setLength(0);
-						setting = new SettingModel();
-					}
-				}
-			}
-			propertiesReader.close();
-		} catch (NullPointerException e) {
-			logger.error("Failed to find resource copy of gitblit.properties");
-		} catch (IOException e) {
-			logger.error("Failed to load resource copy of gitblit.properties");
-		}
-		return settingsModel;
-	}
-
-	/**
-	 * 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) {
-		this.settings = settings;
-		this.baseFolder = folder;
-
-		repositoriesFolder = getRepositoriesFolder();
-
-		logger.info("Gitblit base folder     = " + folder.getAbsolutePath());
-		logger.info("Git repositories folder = " + repositoriesFolder.getAbsolutePath());
-		logger.info("Gitblit settings        = " + settings.toString());
-
-		// prepare service executors
-		mailExecutor = new MailExecutor(settings);
-		luceneExecutor = new LuceneExecutor(settings, repositoriesFolder);
-		gcExecutor = new GCExecutor(settings);
-		
-		// calculate repository list settings checksum for future config changes
-		repositoryListSettingsChecksum.set(getRepositoryListSettingsChecksum());
-
-		// build initial repository list
-		if (settings.getBoolean(Keys.git.cacheRepositoryList,  true)) {
-			logger.info("Identifying available repositories...");
-			getRepositoryList();
-		}
-		
-		logTimezone("JVM", TimeZone.getDefault());
-		logTimezone(Constants.NAME, getTimezone());
-
-		serverStatus = new ServerStatus(isGO());
-
-		if (this.userService == null) {
-			String realm = settings.getString(Keys.realm.userService, "${baseFolder}/users.properties");
-			IUserService loginService = null;
-			try {
-				// check to see if this "file" is a login service class
-				Class<?> realmClass = Class.forName(realm);
-				loginService = (IUserService) realmClass.newInstance();
-			} catch (Throwable t) {
-				loginService = new GitblitUserService();
-			}
-			setUserService(loginService);
-		}
-		
-		// load and cache the project metadata
-		projectConfigs = new FileBasedConfig(getFileOrFolder(Keys.web.projectsFile, "${baseFolder}/projects.conf"), FS.detect());
-		getProjectConfigs();
-		
-		// schedule mail engine
-		if (mailExecutor.isReady()) {
-			logger.info("Mail executor is scheduled to process the message queue every 2 minutes.");
-			scheduledExecutor.scheduleAtFixedRate(mailExecutor, 1, 2, TimeUnit.MINUTES);
-		} else {
-			logger.warn("Mail server is not properly configured.  Mail services disabled.");
-		}
-		
-		// schedule lucene engine
-		logger.info("Lucene executor is scheduled to process indexed branches every 2 minutes.");
-		scheduledExecutor.scheduleAtFixedRate(luceneExecutor, 1, 2, TimeUnit.MINUTES);
-		
-		// schedule gc engine
-		if (gcExecutor.isReady()) {
-			logger.info("GC executor is scheduled to scan repositories every 24 hours.");
-			Calendar c = Calendar.getInstance();
-			c.set(Calendar.HOUR_OF_DAY, settings.getInteger(Keys.git.garbageCollectionHour, 0));
-			c.set(Calendar.MINUTE, 0);
-			c.set(Calendar.SECOND, 0);
-			c.set(Calendar.MILLISECOND, 0);
-			Date cd = c.getTime();
-			Date now = new Date();
-			int delay = 0;
-			if (cd.before(now)) {
-				c.add(Calendar.DATE, 1);
-				cd = c.getTime();
-			}
-			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);
-			}
-			logger.info(MessageFormat.format("Next scheculed GC scan is in {0}", when));
-			scheduledExecutor.scheduleAtFixedRate(gcExecutor, delay, 60*24, TimeUnit.MINUTES);
-		}
-		
-		if (startFederation) {
-			configureFederation();
-		}
-		
-		// Configure JGit
-		WindowCacheConfig cfg = new WindowCacheConfig();
-		
-		cfg.setPackedGitWindowSize(settings.getFilesize(Keys.git.packedGitWindowSize, cfg.getPackedGitWindowSize()));
-		cfg.setPackedGitLimit(settings.getFilesize(Keys.git.packedGitLimit, cfg.getPackedGitLimit()));
-		cfg.setDeltaBaseCacheLimit(settings.getFilesize(Keys.git.deltaBaseCacheLimit, cfg.getDeltaBaseCacheLimit()));
-		cfg.setPackedGitOpenFiles(settings.getFilesize(Keys.git.packedGitOpenFiles, cfg.getPackedGitOpenFiles()));
-		cfg.setStreamFileThreshold(settings.getFilesize(Keys.git.streamFileThreshold, cfg.getStreamFileThreshold()));
-		cfg.setPackedGitMMAP(settings.getBoolean(Keys.git.packedGitMmap, cfg.isPackedGitMMAP()));
-		
-		try {
-			WindowCache.reconfigure(cfg);
-			logger.debug(MessageFormat.format("{0} = {1,number,0}", Keys.git.packedGitWindowSize, cfg.getPackedGitWindowSize()));
-			logger.debug(MessageFormat.format("{0} = {1,number,0}", Keys.git.packedGitLimit, cfg.getPackedGitLimit()));
-			logger.debug(MessageFormat.format("{0} = {1,number,0}", Keys.git.deltaBaseCacheLimit, cfg.getDeltaBaseCacheLimit()));
-			logger.debug(MessageFormat.format("{0} = {1,number,0}", Keys.git.packedGitOpenFiles, cfg.getPackedGitOpenFiles()));
-			logger.debug(MessageFormat.format("{0} = {1,number,0}", Keys.git.streamFileThreshold, cfg.getStreamFileThreshold()));
-			logger.debug(MessageFormat.format("{0} = {1}", Keys.git.packedGitMmap, cfg.isPackedGitMMAP()));
-		} catch (IllegalArgumentException e) {
-			logger.error("Failed to configure JGit parameters!", e);
-		}
-
-		ContainerUtils.CVE_2007_0450.test();
-		
-		// startup Fanout PubSub service
-		if (settings.getInteger(Keys.fanout.port, 0) > 0) {
-			String bindInterface = settings.getString(Keys.fanout.bindInterface, null);
-			int port = settings.getInteger(Keys.fanout.port, FanoutService.DEFAULT_PORT);
-			boolean useNio = settings.getBoolean(Keys.fanout.useNio, true);
-			int limit = settings.getInteger(Keys.fanout.connectionLimit, 0);
-			
-			if (useNio) {
-				if (StringUtils.isEmpty(bindInterface)) {
-					fanoutService = new FanoutNioService(port);
-				} else {
-					fanoutService = new FanoutNioService(bindInterface, port);
-				}
-			} else {
-				if (StringUtils.isEmpty(bindInterface)) {
-					fanoutService = new FanoutSocketService(port);
-				} else {
-					fanoutService = new FanoutSocketService(bindInterface, port);
-				}
-			}
-			
-			fanoutService.setConcurrentConnectionLimit(limit);
-			fanoutService.setAllowAllChannelAnnouncements(false);
-			fanoutService.start();
-		}
-	}
-	
-	private void logTimezone(String type, TimeZone zone) {
-		SimpleDateFormat df = new SimpleDateFormat("z Z");
-		df.setTimeZone(zone);
-		String offset = df.format(new Date());
-		logger.info(type + " timezone is " + zone.getID() + " (" + offset + ")");
-	}
-
-	/**
-	 * Configure Gitblit from the web.xml, if no configuration has already been
-	 * specified.
-	 * 
-	 * @see ServletContextListener.contextInitialize(ServletContextEvent)
-	 */
-	@Override
-	public void contextInitialized(ServletContextEvent contextEvent) {
-		contextInitialized(contextEvent, contextEvent.getServletContext().getResourceAsStream("/WEB-INF/reference.properties"));
-	}
-
-	public void contextInitialized(ServletContextEvent contextEvent, InputStream referencePropertiesInputStream) {
-		servletContext = contextEvent.getServletContext();
-		if (settings == null) {
-			// Gitblit is running in a servlet container
-			ServletContext context = contextEvent.getServletContext();
-			WebXmlSettings webxmlSettings = new WebXmlSettings(context);
-			File contextFolder = new File(context.getRealPath("/"));
-			String openShift = System.getenv("OPENSHIFT_DATA_DIR");
-			
-			if (!StringUtils.isEmpty(openShift)) {
-				// Gitblit is running in OpenShift/JBoss
-				File base = new File(openShift);
-
-				// gitblit.properties setting overrides
-				File overrideFile = new File(base, "gitblit.properties");
-				webxmlSettings.applyOverrides(overrideFile);
-				
-				// Copy the included scripts to the configured groovy folder
-				File localScripts = new File(base, webxmlSettings.getString(Keys.groovy.scriptsFolder, "groovy"));
-				if (!localScripts.exists()) {
-					File warScripts = new File(contextFolder, "/WEB-INF/data/groovy");
-					if (!warScripts.equals(localScripts)) {
-						try {
-							com.gitblit.utils.FileUtils.copy(localScripts, warScripts.listFiles());
-						} catch (IOException e) {
-							logger.error(MessageFormat.format(
-									"Failed to copy included Groovy scripts from {0} to {1}",
-									warScripts, localScripts));
-						}
-					}
-				}
-				
-				// 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.getAbsolutePath());
-				
-				String path = webxmlSettings.getString(Constants.baseFolder, Constants.contextFolder$ + "/WEB-INF/data");
-				File base = com.gitblit.utils.FileUtils.resolveParameter(Constants.contextFolder$, contextFolder, path);
-				base.mkdirs();
-				
-				// try to copy the data folder contents to the baseFolder
-				File localSettings = new File(base, "gitblit.properties");
-				if (!localSettings.exists()) {
-					File contextData = new File(contextFolder, "/WEB-INF/data");
-					if (!base.equals(contextData)) {
-						try {
-							com.gitblit.utils.FileUtils.copy(base, contextData.listFiles());
-						} catch (IOException e) {
-							logger.error(MessageFormat.format(
-									"Failed to copy included data from {0} to {1}",
-								contextData, base));
-						}
-					}
-				}
-				
-				// delegate all config to baseFolder/gitblit.properties file
-				FileSettings settings = new FileSettings(localSettings.getAbsolutePath());				
-				configureContext(settings, base, true);
-			}
-		}
-		
-		settingsModel = loadSettingModels(referencePropertiesInputStream);
-		serverStatus.servletContainer = servletContext.getServerInfo();
-	}
-
-	/**
-	 * Gitblit is being shutdown either because the servlet container is
-	 * shutting down or because the servlet container is re-deploying Gitblit.
-	 */
-	@Override
-	public void contextDestroyed(ServletContextEvent contextEvent) {
-		logger.info("Gitblit context destroyed by servlet container.");
-		scheduledExecutor.shutdownNow();
-		luceneExecutor.close();
-		gcExecutor.close();
-		if (fanoutService != null) {
-			fanoutService.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
-	 */
-	public boolean isCollectingGarbage(String repositoryName) {
-		return gcExecutor.isCollectingGarbage(repositoryName);
-	}
-
-	/**
-	 * 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. 
-	 * 
-	 * @param repository
-	 * @param user
-	 * @return the repository model of the fork, if successful
-	 * @throws GitBlitException
-	 */
-	public RepositoryModel fork(RepositoryModel repository, UserModel user) throws GitBlitException {
-		String cloneName = MessageFormat.format("~{0}/{1}.git", user.username, StringUtils.stripDotGit(StringUtils.getLastPathElement(repository.name)));
-		String fromUrl = MessageFormat.format("file://{0}/{1}", repositoriesFolder.getAbsolutePath(), repository.name);
-
-		// clone the repository
-		try {
-			JGitUtils.cloneRepository(repositoriesFolder, cloneName, fromUrl, true, null);
-		} catch (Exception e) {
-			throw new GitBlitException(e);
-		}
-
-		// create a Gitblit repository model for the clone
-		RepositoryModel cloneModel = repository.cloneAs(cloneName);
-		// owner has REWIND/RW+ permissions
-		cloneModel.addOwner(user.username);
-		updateRepositoryModel(cloneName, cloneModel, false);
-
-		// add the owner of the source repository to the clone's access list
-		if (!ArrayUtils.isEmpty(repository.owners)) {
-			for (String owner : repository.owners) {
-				UserModel originOwner = getUserModel(owner);
-				if (originOwner != null) {
-					originOwner.setRepositoryPermission(cloneName, AccessPermission.CLONE);
-					updateUserModel(originOwner.username, originOwner, false);
-				}
-			}
-		}
-
-		// grant origin's user list clone permission to fork
-		List<String> users = getRepositoryUsers(repository);
-		List<UserModel> cloneUsers = new ArrayList<UserModel>();
-		for (String name : users) {
-			if (!name.equalsIgnoreCase(user.username)) {
-				UserModel cloneUser = getUserModel(name);
-				if (cloneUser.canClone(repository)) {
-					// origin user can clone origin, grant clone access to fork
-					cloneUser.setRepositoryPermission(cloneName, AccessPermission.CLONE);
-				}
-				cloneUsers.add(cloneUser);
-			}
-		}
-		userService.updateUserModels(cloneUsers);
-
-		// grant origin's team list clone permission to fork
-		List<String> teams = getRepositoryTeams(repository);
-		List<TeamModel> cloneTeams = new ArrayList<TeamModel>();
-		for (String name : teams) {
-			TeamModel cloneTeam = getTeamModel(name);
-			if (cloneTeam.canClone(repository)) {
-				// origin team can clone origin, grant clone access to fork
-				cloneTeam.setRepositoryPermission(cloneName, AccessPermission.CLONE);
-			}
-			cloneTeams.add(cloneTeam);
-		}
-		userService.updateTeamModels(cloneTeams);			
-
-		// add this clone to the cached model
-		addToCachedRepositoryList(cloneModel);
-		return cloneModel;
-	}
-
-	/**
-	 * Allow to understand if GitBlit supports and is configured to allow
-	 * cookie-based authentication.
-	 * 
-	 * @return status of Cookie authentication enablement.
-	 */
-	public boolean allowCookieAuthentication() {
-		return GitBlit.getBoolean(Keys.web.allowCookieAuthentication, true) && userService.supportsCookies();
-	}
-}
diff --git a/src/com/gitblit/GitBlitServer.java b/src/com/gitblit/GitBlitServer.java
deleted file mode 100644
index feddb93..0000000
--- a/src/com/gitblit/GitBlitServer.java
+++ /dev/null
@@ -1,620 +0,0 @@
-/*
- * Copyright 2011 gitblit.com.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.gitblit;
-
-import java.io.BufferedReader;
-import java.io.BufferedWriter;
-import java.io.File;
-import java.io.FileWriter;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.io.OutputStream;
-import java.net.InetAddress;
-import java.net.ServerSocket;
-import java.net.Socket;
-import java.net.URI;
-import java.net.URL;
-import java.net.UnknownHostException;
-import java.security.ProtectionDomain;
-import java.text.MessageFormat;
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.List;
-import java.util.Scanner;
-
-import org.eclipse.jetty.ajp.Ajp13SocketConnector;
-import org.eclipse.jetty.server.Connector;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.bio.SocketConnector;
-import org.eclipse.jetty.server.nio.SelectChannelConnector;
-import org.eclipse.jetty.server.session.HashSessionManager;
-import org.eclipse.jetty.server.ssl.SslConnector;
-import org.eclipse.jetty.server.ssl.SslSelectChannelConnector;
-import org.eclipse.jetty.server.ssl.SslSocketConnector;
-import org.eclipse.jetty.util.thread.QueuedThreadPool;
-import org.eclipse.jetty.webapp.WebAppContext;
-import org.eclipse.jgit.storage.file.FileBasedConfig;
-import org.eclipse.jgit.util.FS;
-import org.eclipse.jgit.util.FileUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.beust.jcommander.JCommander;
-import com.beust.jcommander.Parameter;
-import com.beust.jcommander.ParameterException;
-import com.beust.jcommander.Parameters;
-import com.gitblit.authority.GitblitAuthority;
-import com.gitblit.authority.NewCertificateConfig;
-import com.gitblit.utils.StringUtils;
-import com.gitblit.utils.TimeUtils;
-import com.gitblit.utils.X509Utils;
-import com.gitblit.utils.X509Utils.X509Log;
-import com.gitblit.utils.X509Utils.X509Metadata;
-import com.unboundid.ldap.listener.InMemoryDirectoryServer;
-import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig;
-import com.unboundid.ldap.listener.InMemoryListenerConfig;
-import com.unboundid.ldif.LDIFReader;
-
-/**
- * GitBlitServer is the embedded Jetty server for Gitblit GO. This class starts
- * and stops an instance of Jetty that is configured from a combination of the
- * gitblit.properties file and command line parameters. JCommander is used to
- * 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 {
-
-	private static Logger logger;
-
-	public static void main(String... args) {
-		// filter out the baseFolder parameter
-		List<String> filtered = new ArrayList<String>();
-		String folder = "data";
-		for (int i = 0; i< args.length; i++) {
-			String arg = args[i];
-			if (arg.equals("--baseFolder")) {
-				if (i + 1 == args.length) {
-					System.out.println("Invalid --baseFolder parameter!");
-					System.exit(-1);
-				} else if (args[i + 1] != ".") {
-					folder = args[i + 1];
-				}
-				i = i + 1;
-			} else {
-				filtered.add(arg);
-			}
-		}
-		
-		Params.baseFolder = folder;
-		Params params = new Params();
-		JCommander jc = new JCommander(params);
-		try {
-			jc.parse(filtered.toArray(new String[filtered.size()]));
-			if (params.help) {
-				usage(jc, null);
-			}
-		} catch (ParameterException t) {
-			usage(jc, t);
-		}
-
-		if (params.stop) {
-			stop(params);
-		} else {
-			start(params);
-		}
-	}
-
-	/**
-	 * Display the command line usage of Gitblit GO.
-	 * 
-	 * @param jc
-	 * @param t
-	 */
-	private static void usage(JCommander jc, ParameterException t) {
-		System.out.println(Constants.BORDER);
-		System.out.println(Constants.getGitBlitVersion());
-		System.out.println(Constants.BORDER);
-		System.out.println();
-		if (t != null) {
-			System.out.println(t.getMessage());
-			System.out.println();
-		}
-		if (jc != null) {
-			jc.usage();
-			System.out
-					.println("\nExample:\n  java -server -Xmx1024M -jar gitblit.jar --repositoriesFolder c:\\git --httpPort 80 --httpsPort 443");
-		}
-		System.exit(0);
-	}
-
-	/**
-	 * Stop Gitblt GO.
-	 */
-	public static void stop(Params params) {
-		try {
-			Socket s = new Socket(InetAddress.getByName("127.0.0.1"), params.shutdownPort);
-			OutputStream out = s.getOutputStream();
-			System.out.println("Sending Shutdown Request to " + Constants.NAME);
-			out.write("\r\n".getBytes());
-			out.flush();
-			s.close();
-		} catch (UnknownHostException e) {
-			e.printStackTrace();
-		} catch (IOException e) {
-			e.printStackTrace();
-		}
-	}
-
-	/**
-	 * Start Gitblit GO.
-	 */
-	private static void start(Params params) {
-		final File baseFolder = new File(Params.baseFolder).getAbsoluteFile();
-		FileSettings settings = params.FILESETTINGS;
-		if (!StringUtils.isEmpty(params.settingsfile)) {
-			if (new File(params.settingsfile).exists()) {
-				settings = new FileSettings(params.settingsfile);				
-			}
-		}
-		logger = LoggerFactory.getLogger(GitBlitServer.class);
-		logger.info(Constants.BORDER);
-		logger.info("            _____  _  _    _      _  _  _");
-		logger.info("           |  __ \\(_)| |  | |    | |(_)| |");
-		logger.info("           | |  \\/ _ | |_ | |__  | | _ | |_");
-		logger.info("           | | __ | || __|| '_ \\ | || || __|");
-		logger.info("           | |_\\ \\| || |_ | |_) || || || |_");
-		logger.info("            \\____/|_| \\__||_.__/ |_||_| \\__|");
-		int spacing = (Constants.BORDER.length() - Constants.getGitBlitVersion().length()) / 2;
-		StringBuilder sb = new StringBuilder();
-		while (spacing > 0) {
-			spacing--;
-			sb.append(' ');
-		}
-		logger.info(sb.toString() + Constants.getGitBlitVersion());
-		logger.info("");
-		logger.info(Constants.BORDER);
-
-		System.setProperty("java.awt.headless", "true");
-
-		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
-		if (params.port > 0) {
-			Connector httpConnector = createConnector(params.useNIO, params.port);
-			String bindInterface = settings.getString(Keys.server.httpBindInterface, null);
-			if (!StringUtils.isEmpty(bindInterface)) {
-				logger.warn(MessageFormat.format("Binding connector on port {0,number,0} to {1}",
-						params.port, bindInterface));
-				httpConnector.setHost(bindInterface);
-			}
-			if (params.port < 1024 && !isWindows()) {
-				logger.warn("Gitblit needs to run with ROOT permissions for ports < 1024!");
-			}
-			connectors.add(httpConnector);
-		}
-
-		// conditionally configure the https connector
-		if (params.securePort > 0) {
-			File certificatesConf = new File(baseFolder, X509Utils.CA_CONFIG);
-			File serverKeyStore = new File(baseFolder, X509Utils.SERVER_KEY_STORE);
-			File serverTrustStore = new File(baseFolder, X509Utils.SERVER_TRUST_STORE);
-			File caRevocationList = new File(baseFolder, X509Utils.CA_REVOCATION_LIST);
-
-			// generate CA & web certificates, create certificate stores
-			X509Metadata metadata = new X509Metadata("localhost", params.storePassword);
-			// set default certificate values from config file
-			if (certificatesConf.exists()) {
-				FileBasedConfig config = new FileBasedConfig(certificatesConf, FS.detect());
-				try {
-					config.load();
-				} catch (Exception e) {
-					logger.error("Error parsing " + certificatesConf, e);
-				}
-				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
-				public void log(String message) {
-					BufferedWriter writer = null;
-					try {
-						writer = new BufferedWriter(new FileWriter(new File(baseFolder, X509Utils.CERTS + File.separator + "log.txt"), true));
-						writer.write(MessageFormat.format("{0,date,yyyy-MM-dd HH:mm}: {1}", new Date(), message));
-						writer.newLine();
-						writer.flush();
-					} catch (Exception e) {
-						LoggerFactory.getLogger(GitblitAuthority.class).error("Failed to append log entry!", e);
-					} finally {
-						if (writer != null) {
-							try {
-								writer.close();
-							} catch (IOException e) {
-							}
-						}
-					}
-				}
-			});
-
-			if (serverKeyStore.exists()) {		        
-				Connector secureConnector = createSSLConnector(params.alias, serverKeyStore, serverTrustStore, params.storePassword,
-						caRevocationList, params.useNIO, params.securePort, params.requireClientCertificates);
-				String bindInterface = settings.getString(Keys.server.httpsBindInterface, null);
-				if (!StringUtils.isEmpty(bindInterface)) {
-					logger.warn(MessageFormat.format(
-							"Binding ssl connector on port {0,number,0} to {1}", params.securePort,
-							bindInterface));
-					secureConnector.setHost(bindInterface);
-				}
-				if (params.securePort < 1024 && !isWindows()) {
-					logger.warn("Gitblit needs to run with ROOT permissions for ports < 1024!");
-				}
-				connectors.add(secureConnector);
-			} else {
-				logger.warn("Failed to find or load Keystore?");
-				logger.warn("SSL connector DISABLED.");
-			}
-		}
-
-		// conditionally configure the ajp connector
-		if (params.ajpPort > 0) {
-			Connector ajpConnector = createAJPConnector(params.ajpPort);
-			String bindInterface = settings.getString(Keys.server.ajpBindInterface, null);
-			if (!StringUtils.isEmpty(bindInterface)) {
-				logger.warn(MessageFormat.format("Binding connector on port {0,number,0} to {1}",
-						params.ajpPort, bindInterface));
-				ajpConnector.setHost(bindInterface);
-			}
-			if (params.ajpPort < 1024 && !isWindows()) {
-				logger.warn("Gitblit needs to run with ROOT permissions for ports < 1024!");
-			}
-			connectors.add(ajpConnector);
-		}
-
-		// 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);		
-		if (tempDir.exists()) {
-			try {
-				FileUtils.delete(tempDir, FileUtils.RECURSIVE | FileUtils.RETRY);
-			} catch (IOException x) {
-				logger.warn("Failed to delete temp dir " + tempDir.getAbsolutePath(), x);
-			}
-		}
-		if (!tempDir.mkdirs()) {
-			logger.warn("Failed to create temp dir " + tempDir.getAbsolutePath());
-		}
-
-		Server server = new Server();
-		server.setStopAtShutdown(true);
-		server.setConnectors(connectors.toArray(new Connector[connectors.size()]));
-
-		// Get the execution path of this class
-		// We use this to set the WAR path.
-		ProtectionDomain protectionDomain = GitBlitServer.class.getProtectionDomain();
-		URL location = protectionDomain.getCodeSource().getLocation();
-
-		// Root WebApp Context
-		WebAppContext rootContext = new WebAppContext();
-		rootContext.setContextPath(settings.getString(Keys.server.contextPath, "/"));
-		rootContext.setServer(server);
-		rootContext.setWar(location.toExternalForm());
-		rootContext.setTempDirectory(tempDir);
-
-		// Set cookies HttpOnly so they are not accessible to JavaScript engines
-		HashSessionManager sessionManager = new HashSessionManager();
-		sessionManager.setHttpOnly(true);
-		// Use secure cookies if only serving https
-		sessionManager.setSecureCookies(params.port <= 0 && params.securePort > 0);
-		rootContext.getSessionHandler().setSessionManager(sessionManager);
-
-		// Ensure there is a defined User Service
-		String realmUsers = params.userService;
-		if (StringUtils.isEmpty(realmUsers)) {
-			logger.error(MessageFormat.format("PLEASE SPECIFY {0}!!", Keys.realm.userService));
-			return;
-		}
-
-		// Override settings from the command-line
-		settings.overrideSetting(Keys.realm.userService, params.userService);
-		settings.overrideSetting(Keys.git.repositoriesFolder, params.repositoriesFolder);
-		
-		// Start up an in-memory LDAP server, if configured
-		try {
-			if (StringUtils.isEmpty(params.ldapLdifFile) == false) {
-				File ldifFile = new File(params.ldapLdifFile);
-				if (ldifFile != null && ldifFile.exists()) {
-					URI ldapUrl = new URI(settings.getRequiredString(Keys.realm.ldap.server));
-					String firstLine = new Scanner(ldifFile).nextLine();
-					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);
-				}
-			}
-		} catch (Exception e) {
-			// Completely optional, just show a warning
-			logger.warn("Unable to start LDAP server", e);
-		}
-
-		// Set the server's contexts
-		server.setHandler(rootContext);
-
-		// Setup the GitBlit context
-		GitBlit gitblit = GitBlit.self();
-		gitblit.configureContext(settings, baseFolder, true);
-		rootContext.addEventListener(gitblit);
-
-		try {
-			// start the shutdown monitor
-			if (params.shutdownPort > 0) {
-				Thread shutdownMonitor = new ShutdownMonitorThread(server, params);
-				shutdownMonitor.start();
-			}
-
-			// start Jetty
-			server.start();
-			server.join();
-		} catch (Exception e) {
-			e.printStackTrace();
-			System.exit(100);
-		}
-	}
-
-	/**
-	 * Creates an http connector.
-	 * 
-	 * @param useNIO
-	 * @param port
-	 * @return an http connector
-	 */
-	private static Connector createConnector(boolean useNIO, int port) {
-		Connector connector;
-		if (useNIO) {
-			logger.info("Setting up NIO SelectChannelConnector on port " + port);
-			SelectChannelConnector nioconn = new SelectChannelConnector();
-			nioconn.setSoLingerTime(-1);
-			nioconn.setThreadPool(new QueuedThreadPool(20));
-			connector = nioconn;
-		} else {
-			logger.info("Setting up SocketConnector on port " + port);
-			SocketConnector sockconn = new SocketConnector();
-			connector = sockconn;
-		}
-
-		connector.setPort(port);
-		connector.setMaxIdleTime(30000);
-		return connector;
-	}
-
-	/**
-	 * 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
-	 * @param storePassword
-	 * @param caRevocationList
-	 * @param useNIO
-	 * @param port
-	 * @param requireClientCertificates
-	 * @return an https connector
-	 */
-	private static Connector createSSLConnector(String certAlias, File keyStore, File clientTrustStore,
-			String storePassword, File caRevocationList, boolean useNIO, int port, 
-			boolean requireClientCertificates) {
-		GitblitSslContextFactory factory = new GitblitSslContextFactory(certAlias,
-				keyStore, clientTrustStore, storePassword, caRevocationList);
-		SslConnector connector;
-		if (useNIO) {
-			logger.info("Setting up NIO SslSelectChannelConnector on port " + port);
-			SslSelectChannelConnector ssl = new SslSelectChannelConnector(factory);
-			ssl.setSoLingerTime(-1);
-			if (requireClientCertificates) {
-				factory.setNeedClientAuth(true);
-			} else {
-				factory.setWantClientAuth(true);
-			}
-			ssl.setThreadPool(new QueuedThreadPool(20));
-			connector = ssl;
-		} else {
-			logger.info("Setting up NIO SslSocketConnector on port " + port);
-			SslSocketConnector ssl = new SslSocketConnector(factory);
-			connector = ssl;
-		}
-		connector.setPort(port);
-		connector.setMaxIdleTime(30000);
-
-		return connector;
-	}
-	
-	/**
-	 * Creates an ajp connector.
-	 * 
-	 * @param port
-	 * @return an ajp connector
-	 */
-	private static Connector createAJPConnector(int port) {
-		logger.info("Setting up AJP Connector on port " + port);
-		Ajp13SocketConnector ajp = new Ajp13SocketConnector();
-		ajp.setPort(port);
-		if (port < 1024 && !isWindows()) {
-			logger.warn("Gitblit needs to run with ROOT permissions for ports < 1024!");
-		}
-		return ajp;
-	}
-
-	/**
-	 * Tests to see if the operating system is Windows.
-	 * 
-	 * @return true if this is a windows machine
-	 */
-	private static boolean isWindows() {
-		return System.getProperty("os.name").toLowerCase().indexOf("windows") > -1;
-	}
-
-	/**
-	 * 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 {
-
-		private final ServerSocket socket;
-
-		private final Server server;
-
-		private final Logger logger = LoggerFactory.getLogger(ShutdownMonitorThread.class);
-
-		public ShutdownMonitorThread(Server server, Params params) {
-			this.server = server;
-			setDaemon(true);
-			setName(Constants.NAME + " Shutdown Monitor");
-			ServerSocket skt = null;
-			try {
-				skt = new ServerSocket(params.shutdownPort, 1, InetAddress.getByName("127.0.0.1"));
-			} catch (Exception e) {
-				logger.warn("Could not open shutdown monitor on port " + params.shutdownPort, e);
-			}
-			socket = skt;
-		}
-
-		@Override
-		public void run() {
-			logger.info("Shutdown Monitor listening on port " + socket.getLocalPort());
-			Socket accept;
-			try {
-				accept = socket.accept();
-				BufferedReader reader = new BufferedReader(new InputStreamReader(
-						accept.getInputStream()));
-				reader.readLine();
-				logger.info(Constants.BORDER);
-				logger.info("Stopping " + Constants.NAME);
-				logger.info(Constants.BORDER);
-				server.stop();
-				server.setStopAtShutdown(false);
-				accept.close();
-				socket.close();
-			} catch (Exception e) {
-				logger.warn("Failed to shutdown Jetty", e);
-			}
-		}
-	}
-
-	/**
-	 * JCommander Parameters class for GitBlitServer.
-	 */
-	@Parameters(separators = " ")
-	private static class Params {
-
-		public static String baseFolder;
-
-		private final FileSettings FILESETTINGS = new FileSettings(new File(baseFolder, Constants.PROPERTIES_FILE).getAbsolutePath());
-
-		/*
-		 * Server parameters
-		 */
-		@Parameter(names = { "-h", "--help" }, description = "Show this help")
-		public Boolean help = false;
-
-		@Parameter(names = { "--stop" }, description = "Stop Server")
-		public Boolean stop = false;
-
-		@Parameter(names = { "--tempFolder" }, description = "Folder for server to extract built-in webapp")
-		public String temp = FILESETTINGS.getString(Keys.server.tempFolder, "temp");
-
-		/*
-		 * GIT Servlet Parameters
-		 */
-		@Parameter(names = { "--repositoriesFolder" }, description = "Git Repositories Folder")
-		public String repositoriesFolder = FILESETTINGS.getString(Keys.git.repositoriesFolder,
-				"git");
-
-		/*
-		 * Authentication Parameters
-		 */
-		@Parameter(names = { "--userService" }, description = "Authentication and Authorization Service (filename or fully qualified classname)")
-		public String userService = FILESETTINGS.getString(Keys.realm.userService,
-				"users.conf");
-
-		/*
-		 * JETTY Parameters
-		 */
-		@Parameter(names = { "--useNio" }, description = "Use NIO Connector else use Socket Connector.")
-		public Boolean useNIO = FILESETTINGS.getBoolean(Keys.server.useNio, true);
-
-		@Parameter(names = "--httpPort", description = "HTTP port for to serve. (port <= 0 will disable this connector)")
-		public Integer port = FILESETTINGS.getInteger(Keys.server.httpPort, 0);
-
-		@Parameter(names = "--httpsPort", description = "HTTPS port to serve.  (port <= 0 will disable this connector)")
-		public Integer securePort = FILESETTINGS.getInteger(Keys.server.httpsPort, 8443);
-
-		@Parameter(names = "--ajpPort", description = "AJP port to serve.  (port <= 0 will disable this connector)")
-		public Integer ajpPort = FILESETTINGS.getInteger(Keys.server.ajpPort, 0);
-
-		@Parameter(names = "--alias", description = "Alias of SSL certificate in keystore for serving https.")
-		public String alias = FILESETTINGS.getString(Keys.server.certificateAlias, "");
-
-		@Parameter(names = "--storePassword", description = "Password for SSL (https) keystore.")
-		public String storePassword = FILESETTINGS.getString(Keys.server.storePassword, "");
-
-		@Parameter(names = "--shutdownPort", description = "Port for Shutdown Monitor to listen on. (port <= 0 will disable this monitor)")
-		public Integer shutdownPort = FILESETTINGS.getInteger(Keys.server.shutdownPort, 8081);
-
-		@Parameter(names = "--requireClientCertificates", description = "Require client X509 certificates for https connections.")
-		public Boolean requireClientCertificates = FILESETTINGS.getBoolean(Keys.server.requireClientCertificates, false);
-
-		/*
-		 * Setting overrides
-		 */
-		@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;
-
-	}
-}
\ No newline at end of file
diff --git a/src/com/gitblit/GitFilter.java b/src/com/gitblit/GitFilter.java
deleted file mode 100644
index a0d395b..0000000
--- a/src/com/gitblit/GitFilter.java
+++ /dev/null
@@ -1,253 +0,0 @@
-/*
- * Copyright 2011 gitblit.com.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.gitblit;
-
-import java.text.MessageFormat;
-
-import com.gitblit.Constants.AccessRestrictionType;
-import com.gitblit.Constants.AuthorizationControl;
-import com.gitblit.models.RepositoryModel;
-import com.gitblit.models.UserModel;
-import com.gitblit.utils.StringUtils;
-
-/**
- * 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 {
-
-	protected static final String gitReceivePack = "/git-receive-pack";
-
-	protected static final String gitUploadPack = "/git-upload-pack";
-
-	protected static final String[] suffixes = { gitReceivePack, gitUploadPack, "/info/refs", "/HEAD",
-			"/objects" };
-
-	/**
-	 * Extract the repository name from the url.
-	 * 
-	 * @param url
-	 * @return repository name
-	 */
-	public static String getRepositoryName(String value) {
-		String repository = value;
-		// get the repository name from the url by finding a known url suffix
-		for (String urlSuffix : suffixes) {
-			if (repository.indexOf(urlSuffix) > -1) {
-				repository = repository.substring(0, repository.indexOf(urlSuffix));
-			}
-		}
-		return repository;
-	}
-
-	/**
-	 * Extract the repository name from the url.
-	 * 
-	 * @param url
-	 * @return repository name
-	 */
-	@Override
-	protected String extractRepositoryName(String url) {
-		return GitFilter.getRepositoryName(url);
-	}
-
-	/**
-	 * 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
-	 */
-	@Override
-	protected String getUrlRequestAction(String suffix) {
-		if (!StringUtils.isEmpty(suffix)) {
-			if (suffix.startsWith(gitReceivePack)) {
-				return gitReceivePack;
-			} else if (suffix.startsWith(gitUploadPack)) {
-				return gitUploadPack;
-			} else if (suffix.contains("?service=git-receive-pack")) {
-				return gitReceivePack;
-			} else if (suffix.contains("?service=git-upload-pack")) {
-				return gitUploadPack;
-			} else {
-				return gitUploadPack;
-			}
-		}
-		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
-	 */
-	@Override
-	protected boolean isActionAllowed(RepositoryModel repository, String action) {
-		if (!StringUtils.isEmpty(action)) {
-			if (action.equals(gitReceivePack)) {
-				// Push request
-				if (!repository.isBare) {
-					logger.warn("Gitblit does not allow pushes to repositories with a working copy");
-					return false;
-				}
-			}
-		}
-		return true;
-	}
-
-	@Override
-	protected boolean requiresClientCertificate() {
-		return GitBlit.getBoolean(Keys.git.requiresClientCertificate, false);
-	}
-
-	/**
-	 * Determine if the repository requires authentication.
-	 * 
-	 * @param repository
-	 * @param action
-	 * @return true if authentication required
-	 */
-	@Override
-	protected boolean requiresAuthentication(RepositoryModel repository, String action) {
-		if (gitUploadPack.equals(action)) {
-			// send to client
-			return repository.accessRestriction.atLeast(AccessRestrictionType.CLONE);	
-		} else if (gitReceivePack.equals(action)) {
-			// receive from client
-			return repository.accessRestriction.atLeast(AccessRestrictionType.PUSH);
-		}
-		return false;
-	}
-
-	/**
-	 * 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) {
-		if (!GitBlit.getBoolean(Keys.git.enableGitServlet, true)) {
-			// Git Servlet disabled
-			return false;
-		}		
-		if (action.equals(gitReceivePack)) {
-			// Push request
-			if (user.canPush(repository)) {
-				return true;
-			} else {
-				// user is unauthorized to push to this repository
-				logger.warn(MessageFormat.format("user {0} is not authorized to push to {1}",
-						user.username, repository));
-				return false;
-			}
-		} else if (action.equals(gitUploadPack)) {
-			// Clone request
-			if (user.canClone(repository)) {
-				return true;
-			} else {
-				// user is unauthorized to clone this repository
-				logger.warn(MessageFormat.format("user {0} is not authorized to clone {1}",
-						user.username, repository));
-				return false;
-			}
-		}
-		return true;
-	}
-	
-	/**
-	 * An authenticated user with the CREATE role can create a repository on
-	 * push.
-	 * 
-	 * @param user
-	 * @param repository
-	 * @param action
-	 * @return the repository model, if it is created, null otherwise
-	 */
-	@Override
-	protected RepositoryModel createRepository(UserModel user, String repository, String action) {
-		boolean isPush = !StringUtils.isEmpty(action) && gitReceivePack.equals(action);
-		if (isPush) {
-			if (user.canCreate(repository)) {
-				// user is pushing to a new repository
-				// validate name
-				if (repository.startsWith("../")) {
-					logger.error(MessageFormat.format("Illegal relative path in repository name! {0}", repository));
-					return null;
-				}
-				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);
-				if (c != null) {
-					logger.error(MessageFormat.format("Invalid character '{0}' in repository name {1}!", c, repository));
-					return null;
-				}
-
-				// create repository
-				RepositoryModel model = new RepositoryModel();
-				model.name = repository;
-				model.addOwner(user.username);
-				model.projectPath = StringUtils.getFirstPathElement(repository);
-				if (model.isUsersPersonalRepository(user.username)) {
-					// personal repository, default to private for user
-					model.authorizationControl = AuthorizationControl.NAMED;
-					model.accessRestriction = AccessRestrictionType.VIEW;
-				} else {
-					// common repository, user default server settings
-					model.authorizationControl = AuthorizationControl.fromName(GitBlit.getString(Keys.git.defaultAuthorizationControl, ""));
-					model.accessRestriction = AccessRestrictionType.fromName(GitBlit.getString(Keys.git.defaultAccessRestriction, ""));
-				}
-
-				// create the repository
-				try {
-					GitBlit.self().updateRepositoryModel(model.name, model, true);
-					logger.info(MessageFormat.format("{0} created {1} ON-PUSH", user.username, model.name));
-					return GitBlit.self().getRepositoryModel(model.name);
-				} catch (GitBlitException e) {
-					logger.error(MessageFormat.format("{0} failed to create repository {1} ON-PUSH!", user.username, model.name), e);
-				}
-			} else {
-				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;
-	}
-}
diff --git a/src/com/gitblit/GitServlet.java b/src/com/gitblit/GitServlet.java
deleted file mode 100644
index 77be963..0000000
--- a/src/com/gitblit/GitServlet.java
+++ /dev/null
@@ -1,405 +0,0 @@
-/*
- * Copyright 2011 gitblit.com.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.gitblit;
-
-import groovy.lang.Binding;
-import groovy.util.GroovyScriptEngine;
-
-import java.io.File;
-import java.io.IOException;
-import java.text.MessageFormat;
-import java.util.Collection;
-import java.util.Enumeration;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import javax.servlet.ServletConfig;
-import javax.servlet.ServletContext;
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-
-import org.eclipse.jgit.http.server.resolver.DefaultReceivePackFactory;
-import org.eclipse.jgit.http.server.resolver.DefaultUploadPackFactory;
-import org.eclipse.jgit.lib.PersonIdent;
-import org.eclipse.jgit.lib.Ref;
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.revwalk.RevCommit;
-import org.eclipse.jgit.transport.PostReceiveHook;
-import org.eclipse.jgit.transport.PreReceiveHook;
-import org.eclipse.jgit.transport.ReceiveCommand;
-import org.eclipse.jgit.transport.ReceiveCommand.Result;
-import org.eclipse.jgit.transport.ReceivePack;
-import org.eclipse.jgit.transport.RefFilter;
-import org.eclipse.jgit.transport.UploadPack;
-import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException;
-import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.gitblit.Constants.AccessRestrictionType;
-import com.gitblit.models.RepositoryModel;
-import com.gitblit.models.UserModel;
-import com.gitblit.utils.ClientLogger;
-import com.gitblit.utils.HttpUtils;
-import com.gitblit.utils.IssueUtils;
-import com.gitblit.utils.JGitUtils;
-import com.gitblit.utils.PushLogUtils;
-import com.gitblit.utils.StringUtils;
-
-/**
- * The GitServlet exists to force configuration of the JGit GitServlet based on
- * the Gitblit settings from either gitblit.properties or from context
- * parameters in the web.xml file.
- * 
- * It also implements and registers the Groovy hook mechanism.
- * 
- * Access to this servlet is protected by the GitFilter.
- * 
- * @author James Moger
- * 
- */
-public class GitServlet extends org.eclipse.jgit.http.server.GitServlet {
-
-	private static final long serialVersionUID = 1L;
-
-	private GroovyScriptEngine gse;
-
-	private File groovyDir;
-
-	@Override
-	public void init(ServletConfig config) throws ServletException {
-		groovyDir = GitBlit.getGroovyScriptsFolder();
-		try {
-			// set Grape root
-			File grapeRoot = GitBlit.getFileOrFolder(Keys.groovy.grapeFolder, "${baseFolder}/groovy/grape").getAbsoluteFile();
-			grapeRoot.mkdirs();
-			System.setProperty("grape.root", grapeRoot.getAbsolutePath());
-			
-			gse = new GroovyScriptEngine(groovyDir.getAbsolutePath());			
-		} catch (IOException e) {
-			throw new ServletException("Failed to instantiate Groovy Script Engine!", e);
-		}
-
-		// set the Gitblit receive hook
-		setReceivePackFactory(new DefaultReceivePackFactory() {
-			@Override
-			public ReceivePack create(HttpServletRequest req, Repository db)
-					throws ServiceNotEnabledException, ServiceNotAuthorizedException {
-				
-				// determine repository name from request
-				String repositoryName = req.getPathInfo().substring(1);
-				repositoryName = GitFilter.getRepositoryName(repositoryName);
-				
-				GitblitReceiveHook hook = new GitblitReceiveHook();
-				hook.repositoryName = repositoryName;
-				hook.gitblitUrl = HttpUtils.getGitblitURL(req);
-
-				ReceivePack rp = super.create(req, db);
-				rp.setPreReceiveHook(hook);
-				rp.setPostReceiveHook(hook);
-
-				// determine pushing user
-				PersonIdent person = rp.getRefLogIdent();
-				UserModel user = GitBlit.self().getUserModel(person.getName());
-				if (user == null) {
-					// anonymous push, create a temporary usermodel
-					user = new UserModel(person.getName());
-				}
-				
-				// enforce advanced ref permissions
-				RepositoryModel repository = GitBlit.self().getRepositoryModel(repositoryName);
-				rp.setAllowCreates(user.canCreateRef(repository));
-				rp.setAllowDeletes(user.canDeleteRef(repository));
-				rp.setAllowNonFastForwards(user.canRewindRef(repository));
-				
-				if (repository.isFrozen) {
-					throw new ServiceNotEnabledException();
-				}
-				
-				return rp;
-			}
-		});
-		
-		// override the default upload pack to exclude gitblit refs
-		setUploadPackFactory(new DefaultUploadPackFactory() {
-			@Override
-			public UploadPack create(final HttpServletRequest req, final Repository db)
-					throws ServiceNotEnabledException, ServiceNotAuthorizedException {
-				UploadPack up = super.create(req, db);
-				RefFilter refFilter = new RefFilter() {
-					@Override
-					public Map<String, Ref> filter(Map<String, Ref> refs) {
-						// admin accounts can access all refs 
-						UserModel user = GitBlit.self().authenticate(req);
-						if (user == null) {
-							user = UserModel.ANONYMOUS;
-						}
-						if (user.canAdmin()) {
-							return refs;
-						}
-
-						// normal users can not clone gitblit refs
-						refs.remove(IssueUtils.GB_ISSUES);
-						refs.remove(PushLogUtils.GB_PUSHES);
-						return refs;
-					}
-				};
-				up.setRefFilter(refFilter);
-				return up;
-			}
-		});
-		super.init(new GitblitServletConfig(config));
-	}
-
-	/**
-	 * Transitional wrapper class to configure the JGit 1.2 GitFilter. This
-	 * GitServlet will probably be replaced by a GitFilter so that Gitblit can
-	 * serve Git repositories on the root URL and not a /git sub-url.
-	 * 
-	 * @author James Moger
-	 * 
-	 */
-	private class GitblitServletConfig implements ServletConfig {
-		final ServletConfig config;
-
-		GitblitServletConfig(ServletConfig config) {
-			this.config = config;
-		}
-
-		@Override
-		public String getServletName() {
-			return config.getServletName();
-		}
-
-		@Override
-		public ServletContext getServletContext() {
-			return config.getServletContext();
-		}
-
-		@Override
-		public String getInitParameter(String name) {
-			if (name.equals("base-path")) {
-				return GitBlit.getRepositoriesFolder().getAbsolutePath();
-			} else if (name.equals("export-all")) {
-				return "1";
-			}
-			return config.getInitParameter(name);
-		}
-
-		@Override
-		public Enumeration<String> getInitParameterNames() {
-			return config.getInitParameterNames();
-		}
-	}
-
-	/**
-	 * The Gitblit receive hook allows for special processing on push events.
-	 * That might include rejecting writes to specific branches or executing a
-	 * script.
-	 * 
-	 * @author James Moger
-	 * 
-	 */
-	private class GitblitReceiveHook implements PreReceiveHook, PostReceiveHook {
-
-		protected final Logger logger = LoggerFactory.getLogger(GitblitReceiveHook.class);
-
-		protected String repositoryName;
-		
-		protected String gitblitUrl;
-
-		/**
-		 * Instrumentation point where the incoming push event has been parsed,
-		 * validated, objects created BUT refs have not been updated. You might
-		 * use this to enforce a branch-write permissions model.
-		 */
-		@Override
-		public void onPreReceive(ReceivePack rp, Collection<ReceiveCommand> commands) {
-			RepositoryModel repository = GitBlit.self().getRepositoryModel(repositoryName);
-			UserModel user = getUserModel(rp);
-			
-			if (repository.accessRestriction.atLeast(AccessRestrictionType.PUSH) && repository.verifyCommitter) {
-				if (StringUtils.isEmpty(user.emailAddress)) {
-					// emit warning if user does not have an email address 
-					logger.warn(MessageFormat.format("Consider setting an email address for {0} ({1}) to improve committer verification.", user.getDisplayName(), user.username));
-				}
-				
-				// Optionally enforce that the committer of the left parent chain
-				// match the account being used to push the commits.
-				// 
-				// This requires all merge commits are executed with the "--no-ff"
-				// option to force a merge commit even if fast-forward is possible.
-				// This ensures that the chain of left parents has the commit
-				// identity of the merging user.
-				for (ReceiveCommand cmd : commands) {
-					try {
-						List<RevCommit> commits = JGitUtils.getRevLog(rp.getRepository(), cmd.getOldId().name(), cmd.getNewId().name());
-						for (RevCommit commit : commits) {
-							PersonIdent committer = commit.getCommitterIdent();
-							if (!user.is(committer.getName(), committer.getEmailAddress())) {
-								String reason;
-								if (StringUtils.isEmpty(user.emailAddress)) {
-									// account does not have en email address
-									reason = MessageFormat.format("{0} by {1} <{2}> was not committed by {3} ({4})", commit.getId().name(), committer.getName(), StringUtils.isEmpty(committer.getEmailAddress()) ? "?":committer.getEmailAddress(), user.getDisplayName(), user.username);
-								} else {
-									// account has an email address
-									reason = MessageFormat.format("{0} by {1} <{2}> was not committed by {3} ({4}) <{5}>", commit.getId().name(), committer.getName(), StringUtils.isEmpty(committer.getEmailAddress()) ? "?":committer.getEmailAddress(), user.getDisplayName(), user.username, user.emailAddress);
-								}
-								cmd.setResult(Result.REJECTED_OTHER_REASON, reason);
-								break;
-							}
-						}
-					} catch (Exception e) {
-						logger.error("Failed to verify commits were made by pushing user", e);
-					}
-				}
-			}
-			
-			Set<String> scripts = new LinkedHashSet<String>();
-			scripts.addAll(GitBlit.self().getPreReceiveScriptsInherited(repository));
-			scripts.addAll(repository.preReceiveScripts);
-			runGroovy(repository, user, commands, rp, scripts);
-			for (ReceiveCommand cmd : commands) {
-				if (!Result.NOT_ATTEMPTED.equals(cmd.getResult())) {
-					logger.warn(MessageFormat.format("{0} {1} because \"{2}\"", cmd.getNewId()
-							.getName(), cmd.getResult(), cmd.getMessage()));
-				}
-			}
-		}
-
-		/**
-		 * Instrumentation point where the incoming push has been applied to the
-		 * repository. This is the point where we would trigger a Jenkins build
-		 * or send an email.
-		 */
-		@Override
-		public void onPostReceive(ReceivePack rp, Collection<ReceiveCommand> commands) {
-			if (commands.size() == 0) {
-				logger.info("skipping post-receive hooks, no refs created, updated, or removed");
-				return;
-			}
-
-			UserModel user = getUserModel(rp);
-			RepositoryModel repository = GitBlit.self().getRepositoryModel(repositoryName);
-
-			// log ref changes
-			for (ReceiveCommand cmd : commands) {
-				if (Result.OK.equals(cmd.getResult())) {
-					// add some logging for important ref changes
-					switch (cmd.getType()) {
-					case DELETE:
-						logger.info(MessageFormat.format("{0} DELETED {1} in {2} ({3})", user.username, cmd.getRefName(), repository.name, cmd.getOldId().name()));
-						break;
-					case CREATE:
-						logger.info(MessageFormat.format("{0} CREATED {1} in {2}", user.username, cmd.getRefName(), repository.name));
-						break;
-					case UPDATE_NONFASTFORWARD:
-						logger.info(MessageFormat.format("{0} UPDATED NON-FAST-FORWARD {1} in {2} (from {3} to {4})", user.username, cmd.getRefName(), repository.name, cmd.getOldId().name(), cmd.getNewId().name()));
-						break;
-					default:
-						break;
-					}
-				}
-			}
-
-			// update push log
-			try {
-				PushLogUtils.updatePushLog(user, rp.getRepository(), commands);
-				logger.info(MessageFormat.format("{0} push log updated", repository.name));
-			} catch (Exception e) {
-				logger.error(MessageFormat.format("Failed to update {0} pushlog", repository.name), e);
-			}
-			
-			// run Groovy hook scripts 
-			Set<String> scripts = new LinkedHashSet<String>();
-			scripts.addAll(GitBlit.self().getPostReceiveScriptsInherited(repository));
-			scripts.addAll(repository.postReceiveScripts);
-			runGroovy(repository, user, commands, rp, scripts);
-		}
-
-		/**
-		 * Returns the UserModel for the user pushing the changes.
-		 * 
-		 * @param rp
-		 * @return a UserModel
-		 */
-		protected UserModel getUserModel(ReceivePack rp) {
-			PersonIdent person = rp.getRefLogIdent();
-			UserModel user = GitBlit.self().getUserModel(person.getName());
-			if (user == null) {
-				// anonymous push, create a temporary usermodel
-				user = new UserModel(person.getName());
-				user.isAuthenticated = false;
-			}
-			return user;
-		}
-
-		/**
-		 * Runs the specified Groovy hook scripts.
-		 * 
-		 * @param repository
-		 * @param user
-		 * @param commands
-		 * @param scripts
-		 */
-		protected void runGroovy(RepositoryModel repository, UserModel user,
-				Collection<ReceiveCommand> commands, ReceivePack rp, Set<String> scripts) {
-			if (scripts == null || scripts.size() == 0) {
-				// no Groovy scripts to execute
-				return;
-			}
-
-			Binding binding = new Binding();
-			binding.setVariable("gitblit", GitBlit.self());
-			binding.setVariable("repository", repository);
-			binding.setVariable("receivePack", rp);
-			binding.setVariable("user", user);
-			binding.setVariable("commands", commands);
-			binding.setVariable("url", gitblitUrl);
-			binding.setVariable("logger", logger);
-			binding.setVariable("clientLogger", new ClientLogger(rp));
-			for (String script : scripts) {
-				if (StringUtils.isEmpty(script)) {
-					continue;
-				}
-				// allow script to be specified without .groovy extension
-				// this is easier to read in the settings
-				File file = new File(groovyDir, script);
-				if (!file.exists() && !script.toLowerCase().endsWith(".groovy")) {
-					file = new File(groovyDir, script + ".groovy");
-					if (file.exists()) {
-						script = file.getName();
-					}
-				}
-				try {
-					Object result = gse.run(script, binding);
-					if (result instanceof Boolean) {
-						if (!((Boolean) result)) {
-							logger.error(MessageFormat.format(
-									"Groovy script {0} has failed!  Hook scripts aborted.", script));
-							break;
-						}
-					}
-				} catch (Exception e) {
-					logger.error(
-							MessageFormat.format("Failed to execute Groovy script {0}", script), e);
-				}
-			}
-		}
-	}
-}
diff --git a/src/com/gitblit/GitblitUserService.java b/src/com/gitblit/GitblitUserService.java
deleted file mode 100644
index 37f22b0..0000000
--- a/src/com/gitblit/GitblitUserService.java
+++ /dev/null
@@ -1,338 +0,0 @@
-/*
- * Copyright 2011 gitblit.com.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.gitblit;
-
-import java.io.File;
-import java.io.IOException;
-import java.text.MessageFormat;
-import java.util.List;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.gitblit.Constants.AccountType;
-import com.gitblit.models.TeamModel;
-import com.gitblit.models.UserModel;
-import com.gitblit.utils.DeepCopier;
-import com.gitblit.utils.StringUtils;
-
-/**
- * This class wraps the default user service and is recommended as the starting
- * point for custom user service implementations.
- * 
- * This does seem a little convoluted, but the idea is to allow IUserService to
- * evolve with new methods and implementations without breaking custom
- * authentication implementations.
- * 
- * The most common implementation of a custom IUserService is to only override
- * authentication and then delegate all other functionality to one of Gitblit's
- * user services. This class optimizes that use-case.
- * 
- * Extending GitblitUserService allows for authentication customization without
- * having to keep-up-with IUSerService API changes.
- * 
- * @author James Moger
- * 
- */
-public class GitblitUserService implements IUserService {
-
-	protected IUserService serviceImpl;
-	
-	protected final String ExternalAccount = "#externalAccount";
-
-	private final Logger logger = LoggerFactory.getLogger(GitblitUserService.class);
-
-	public GitblitUserService() {
-	}
-
-	@Override
-	public void setup(IStoredSettings settings) {
-		File realmFile = GitBlit.getFileOrFolder(Keys.realm.userService, "${baseFolder}/users.conf");
-		serviceImpl = createUserService(realmFile);
-		logger.info("GUS delegating to " + serviceImpl.toString());
-	}
-
-	@SuppressWarnings("deprecation")
-	protected IUserService createUserService(File realmFile) {
-		IUserService service = null;
-		if (realmFile.getName().toLowerCase().endsWith(".properties")) {
-			// v0.5.0 - v0.7.0 properties-based realm file
-			service = new FileUserService(realmFile);
-		} else if (realmFile.getName().toLowerCase().endsWith(".conf")) {
-			// v0.8.0+ config-based realm file
-			service = new ConfigUserService(realmFile);
-		}
-
-		assert service != null;
-
-		if (!realmFile.exists()) {
-			// Create the Administrator account for a new realm file
-			try {
-				realmFile.createNewFile();
-			} catch (IOException x) {
-				logger.error(MessageFormat.format("COULD NOT CREATE REALM FILE {0}!", realmFile), x);
-			}
-			UserModel admin = new UserModel("admin");
-			admin.password = "admin";
-			admin.canAdmin = true;
-			admin.excludeFromFederation = true;
-			service.updateUserModel(admin);
-		}
-
-		if (service instanceof FileUserService) {
-			// automatically create a users.conf realm file from the original
-			// users.properties file
-			File usersConfig = new File(realmFile.getParentFile(), "users.conf");
-			if (!usersConfig.exists()) {
-				logger.info(MessageFormat.format("Automatically creating {0} based on {1}",
-						usersConfig.getAbsolutePath(), realmFile.getAbsolutePath()));
-				ConfigUserService configService = new ConfigUserService(usersConfig);
-				for (String username : service.getAllUsernames()) {
-					UserModel userModel = service.getUserModel(username);
-					configService.updateUserModel(userModel);
-				}
-			}
-			// issue suggestion about switching to users.conf
-			logger.warn("Please consider using \"users.conf\" instead of the deprecated \"users.properties\" file");
-		}
-		return service;
-	}
-	
-	@Override
-	public String toString() {
-		return getClass().getSimpleName();
-	}
-
-	@Override
-	public boolean supportsCredentialChanges() {
-		return serviceImpl.supportsCredentialChanges();
-	}
-
-	@Override
-	public boolean supportsDisplayNameChanges() {
-		return serviceImpl.supportsDisplayNameChanges();
-	}
-
-	@Override
-	public boolean supportsEmailAddressChanges() {
-		return serviceImpl.supportsEmailAddressChanges();
-	}
-
-	@Override
-	public boolean supportsTeamMembershipChanges() {
-		return serviceImpl.supportsTeamMembershipChanges();
-	}
-
-	@Override
-	public boolean supportsCookies() {
-		return serviceImpl.supportsCookies();
-	}
-
-	@Override
-	public String getCookie(UserModel model) {
-		return serviceImpl.getCookie(model);
-	}
-
-	@Override
-	public UserModel authenticate(char[] cookie) {
-		UserModel user = serviceImpl.authenticate(cookie);
-		setAccountType(user);
-		return user;
-	}
-
-	@Override
-	public UserModel authenticate(String username, char[] password) {
-		UserModel user = serviceImpl.authenticate(username, password);
-		setAccountType(user);
-		return user;
-	}
-	
-	@Override
-	public void logout(UserModel user) {
-		serviceImpl.logout(user);
-	}
-
-	@Override
-	public UserModel getUserModel(String username) {
-		UserModel user = serviceImpl.getUserModel(username);
-		setAccountType(user);
-		return user;
-	}
-
-	@Override
-	public boolean updateUserModel(UserModel model) {
-		return serviceImpl.updateUserModel(model);
-	}
-
-	@Override
-	public boolean updateUserModels(List<UserModel> models) {
-		return serviceImpl.updateUserModels(models);
-	}
-
-	@Override
-	public boolean updateUserModel(String username, UserModel model) {
-		if (model.isLocalAccount() || supportsCredentialChanges()) {
-			if (!model.isLocalAccount() && !supportsTeamMembershipChanges()) {
-				//  teams are externally controlled - copy from original model
-				UserModel existingModel = getUserModel(username);
-				
-				model = DeepCopier.copy(model);
-				model.teams.clear();
-				model.teams.addAll(existingModel.teams);
-			}
-			return serviceImpl.updateUserModel(username, model);
-		}
-		if (model.username.equals(username)) {
-			// passwords are not persisted by the backing user service
-			model.password = null;
-			if (!model.isLocalAccount() && !supportsTeamMembershipChanges()) {
-				//  teams are externally controlled- copy from original model
-				UserModel existingModel = getUserModel(username);
-				
-				model = DeepCopier.copy(model);
-				model.teams.clear();
-				model.teams.addAll(existingModel.teams);
-			}
-			return serviceImpl.updateUserModel(username, model);
-		}
-		logger.error("Users can not be renamed!");
-		return false;
-	}
-	@Override
-	public boolean deleteUserModel(UserModel model) {
-		return serviceImpl.deleteUserModel(model);
-	}
-
-	@Override
-	public boolean deleteUser(String username) {
-		return serviceImpl.deleteUser(username);
-	}
-
-	@Override
-	public List<String> getAllUsernames() {
-		return serviceImpl.getAllUsernames();
-	}
-
-	@Override
-	public List<UserModel> getAllUsers() {
-		List<UserModel> users = serviceImpl.getAllUsers();
-    	for (UserModel user : users) {
-    		setAccountType(user);
-    	}
-		return users; 
-	}
-
-	@Override
-	public List<String> getAllTeamNames() {
-		return serviceImpl.getAllTeamNames();
-	}
-
-	@Override
-	public List<TeamModel> getAllTeams() {
-		return serviceImpl.getAllTeams();
-	}
-
-	@Override
-	public List<String> getTeamnamesForRepositoryRole(String role) {
-		return serviceImpl.getTeamnamesForRepositoryRole(role);
-	}
-
-	@Override
-	@Deprecated
-	public boolean setTeamnamesForRepositoryRole(String role, List<String> teamnames) {
-		return serviceImpl.setTeamnamesForRepositoryRole(role, teamnames);
-	}
-
-	@Override
-	public TeamModel getTeamModel(String teamname) {
-		return serviceImpl.getTeamModel(teamname);
-	}
-
-	@Override
-	public boolean updateTeamModel(TeamModel model) {
-		return serviceImpl.updateTeamModel(model);
-	}
-
-	@Override
-	public boolean updateTeamModels(List<TeamModel> models) {
-		return serviceImpl.updateTeamModels(models);
-	}
-
-	@Override
-	public boolean updateTeamModel(String teamname, TeamModel model) {
-		if (!supportsTeamMembershipChanges()) {
-			// teams are externally controlled - copy from original model
-			TeamModel existingModel = getTeamModel(teamname);
-			
-			model = DeepCopier.copy(model);
-			model.users.clear();
-			model.users.addAll(existingModel.users);
-		}
-		return serviceImpl.updateTeamModel(teamname, model);
-	}
-
-	@Override
-	public boolean deleteTeamModel(TeamModel model) {
-		return serviceImpl.deleteTeamModel(model);
-	}
-
-	@Override
-	public boolean deleteTeam(String teamname) {
-		return serviceImpl.deleteTeam(teamname);
-	}
-
-	@Override
-	public List<String> getUsernamesForRepositoryRole(String role) {
-		return serviceImpl.getUsernamesForRepositoryRole(role);
-	}
-
-	@Override
-	@Deprecated
-	public boolean setUsernamesForRepositoryRole(String role, List<String> usernames) {
-		return serviceImpl.setUsernamesForRepositoryRole(role, usernames);
-	}
-
-	@Override
-	public boolean renameRepositoryRole(String oldRole, String newRole) {
-		return serviceImpl.renameRepositoryRole(oldRole, newRole);
-	}
-
-	@Override
-	public boolean deleteRepositoryRole(String role) {
-		return serviceImpl.deleteRepositoryRole(role);
-	}
-	
-	protected boolean isLocalAccount(String username) {
-		UserModel user = getUserModel(username);
-		return user != null && user.isLocalAccount();
-	}
-	
-	protected void setAccountType(UserModel user) {
-		if (user != null) {
-			if (!StringUtils.isEmpty(user.password)
-					&& !ExternalAccount.equalsIgnoreCase(user.password)
-					&& !"StoredInLDAP".equalsIgnoreCase(user.password)) {
-				user.accountType = AccountType.LOCAL;
-			} else {
-				user.accountType = getAccountType();
-			}
-		}
-	}
-	
-	protected AccountType getAccountType() {
-		return AccountType.LOCAL;
-	}
-}
diff --git a/src/com/gitblit/IStoredSettings.java b/src/com/gitblit/IStoredSettings.java
deleted file mode 100644
index 790f8b6..0000000
--- a/src/com/gitblit/IStoredSettings.java
+++ /dev/null
@@ -1,299 +0,0 @@
-/*
- * Copyright 2011 gitblit.com.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.gitblit;
-
-import java.util.ArrayList;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Properties;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.gitblit.utils.StringUtils;
-
-/**
- * Base class for stored settings implementations.
- * 
- * @author James Moger
- * 
- */
-public abstract class IStoredSettings {
-
-	protected final Logger logger;
-
-	protected final Properties overrides = new Properties();
-
-	public IStoredSettings(Class<? extends IStoredSettings> clazz) {
-		logger = LoggerFactory.getLogger(clazz);
-	}
-
-	protected abstract Properties read();
-
-	private Properties getSettings() {
-		Properties props = read();
-		props.putAll(overrides);
-		return props;
-	}
-
-	/**
-	 * 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
-	 */
-	public List<String> getAllKeys(String startingWith) {
-		List<String> keys = new ArrayList<String>();
-		Properties props = getSettings();
-		if (StringUtils.isEmpty(startingWith)) {
-			keys.addAll(props.stringPropertyNames());
-		} else {
-			startingWith = startingWith.toLowerCase();
-			for (Object o : props.keySet()) {
-				String key = o.toString();
-				if (key.toLowerCase().startsWith(startingWith)) {
-					keys.add(key);
-				}
-			}
-		}
-		return keys;
-	}
-
-	/**
-	 * 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
-	 */
-	public boolean getBoolean(String name, boolean defaultValue) {
-		Properties props = getSettings();
-		if (props.containsKey(name)) {
-			String value = props.getProperty(name);
-			if (!StringUtils.isEmpty(value)) {
-				return Boolean.parseBoolean(value.trim());
-			}
-		}
-		return defaultValue;
-	}
-
-	/**
-	 * 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
-	 */
-	public int getInteger(String name, int defaultValue) {
-		Properties props = getSettings();
-		if (props.containsKey(name)) {
-			try {
-				String value = props.getProperty(name);
-				if (!StringUtils.isEmpty(value)) {
-					return Integer.parseInt(value.trim());
-				}
-			} catch (NumberFormatException e) {
-				logger.warn("Failed to parse integer for " + name + " using default of "
-						+ defaultValue);
-			}
-		}
-		return defaultValue;
-	}
-
-	/**
-	 * 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
-	 */
-	public long getLong(String name, long defaultValue) {
-		Properties props = getSettings();
-		if (props.containsKey(name)) {
-			try {
-				String value = props.getProperty(name);
-				if (!StringUtils.isEmpty(value)) {
-					return Long.parseLong(value.trim());
-				}
-			} catch (NumberFormatException e) {
-				logger.warn("Failed to parse long for " + name + " using default of "
-						+ defaultValue);
-			}
-		}
-		return defaultValue;
-	}
-	
-	/**
-	 * Returns an int filesize from a string value such as 50m or 50mb
-	 * @param name
-	 * @param defaultValue
-	 * @return an int filesize or defaultValue if the key does not exist or can
-	 *         not be parsed
-	 */
-	public int getFilesize(String name, int defaultValue) {
-		String val = getString(name, null);
-		if (StringUtils.isEmpty(val)) {
-			return defaultValue;
-		}
-		return com.gitblit.utils.FileUtils.convertSizeToInt(val, defaultValue);
-	}
-	
-	/**
-	 * Returns an long filesize from a string value such as 50m or 50mb
-	 * @param name
-	 * @param defaultValue
-	 * @return a long filesize or defaultValue if the key does not exist or can
-	 *         not be parsed
-	 */
-	public long getFilesize(String key, long defaultValue) {
-		String val = getString(key, null);
-		if (StringUtils.isEmpty(val)) {
-			return defaultValue;
-		}
-		return com.gitblit.utils.FileUtils.convertSizeToLong(val, defaultValue);
-	}
-
-	/**
-	 * 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
-	 */
-	public char getChar(String name, char defaultValue) {
-		Properties props = getSettings();
-		if (props.containsKey(name)) {
-			String value = props.getProperty(name);
-			if (!StringUtils.isEmpty(value)) {
-				return value.trim().charAt(0);
-			}
-		}
-		return defaultValue;
-	}
-
-	/**
-	 * 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
-	 */
-	public String getString(String name, String defaultValue) {
-		Properties props = getSettings();
-		if (props.containsKey(name)) {
-			String value = props.getProperty(name);
-			if (value != null) {
-				return value.trim();
-			}
-		}
-		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
-	 */
-	public String getRequiredString(String name) {
-		Properties props = getSettings();
-		if (props.containsKey(name)) {
-			String value = props.getProperty(name);
-			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
-	 */
-	public List<String> getStrings(String name) {
-		return getStrings(name, " ");
-	}
-
-	/**
-	 * Returns a list of strings from the specified key using the specified
-	 * string separator.
-	 * 
-	 * @param name
-	 * @param separator
-	 * @return list of strings
-	 */
-	public List<String> getStrings(String name, String separator) {
-		List<String> strings = new ArrayList<String>();
-		Properties props = getSettings();
-		if (props.containsKey(name)) {
-			String value = props.getProperty(name);
-			strings = StringUtils.getStringsFromValue(value, separator);
-		}
-		return strings;
-	}
-	
-	/**
-	 * Returns a map of strings from the specified key.
-	 * 
-	 * @param name
-	 * @return map of string, string
-	 */
-	public Map<String, String> getMap(String name) {
-		Map<String, String> map = new LinkedHashMap<String, String>();
-		for (String string : getStrings(name)) {
-			String[] kvp = string.split("=", 2);
-			String key = kvp[0];
-			String value = kvp[1];				
-			map.put(key,  value);
-		}
-		return map;
-	}
-
-	/**
-	 * Override the specified key with the specified value.
-	 * 
-	 * @param key
-	 * @param value
-	 */
-	public void overrideSetting(String key, String value) {
-		overrides.put(key, value);
-	}
-
-	/**
-	 * Updates the values for the specified keys and persists the entire
-	 * configuration file.
-	 * 
-	 * @param map
-	 *            of key, value pairs
-	 * @return true if successful
-	 */
-	public abstract boolean saveSettings(Map<String, String> updatedSettings);
-}
\ No newline at end of file
diff --git a/src/com/gitblit/IUserService.java b/src/com/gitblit/IUserService.java
deleted file mode 100644
index 059d648..0000000
--- a/src/com/gitblit/IUserService.java
+++ /dev/null
@@ -1,324 +0,0 @@
-/*
- * Copyright 2011 gitblit.com.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.gitblit;
-
-import java.util.List;
-
-import com.gitblit.models.TeamModel;
-import com.gitblit.models.UserModel;
-
-/**
- * Implementations of IUserService control all aspects of UserModel objects and
- * user authentication.
- * 
- * @author James Moger
- * 
- */
-public interface IUserService {
-
-	/**
-	 * 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
-	 */
-	void setup(IStoredSettings settings);
-
-	/**
-	 * 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
-	 */
-	String getCookie(UserModel model);
-
-	/**
-	 * Authenticate a user based on their cookie.
-	 * 
-	 * @param cookie
-	 * @return a user object or null
-	 */
-	UserModel authenticate(char[] cookie);
-
-	/**
-	 * Authenticate a user based on a username and password.
-	 * 
-	 * @param username
-	 * @param password
-	 * @return a user object or null
-	 */
-	UserModel authenticate(String username, char[] password);
-
-	/**
-	 * 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
-	 */
-	UserModel getUserModel(String username);
-
-	/**
-	 * Updates/writes a complete user object.
-	 * 
-	 * @param model
-	 * @return true if update is successful
-	 */
-	boolean updateUserModel(UserModel model);
-
-	/**
-	 * 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(List<UserModel> models);
-	
-	/**
-	 * Adds/updates a user object keyed by username. This method allows for
-	 * renaming a user.
-	 * 
-	 * @param username
-	 *            the old username
-	 * @param model
-	 *            the user object to use for username
-	 * @return true if update is successful
-	 */
-	boolean updateUserModel(String username, UserModel model);
-
-	/**
-	 * Deletes the user object from the user service.
-	 * 
-	 * @param model
-	 * @return true if successful
-	 */
-	boolean deleteUserModel(UserModel model);
-
-	/**
-	 * Delete the user object with the specified username
-	 * 
-	 * @param username
-	 * @return true if successful
-	 */
-	boolean deleteUser(String username);
-
-	/**
-	 * 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
-	 */
-	List<UserModel> getAllUsers();
-
-	/**
-	 * 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
-	 * @return true if successful
-	 * @since 0.8.0
-	 */
-	@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(List<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
-	 *            the team object to use for teamname
-	 * @return true if update is successful
-	 * @since 0.8.0
-	 */
-	boolean updateTeamModel(String teamname, TeamModel model);
-
-	/**
-	 * Deletes the team object from the user service.
-	 * 
-	 * @param model
-	 * @return true if successful
-	 * @since 0.8.0
-	 */
-	boolean deleteTeamModel(TeamModel model);
-
-	/**
-	 * 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
-	 * @since 0.8.0
-	 */
-	List<String> getUsernamesForRepositoryRole(String role);
-
-	/**
-	 * 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
-	 * @return true if successful
-	 */
-	@Deprecated
-	boolean setUsernamesForRepositoryRole(String role, List<String> usernames);
-
-	/**
-	 * Renames a repository role.
-	 * 
-	 * @param oldRole
-	 * @param newRole
-	 * @return true if successful
-	 */
-	boolean renameRepositoryRole(String oldRole, String newRole);
-
-	/**
-	 * Removes a repository role from all users.
-	 * 
-	 * @param role
-	 * @return true if successful
-	 */
-	boolean deleteRepositoryRole(String role);
-
-	/**
-	 * @See java.lang.Object.toString();
-	 * @return string representation of the login service
-	 */
-	String toString();
-}
diff --git a/src/com/gitblit/JsonServlet.java b/src/com/gitblit/JsonServlet.java
deleted file mode 100644
index 3ad2b7d..0000000
--- a/src/com/gitblit/JsonServlet.java
+++ /dev/null
@@ -1,129 +0,0 @@
-/*
- * Copyright 2011 gitblit.com.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.gitblit;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.lang.reflect.Type;
-import java.text.MessageFormat;
-
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.gitblit.utils.JsonUtils;
-import com.gitblit.utils.StringUtils;
-
-/**
- * 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() {
-		super();
-		logger = LoggerFactory.getLogger(getClass());
-	}
-
-	/**
-	 * Processes an gson request.
-	 * 
-	 * @param request
-	 * @param response
-	 * @throws javax.servlet.ServletException
-	 * @throws java.io.IOException
-	 */
-	protected abstract void processRequest(HttpServletRequest request, HttpServletResponse response)
-			throws ServletException, IOException;
-
-	@Override
-	protected void doPost(HttpServletRequest request, HttpServletResponse response)
-			throws ServletException, java.io.IOException {
-		processRequest(request, response);
-	}
-
-	@Override
-	protected void doGet(HttpServletRequest request, HttpServletResponse response)
-			throws ServletException, IOException {
-		processRequest(request, response);
-	}
-
-	protected <X> X deserialize(HttpServletRequest request, HttpServletResponse response,
-			Class<X> clazz) throws IOException {
-		String json = readJson(request, response);
-		if (StringUtils.isEmpty(json)) {
-			return null;
-		}
-
-		X object = JsonUtils.fromJsonString(json.toString(), clazz);
-		return object;
-	}
-
-	protected <X> X deserialize(HttpServletRequest request, HttpServletResponse response, Type type)
-			throws IOException {
-		String json = readJson(request, response);
-		if (StringUtils.isEmpty(json)) {
-			return null;
-		}
-
-		X object = JsonUtils.fromJsonString(json.toString(), type);
-		return object;
-	}
-
-	private String readJson(HttpServletRequest request, HttpServletResponse response)
-			throws IOException {
-		BufferedReader reader = request.getReader();
-		StringBuilder json = new StringBuilder();
-		String line = null;
-		while ((line = reader.readLine()) != null) {
-			json.append(line);
-		}
-		reader.close();
-
-		if (json.length() == 0) {
-			logger.error(MessageFormat.format("Failed to receive json data from {0}",
-					request.getRemoteAddr()));
-			response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
-			return null;
-		}
-		return json.toString();
-	}
-
-	protected void serialize(HttpServletResponse response, Object o) throws IOException {
-		if (o != null) {
-			// Send JSON response
-			String json = JsonUtils.toJsonString(o);
-			response.setCharacterEncoding(Constants.ENCODING);
-			response.getWriter().append(json);
-		}
-	}
-}
diff --git a/src/com/gitblit/Launcher.java b/src/com/gitblit/Launcher.java
deleted file mode 100644
index a43331b..0000000
--- a/src/com/gitblit/Launcher.java
+++ /dev/null
@@ -1,155 +0,0 @@
-/*
- * Copyright 2011 gitblit.com.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.gitblit;
-
-import java.io.File;
-import java.io.FileFilter;
-import java.io.IOException;
-import java.lang.reflect.Method;
-import java.net.URL;
-import java.net.URLClassLoader;
-import java.security.ProtectionDomain;
-import java.text.MessageFormat;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-
-import com.gitblit.build.Build;
-
-/**
- * Launch helper class that adds all jars found in the local "lib" & "ext"
- * 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.
- * 
- * This class also downloads all runtime dependencies, if they are not found.
- * 
- * @author James Moger
- * 
- */
-public class Launcher {
-
-	public static final boolean DEBUG = false;
-
-	/**
-	 * Parameters of the method to add an URL to the System classes.
-	 */
-	private static final Class<?>[] PARAMETERS = new Class[] { URL.class };
-
-	public static void main(String[] args) {
-		if (DEBUG) {
-			System.out.println("jcp=" + System.getProperty("java.class.path"));
-			ProtectionDomain protectionDomain = Launcher.class.getProtectionDomain();
-			System.out.println("launcher="
-					+ protectionDomain.getCodeSource().getLocation().toExternalForm());
-		}
-
-		// download all runtime dependencies
-		Build.runtime();
-
-		// Load the JARs in the lib and ext folder
-		String[] folders = new String[] { "lib", "ext" };
-		List<File> jars = new ArrayList<File>();
-		for (String folder : folders) {
-			if (folder == null) {
-				continue;
-			}
-			File libFolder = new File(folder);
-			if (!libFolder.exists()) {
-				continue;
-			}
-			List<File> found = findJars(libFolder.getAbsoluteFile());
-			jars.addAll(found);
-		}
-		// 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);
-		Collections.reverse(jars);
-
-		if (jars.size() == 0) {
-			for (String folder : folders) {
-				File libFolder = new File(folder);
-				// this is a test of adding a comment
-				// more really interesting things
-				System.err.println("Failed to find any really cool JARs in " + libFolder.getPath());
-			}
-			System.exit(-1);
-		} else {
-			for (File jar : jars) {
-				try {
-					jar.canRead();
-					addJarFile(jar);
-				} catch (Throwable t) {
-					t.printStackTrace();
-				}
-			}
-		}
-
-		// Start Server
-		GitBlitServer.main(args);
-	}
-
-	public static List<File> findJars(File folder) {
-		List<File> jars = new ArrayList<File>();
-		if (folder.exists()) {
-			File[] libs = folder.listFiles(new FileFilter() {
-				@Override
-				public boolean accept(File file) {
-					return !file.isDirectory() && file.getName().toLowerCase().endsWith(".jar");
-				}
-			});
-			if (libs != null && libs.length > 0) {
-				jars.addAll(Arrays.asList(libs));
-				if (DEBUG) {
-					for (File jar : jars) {
-						System.out.println("found " + jar);
-					}
-				}
-			}
-		}
-
-		return jars;
-	}
-
-	/**
-	 * Adds a file to the classpath
-	 * 
-	 * @param f
-	 *            the file to be added
-	 * @throws IOException
-	 */
-	public static void addJarFile(File f) throws IOException {
-		if (f.getName().indexOf("-sources") > -1 || f.getName().indexOf("-javadoc") > -1) {
-			// don't add source or javadoc jars to runtime classpath
-			return;
-		}
-		URL u = f.toURI().toURL();
-		if (DEBUG) {
-			System.out.println("load=" + u.toExternalForm());
-		}
-		URLClassLoader sysloader = (URLClassLoader) ClassLoader.getSystemClassLoader();
-		Class<?> sysclass = URLClassLoader.class;
-		try {
-			Method method = sysclass.getDeclaredMethod("addURL", PARAMETERS);
-			method.setAccessible(true);
-			method.invoke(sysloader, new Object[] { u });
-		} catch (Throwable t) {
-			throw new IOException(MessageFormat.format(
-					"Error, could not add {0} to system classloader", f.getPath()), t);
-		}
-	}
-}
diff --git a/src/com/gitblit/LdapUserService.java b/src/com/gitblit/LdapUserService.java
deleted file mode 100644
index 595c658..0000000
--- a/src/com/gitblit/LdapUserService.java
+++ /dev/null
@@ -1,391 +0,0 @@
-/*
- * Copyright 2012 John Crygier
- * 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;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.security.GeneralSecurityException;
-import java.util.List;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.gitblit.Constants.AccountType;
-import com.gitblit.models.TeamModel;
-import com.gitblit.models.UserModel;
-import com.gitblit.utils.ArrayUtils;
-import com.gitblit.utils.StringUtils;
-import com.unboundid.ldap.sdk.Attribute;
-import com.unboundid.ldap.sdk.ExtendedResult;
-import com.unboundid.ldap.sdk.LDAPConnection;
-import com.unboundid.ldap.sdk.LDAPException;
-import com.unboundid.ldap.sdk.LDAPSearchException;
-import com.unboundid.ldap.sdk.ResultCode;
-import com.unboundid.ldap.sdk.SearchResult;
-import com.unboundid.ldap.sdk.SearchResultEntry;
-import com.unboundid.ldap.sdk.SearchScope;
-import com.unboundid.ldap.sdk.extensions.StartTLSExtendedRequest;
-import com.unboundid.util.ssl.SSLUtil;
-import com.unboundid.util.ssl.TrustAllTrustManager;
-
-/**
- * Implementation of an LDAP user service.
- * 
- * @author John Crygier
- */
-public class LdapUserService extends GitblitUserService {
-
-	public static final Logger logger = LoggerFactory.getLogger(LdapUserService.class);
-
-	private IStoredSettings settings;
-	
-	public LdapUserService() {
-		super();
-	}
-
-	@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());
-	}
-	
-	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;
-				
-				SSLUtil sslUtil = new SSLUtil(new TrustAllTrustManager()); 
-				return new LDAPConnection(sslUtil.createSSLSocketFactory(), ldapUrl.getHost(), ldapPort, bindUserName, bindPassword);
-			} else {
-				if (ldapPort == -1)	// Default Port
-					ldapPort = 389;
-				
-				LDAPConnection conn = new LDAPConnection(ldapUrl.getHost(), ldapPort, bindUserName, bindPassword);
-
-				if (ldapUrl.getScheme().equalsIgnoreCase("ldap+tls")) {
-					SSLUtil sslUtil = new SSLUtil(new TrustAllTrustManager());
-
-					ExtendedResult extendedResult = conn.processExtendedOperation(
-						new StartTLSExtendedRequest(sslUtil.createSSLContext()));
-
-					if (extendedResult.getResultCode() != ResultCode.SUCCESS) {
-						throw new LDAPException(extendedResult.getResultCode());
-					}
-				}
-				return conn;
-			}
-		} catch (URISyntaxException e) {
-			logger.error("Bad LDAP URL, should be in the form: ldap(s|+tls)://<server>:<port>", e);
-		} catch (GeneralSecurityException e) {
-			logger.error("Unable to create SSL Connection", e);
-		} 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.
-	 *
-	 * @return false
-	 * @since 1.0.0
-	 */
-	@Override
-	public boolean supportsCredentialChanges() {
-		return false;
-	}
-	
-	/**
-	 * If no displayName pattern is defined then Gitblit can manage the display name.
-	 *
-	 * @return true if Gitblit can manage the user display name
-	 * @since 1.0.0
-	 */
-	@Override
-	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.
-	 *
-	 * @return true if Gitblit can manage the user email address
-	 * @since 1.0.0
-	 */
-	@Override
-	public boolean supportsEmailAddressChanges() {
-		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
-	 */	
-	public boolean supportsTeamMembershipChanges() {
-		return !settings.getBoolean(Keys.realm.ldap.maintainTeams, false);
-	}
-	
-	@Override
-	protected AccountType getAccountType() {
-		 return AccountType.LDAP;
-	}
-
-	@Override
-	public UserModel authenticate(String username, char[] password) {
-		if (isLocalAccount(username)) {
-			// local account, bypass LDAP authentication
-			return super.authenticate(username, password);
-		}
-		
-		String simpleUsername = getSimpleUsername(username);
-		
-		LDAPConnection ldapConnection = getLdapConnection();
-		if (ldapConnection != null) {
-			try {
-				// Find the logging in user's DN
-				String accountBase = settings.getString(Keys.realm.ldap.accountBase, "");
-				String accountPattern = settings.getString(Keys.realm.ldap.accountPattern, "(&(objectClass=person)(sAMAccountName=${username}))");
-				accountPattern = StringUtils.replace(accountPattern, "${username}", escapeLDAPSearchFilter(simpleUsername));
-
-				SearchResult result = doSearch(ldapConnection, accountBase, accountPattern);
-				if (result != null && result.getEntryCount() == 1) {
-					SearchResultEntry loggingInUser = result.getSearchEntries().get(0);
-					String loggingInUserDN = loggingInUser.getDN();
-
-					if (isAuthenticated(ldapConnection, loggingInUserDN, new String(password))) {
-						logger.debug("LDAP authenticated: " + username);
-
-						UserModel user = getUserModel(simpleUsername);
-						if (user == null)	// create user object for new authenticated user
-							user = new UserModel(simpleUsername);
-
-						// create a user cookie
-						if (StringUtils.isEmpty(user.cookie) && !ArrayUtils.isEmpty(password)) {
-							user.cookie = StringUtils.getSHA1(user.username + new String(password));
-						}
-
-						if (!supportsTeamMembershipChanges())
-							getTeamsFromLdap(ldapConnection, simpleUsername, loggingInUser, user);
-
-						// Get User Attributes
-						setUserAttributes(user, loggingInUser);
-
-						// Push the ldap looked up values to backing file
-						super.updateUserModel(user);
-						if (!supportsTeamMembershipChanges()) {
-							for (TeamModel userTeam : user.teams)
-								updateTeamModel(userTeam);
-						}
-
-						return user;
-					}
-				}
-			} finally {
-				ldapConnection.close();
-			}
-		}
-		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) {
-		if (!supportsTeamMembershipChanges()) {
-			List<String> admins = settings.getStrings(Keys.realm.ldap.admins);
-			// if we have defined administrative teams, then set admin flag
-			// otherwise leave admin flag unchanged
-			if (!ArrayUtils.isEmpty(admins)) {
-				user.canAdmin = false;
-				for (String admin : admins) {
-					if (admin.startsWith("@")) { // Team
-						if (user.getTeam(admin.substring(1)) != null)
-							user.canAdmin = true;
-					} else
-						if (user.getName().equalsIgnoreCase(admin))
-							user.canAdmin = true;
-				}
-			}
-		}
-	}
-	
-	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 = ExternalAccount;
-		user.accountType = getAccountType();
-		
-		// Get full name Attribute
-		String displayName = settings.getString(Keys.realm.ldap.displayName, "");		
-		if (!StringUtils.isEmpty(displayName)) {
-			// Replace embedded ${} with attributes
-			if (displayName.contains("${")) {
-				for (Attribute userAttribute : userEntry.getAttributes())
-					displayName = StringUtils.replace(displayName, "${" + userAttribute.getName() + "}", userAttribute.getValue());
-
-				user.displayName = displayName;
-			} else {
-				Attribute attribute = userEntry.getAttribute(displayName);
-				if (attribute != null && attribute.hasValue()) {
-					user.displayName = attribute.getValue();
-				}
-			}
-		}
-		
-		// Get email address Attribute
-		String email = settings.getString(Keys.realm.ldap.email, "");
-		if (!StringUtils.isEmpty(email)) {
-			if (email.contains("${")) {
-				for (Attribute userAttribute : userEntry.getAttributes())
-					email = StringUtils.replace(email, "${" + userAttribute.getName() + "}", userAttribute.getValue());
-
-				user.emailAddress = email;
-			} else {
-				Attribute attribute = userEntry.getAttribute(email);
-				if (attribute != null && attribute.hasValue()) {
-					user.emailAddress = attribute.getValue();
-				}
-			}
-		}
-	}
-
-	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;		
-	}
-
-	private SearchResult doSearch(LDAPConnection ldapConnection, String base, String filter) {
-		try {
-			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
-			ldapConnection.bind(userDn, password);
-			return true;
-		} catch (LDAPException e) {
-			logger.error("Error authenticating user", e);
-			return false;
-		}
-	}
-
-	
-	/**
-	 * Returns a simple username without any domain prefixes.
-	 * 
-	 * @param username
-	 * @return a simple username
-	 */
-	protected String getSimpleUsername(String username) {
-		int lastSlash = username.lastIndexOf('\\');
-		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();
-		for (int i = 0; i < filter.length(); i++) {
-			char curChar = filter.charAt(i);
-			switch (curChar) {
-			case '\\':
-				sb.append("\\5c");
-				break;
-			case '*':
-				sb.append("\\2a");
-				break;
-			case '(':
-				sb.append("\\28");
-				break;
-			case ')':
-				sb.append("\\29");
-				break;
-			case '\u0000': 
-				sb.append("\\00"); 
-				break;
-			default:
-				sb.append(curChar);
-			}
-		}
-		return sb.toString();
-	}
-}
diff --git a/src/com/gitblit/MailExecutor.java b/src/com/gitblit/MailExecutor.java
deleted file mode 100644
index 9001e83..0000000
--- a/src/com/gitblit/MailExecutor.java
+++ /dev/null
@@ -1,238 +0,0 @@
-/*
- * Copyright 2011 gitblit.com.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.gitblit;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Date;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Properties;
-import java.util.Queue;
-import java.util.Set;
-import java.util.concurrent.ConcurrentLinkedQueue;
-import java.util.regex.Pattern;
-
-import javax.mail.Authenticator;
-import javax.mail.Message;
-import javax.mail.PasswordAuthentication;
-import javax.mail.Session;
-import javax.mail.Transport;
-import javax.mail.internet.InternetAddress;
-import javax.mail.internet.MimeMessage;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.gitblit.utils.StringUtils;
-
-/**
- * The mail executor handles sending email messages asynchronously from queue.
- * 
- * @author James Moger
- * 
- */
-public class MailExecutor implements Runnable {
-
-	private final Logger logger = LoggerFactory.getLogger(MailExecutor.class);
-
-	private final Queue<Message> queue = new ConcurrentLinkedQueue<Message>();
-
-	private final Session session;
-
-	private final IStoredSettings settings;
-
-	public MailExecutor(IStoredSettings settings) {
-		this.settings = settings;
-
-		final String mailUser = settings.getString(Keys.mail.username, null);
-		final String mailPassword = settings.getString(Keys.mail.password, null);
-		boolean authenticate = !StringUtils.isEmpty(mailUser) && !StringUtils.isEmpty(mailPassword);
-		String server = settings.getString(Keys.mail.server, "");
-		if (StringUtils.isEmpty(server)) {
-			session = null;
-			return;
-		}
-		int port = settings.getInteger(Keys.mail.port, 25);
-		boolean isGMail = false;
-		if (server.equals("smtp.gmail.com")) {
-			port = 465;
-			isGMail = true;
-		}
-
-		Properties props = new Properties();
-		props.setProperty("mail.smtp.host", server);
-		props.setProperty("mail.smtp.port", String.valueOf(port));
-		props.setProperty("mail.smtp.auth", String.valueOf(authenticate));
-		props.setProperty("mail.smtp.auths", String.valueOf(authenticate));
-
-		if (isGMail) {
-			props.setProperty("mail.smtp.starttls.enable", "true");
-			props.put("mail.smtp.socketFactory.port", String.valueOf(port));
-			props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
-			props.put("mail.smtp.socketFactory.fallback", "false");
-		}
-
-		if (!StringUtils.isEmpty(mailUser) && !StringUtils.isEmpty(mailPassword)) {
-			// SMTP requires authentication
-			session = Session.getInstance(props, new Authenticator() {
-				protected PasswordAuthentication getPasswordAuthentication() {
-					PasswordAuthentication passwordAuthentication = new PasswordAuthentication(
-							mailUser, mailPassword);
-					return passwordAuthentication;
-				}
-			});
-		} else {
-			// SMTP does not require authentication
-			session = Session.getInstance(props);
-		}
-	}
-
-	/**
-	 * Indicates if the mail executor can send emails.
-	 * 
-	 * @return true if the mail executor is ready to send emails
-	 */
-	public boolean isReady() {
-		return session != null;
-	}
-
-	/**
-	 * Creates a message for the administrators.
-	 * 
-	 * @returna message
-	 */
-	public Message createMessageForAdministrators() {
-		List<String> toAddresses = settings.getStrings(Keys.mail.adminAddresses);
-		if (toAddresses.size() == 0) {
-			logger.warn("Can not notify administrators because no email addresses are defined!");
-			return null;
-		}
-		return createMessage(toAddresses);
-	}
-
-	/**
-	 * Create a message.
-	 * 
-	 * @param toAddresses
-	 * @return a message
-	 */
-	public Message createMessage(String... toAddresses) {
-		return createMessage(Arrays.asList(toAddresses));
-	}
-
-	/**
-	 * Create a message.
-	 * 
-	 * @param toAddresses
-	 * @return a message
-	 */
-	public Message createMessage(List<String> toAddresses) {
-		MimeMessage message = new MimeMessage(session);
-		try {
-			String fromAddress = settings.getString(Keys.mail.fromAddress, null);
-			if (StringUtils.isEmpty(fromAddress)) {
-				fromAddress = "gitblit@gitblit.com";
-			}
-			InternetAddress from = new InternetAddress(fromAddress, "Gitblit");
-			message.setFrom(from);
-
-			// determine unique set of addresses
-			Set<String> uniques = new HashSet<String>();
-			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>();
-			for (String address : uniques) {
-				if (StringUtils.isEmpty(address)) {
-					continue;
-				}
-				if (validEmail.matcher(address).find()) {
-					try {
-						tos.add(new InternetAddress(address));
-					} catch (Throwable t) {
-					}
-				}
-			}			
-			message.setRecipients(Message.RecipientType.BCC,
-					tos.toArray(new InternetAddress[tos.size()]));
-			message.setSentDate(new Date());
-		} catch (Exception e) {
-			logger.error("Failed to properly create message", e);
-		}
-		return message;
-	}
-
-	/**
-	 * Returns the status of the mail queue.
-	 * 
-	 * @return true, if the queue is empty
-	 */
-	public boolean hasEmptyQueue() {
-		return queue.isEmpty();
-	}
-
-	/**
-	 * Queue's an email message to be sent.
-	 * 
-	 * @param message
-	 * @return true if the message was queued
-	 */
-	public boolean queue(Message message) {
-		if (!isReady()) {
-			return false;
-		}
-		try {
-			message.saveChanges();
-		} catch (Throwable t) {
-			logger.error("Failed to save changes to message!", t);
-		}
-		queue.add(message);
-		return true;
-	}
-
-	@Override
-	public void run() {
-		if (!queue.isEmpty()) {
-			if (session != null) {
-				// send message via mail server
-				List<Message> failures = new ArrayList<Message>();
-				Message message = null;
-				while ((message = queue.poll()) != null) {
-					try {
-						if (settings.getBoolean(Keys.mail.debug, false)) {
-							logger.info("send: " + StringUtils.trimString(message.getSubject(), 60));
-						}
-						Transport.send(message);
-					} catch (Throwable e) {
-						logger.error("Failed to send message", e);
-						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);
-	}
-}
diff --git a/src/com/gitblit/PagesFilter.java b/src/com/gitblit/PagesFilter.java
deleted file mode 100644
index f88624e..0000000
--- a/src/com/gitblit/PagesFilter.java
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * 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 org.eclipse.jgit.lib.Repository;
-
-import com.gitblit.Constants.AccessRestrictionType;
-import com.gitblit.models.RepositoryModel;
-import com.gitblit.models.UserModel;
-
-/**
- * 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) {		
-		// get the repository name from the url by finding a known url suffix
-		String repository = "";		
-		Repository r = null;
-		int offset = 0;
-		while (r == null) {
-			int slash = url.indexOf('/', offset);
-			if (slash == -1) {
-				repository = url;
-			} else {
-				repository = url.substring(0, slash);
-			}
-			r = GitBlit.self().getRepository(repository, false);
-			if (r == null) {
-				// try again
-				offset = slash + 1;	
-			} else {
-				// close the repo
-				r.close();
-			}			
-			if (repository.equals(url)) {
-				// either only repository in url or no repository found
-				break;
-			}
-		}
-		return repository;
-	}
-
-	/**
-	 * Analyze the url and returns the action of the request.
-	 * 
-	 * @param url
-	 * @return action of the request
-	 */
-	@Override
-	protected String getUrlRequestAction(String suffix) {
-		return "VIEW";
-	}
-
-	/**
-	 * Determine if a non-existing repository can be created using this filter.
-	 *  
-	 * @return true if the filter allows repository creation
-	 */
-	@Override
-	protected boolean isCreationAllowed() {
-		return false;
-	}
-
-	/**
-	 * Determine if the action may be executed on the repository.
-	 * 
-	 * @param repository
-	 * @param action
-	 * @return true if the action may be performed
-	 */
-	@Override
-	protected boolean isActionAllowed(RepositoryModel repository, String action) {
-		return true;
-	}
-	
-	/**
-	 * Determine if the repository requires authentication.
-	 * 
-	 * @param repository
-	 * @param action
-	 * @return true if authentication required
-	 */
-	@Override
-	protected boolean requiresAuthentication(RepositoryModel repository, String action) {
-		return repository.accessRestriction.atLeast(AccessRestrictionType.VIEW);
-	}
-
-	/**
-	 * 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) {		
-		return user.canView(repository);
-	}
-}
diff --git a/src/com/gitblit/PagesServlet.java b/src/com/gitblit/PagesServlet.java
deleted file mode 100644
index 91f25b7..0000000
--- a/src/com/gitblit/PagesServlet.java
+++ /dev/null
@@ -1,249 +0,0 @@
-/*
- * 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.IOException;
-import java.text.MessageFormat;
-import java.text.ParseException;
-
-import javax.servlet.ServletContext;
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.revwalk.RevCommit;
-import org.eclipse.jgit.revwalk.RevTree;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.gitblit.models.RefModel;
-import com.gitblit.utils.ArrayUtils;
-import com.gitblit.utils.JGitUtils;
-import com.gitblit.utils.MarkdownUtils;
-import com.gitblit.utils.StringUtils;
-
-/**
- * Serves the content of a gh-pages branch.
- * 
- * @author James Moger
- * 
- */
-public class PagesServlet extends HttpServlet {
-
-	private static final long serialVersionUID = 1L;
-
-	private transient Logger logger = LoggerFactory.getLogger(PagesServlet.class);
-
-	public PagesServlet() {
-		super();
-	}
-
-	/**
-	 * Returns an url to this servlet for the specified parameters.
-	 * 
-	 * @param baseURL
-	 * @param repository
-	 * @param path
-	 * @return an url
-	 */
-	public static String asLink(String baseURL, String repository, String path) {
-		if (baseURL.length() > 0 && baseURL.charAt(baseURL.length() - 1) == '/') {
-			baseURL = baseURL.substring(0, baseURL.length() - 1);
-		}
-		return baseURL + Constants.PAGES + repository + "/" + (path == null ? "" : ("/" + path));
-	}
-
-	/**
-	 * Retrieves the specified resource from the gh-pages branch of the
-	 * repository.
-	 * 
-	 * @param request
-	 * @param response
-	 * @throws javax.servlet.ServletException
-	 * @throws java.io.IOException
-	 */
-	private void processRequest(HttpServletRequest request, HttpServletResponse response)
-			throws ServletException, IOException {
-		String path = request.getPathInfo();
-		if (path.toLowerCase().endsWith(".git")) {
-			// forward to url with trailing /
-			// this is important for relative pages links
-			response.sendRedirect(request.getServletPath() + path + "/");
-			return;
-		}
-		if (path.charAt(0) == '/') {
-			// strip leading /
-			path = path.substring(1);
-		}
-
-		// determine repository and resource from url
-		String repository = "";
-		String resource = "";
-		Repository r = null;
-		int offset = 0;
-		while (r == null) {
-			int slash = path.indexOf('/', offset);
-			if (slash == -1) {
-				repository = path;
-			} else {
-				repository = path.substring(0, slash);
-			}
-			r = GitBlit.self().getRepository(repository, false);
-			offset = slash + 1;
-			if (offset > 0) {
-				resource = path.substring(offset);
-			}
-			if (repository.equals(path)) {
-				// either only repository in url or no repository found
-				break;
-			}
-		}
-
-		ServletContext context = request.getSession().getServletContext();
-
-		try {
-			if (r == null) {
-				// repository not found!
-				String mkd = MessageFormat.format(
-						"# Error\nSorry, no valid **repository** specified in this url: {0}!",
-						repository);
-				error(response, mkd);
-				return;
-			}
-
-			// retrieve the content from the repository
-			RefModel pages = JGitUtils.getPagesBranch(r);
-			RevCommit commit = JGitUtils.getCommit(r, pages.getObjectId().getName());
-
-			if (commit == null) {
-				// branch not found!
-				String mkd = MessageFormat.format(
-						"# Error\nSorry, the repository {0} does not have a **gh-pages** branch!",
-						repository);
-				error(response, mkd);
-				r.close();
-				return;
-			}
-			response.setDateHeader("Last-Modified", JGitUtils.getCommitDate(commit).getTime());
-
-			String [] encodings = GitBlit.getEncodings();
-
-			RevTree tree = commit.getTree();
-			byte[] content = null;
-			if (StringUtils.isEmpty(resource)) {
-				// find resource
-				String[] files = { "index.html", "index.htm", "index.mkd" };
-				for (String file : files) {
-					content = JGitUtils.getStringContent(r, tree, file, encodings)
-							.getBytes(Constants.ENCODING);
-					if (content != null) {
-						resource = file;
-						// assume text/html unless the servlet container
-						// overrides
-						response.setContentType("text/html; charset=" + Constants.ENCODING);
-						break;
-					}
-				}
-			} else {
-				// specific resource
-				try {
-					String contentType = context.getMimeType(resource);
-					if (contentType == null) {
-						contentType = "text/plain";
-					}
-					if (contentType.startsWith("text")) {
-						content = JGitUtils.getStringContent(r, tree, resource, encodings).getBytes(
-								Constants.ENCODING);
-					} else {
-						content = JGitUtils.getByteContent(r, tree, resource, false);
-					}
-					response.setContentType(contentType);
-				} catch (Exception e) {
-				}
-			}
-
-			// no content, try custom 404 page
-			if (ArrayUtils.isEmpty(content)) {
-				String custom404 = JGitUtils.getStringContent(r, tree, "404.html", encodings);
-				if (!StringUtils.isEmpty(custom404)) {
-					content = custom404.getBytes(Constants.ENCODING);
-				}
-
-				// still no content
-				if (ArrayUtils.isEmpty(content)) {
-					String str = MessageFormat.format(
-							"# Error\nSorry, the requested resource **{0}** was not found.",
-							resource);
-					content = MarkdownUtils.transformMarkdown(str).getBytes(Constants.ENCODING);
-				}
-
-				try {
-					// output the content
-					logger.warn("Pages 404: " + resource);
-					response.setStatus(HttpServletResponse.SC_NOT_FOUND);
-					response.getOutputStream().write(content);
-					response.flushBuffer();
-				} catch (Throwable t) {
-					logger.error("Failed to write page to client", t);
-				}
-				return;
-			}
-
-			// check to see if we should transform markdown files
-			for (String ext : GitBlit.getStrings(Keys.web.markdownExtensions)) {
-				if (resource.endsWith(ext)) {
-					String mkd = new String(content, Constants.ENCODING);
-					content = MarkdownUtils.transformMarkdown(mkd).getBytes(Constants.ENCODING);
-					break;
-				}
-			}
-
-			try {
-				// output the content
-				response.getOutputStream().write(content);
-				response.flushBuffer();
-			} catch (Throwable t) {
-				logger.error("Failed to write page to client", t);
-			}
-
-			// close the repository
-			r.close();
-		} catch (Throwable t) {
-			logger.error("Failed to write page to client", t);
-		}
-	}
-
-	private void error(HttpServletResponse response, String mkd) throws ServletException,
-			IOException, ParseException {
-		String content = MarkdownUtils.transformMarkdown(mkd);
-		response.setContentType("text/html; charset=" + Constants.ENCODING);
-		response.getWriter().write(content);
-	}
-
-	@Override
-	protected void doPost(HttpServletRequest request, HttpServletResponse response)
-			throws ServletException, IOException {
-		processRequest(request, response);
-	}
-
-	@Override
-	protected void doGet(HttpServletRequest request, HttpServletResponse response)
-			throws ServletException, IOException {
-		processRequest(request, response);
-	}
-}
diff --git a/src/com/gitblit/RedmineUserService.java b/src/com/gitblit/RedmineUserService.java
deleted file mode 100644
index 9d571e3..0000000
--- a/src/com/gitblit/RedmineUserService.java
+++ /dev/null
@@ -1,187 +0,0 @@
-package com.gitblit;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.net.HttpURLConnection;
-
-import org.apache.wicket.util.io.IOUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.gitblit.Constants.AccountType;
-import com.gitblit.models.UserModel;
-import com.gitblit.utils.ArrayUtils;
-import com.gitblit.utils.ConnectionUtils;
-import com.gitblit.utils.StringUtils;
-import com.google.gson.Gson;
-
-/**
- * Implementation of an Redmine user service.<br>
- * you can login to gitblit with Redmine user id and api key.
- */
-public class RedmineUserService extends GitblitUserService {
-
-    private final Logger logger = LoggerFactory.getLogger(RedmineUserService.class);
-
-    private IStoredSettings settings;
-
-    private String testingJson;
-
-    private class RedmineCurrent {
-        private class RedmineUser {
-            public String login;
-            public String firstname;
-            public String lastname;
-            public String mail;
-        }
-
-        public RedmineUser user;
-    }
-
-    public RedmineUserService() {
-        super();
-    }
-
-    @Override
-    public void setup(IStoredSettings settings) {
-        this.settings = settings;
-
-        String file = settings.getString(Keys.realm.redmine.backingUserService, "${baseFolder}/users.conf");
-        File realmFile = GitBlit.getFileOrFolder(file);
-
-        serviceImpl = createUserService(realmFile);
-        logger.info("Redmine User Service backed by " + serviceImpl.toString());
-    }
-
-    @Override
-    public boolean supportsCredentialChanges() {
-        return false;
-    }
-
-    @Override
-    public boolean supportsDisplayNameChanges() {
-        return false;
-    }
-
-    @Override
-    public boolean supportsEmailAddressChanges() {
-        return false;
-    }
-
-    @Override
-    public boolean supportsTeamMembershipChanges() {
-        return false;
-    }
-    
-	 @Override
-	protected AccountType getAccountType() {
-		return AccountType.REDMINE;
-	}
-
-    @Override
-    public UserModel authenticate(String username, char[] password) {
-		if (isLocalAccount(username)) {
-			// local account, bypass Redmine authentication
-			return super.authenticate(username, password);
-		}
-
-        String jsonString = null;
-        try {
-        	// first attempt by username/password
-        	jsonString = getCurrentUserAsJson(username, password);
-        } catch (Exception e1) {
-        	logger.warn("Failed to authenticate via username/password against Redmine");
-        	try {
-        		// second attempt is by apikey
-        		jsonString = getCurrentUserAsJson(null, password);
-        		username = null;
-        	} catch (Exception e2) {
-        		logger.error("Failed to authenticate via apikey against Redmine", e2);
-        		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);
-        } catch (Exception e) {
-        	logger.error("Failed to deserialize Redmine json response: " + jsonString, e);
-        	return null;
-        }
-
-        if (StringUtils.isEmpty(username)) {
-        	// if the username has been reset because of apikey authentication
-        	// then use the email address of the user. this is the original
-        	// behavior as contributed by github/mallowlabs
-        	username = current.user.mail;
-        }
-
-        UserModel user = getUserModel(username);
-        if (user == null)	// create user object for new authenticated user
-        	user = new UserModel(username.toLowerCase());
-
-        // create a user cookie
-        if (StringUtils.isEmpty(user.cookie) && !ArrayUtils.isEmpty(password)) {
-        	user.cookie = StringUtils.getSHA1(user.username + new String(password));
-        }
-
-        // update user attributes from Redmine
-        user.accountType = getAccountType();
-        user.displayName = current.user.firstname + " " + current.user.lastname;
-        user.emailAddress = current.user.mail;
-        user.password = ExternalAccount;
-        if (!StringUtils.isEmpty(current.user.login)) {
-        	// only admin users can get login name
-        	// evidently this is an undocumented behavior of Redmine
-        	user.canAdmin = true;
-        }
-
-        // TODO consider Redmine group mapping for team membership
-        // http://www.redmine.org/projects/redmine/wiki/Rest_Users
-
-        // push the changes to the backing user service
-        super.updateUserModel(user);
-
-        return user;
-    }
-
-    private String getCurrentUserAsJson(String username, char [] password) throws IOException {
-        if (testingJson != null) { // for testing
-            return testingJson;
-        }
-
-        String url = this.settings.getString(Keys.realm.redmine.url, "");
-        if (!url.endsWith("/")) {
-        	url.concat("/");
-        }
-        HttpURLConnection http;
-        if (username == null) {
-        	// apikey authentication
-        	String apiKey = String.valueOf(password);
-        	String apiUrl = url + "users/current.json?key=" + apiKey;
-        	http = (HttpURLConnection) ConnectionUtils.openConnection(apiUrl, null, null);
-        } else {
-        	// username/password BASIC authentication
-        	String apiUrl = url + "users/current.json";
-        	http = (HttpURLConnection) ConnectionUtils.openConnection(apiUrl, username, password);
-        }
-        http.setRequestMethod("GET");
-        http.connect();
-        InputStreamReader reader = new InputStreamReader(http.getInputStream());
-        return IOUtils.toString(reader);
-    }
-    
-    /**
-     * set json response. do NOT invoke from production code.
-     * @param json json
-     */
-    public void setTestingCurrentUserAsJson(String json) {
-        this.testingJson = json;
-    }
-}
diff --git a/src/com/gitblit/RpcServlet.java b/src/com/gitblit/RpcServlet.java
deleted file mode 100644
index f6368dd..0000000
--- a/src/com/gitblit/RpcServlet.java
+++ /dev/null
@@ -1,348 +0,0 @@
-/*
- * Copyright 2011 gitblit.com.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.gitblit;
-
-import java.io.IOException;
-import java.text.MessageFormat;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.eclipse.jgit.lib.Repository;
-
-import com.gitblit.Constants.RpcRequest;
-import com.gitblit.models.RegistrantAccessPermission;
-import com.gitblit.models.RefModel;
-import com.gitblit.models.RepositoryModel;
-import com.gitblit.models.ServerSettings;
-import com.gitblit.models.TeamModel;
-import com.gitblit.models.UserModel;
-import com.gitblit.utils.HttpUtils;
-import com.gitblit.utils.JGitUtils;
-import com.gitblit.utils.RpcUtils;
-
-/**
- * Handles remote procedure calls.
- * 
- * @author James Moger
- * 
- */
-public class RpcServlet extends JsonServlet {
-
-	private static final long serialVersionUID = 1L;
-
-	public static final int PROTOCOL_VERSION = 5;
-
-	public RpcServlet() {
-		super();
-	}
-
-	/**
-	 * Processes an rpc request.
-	 * 
-	 * @param request
-	 * @param response
-	 * @throws javax.servlet.ServletException
-	 * @throws java.io.IOException
-	 */
-	@Override
-	protected void processRequest(HttpServletRequest request, HttpServletResponse response)
-			throws ServletException, IOException {
-		RpcRequest reqType = RpcRequest.fromName(request.getParameter("req"));
-		String objectName = request.getParameter("name");
-		logger.info(MessageFormat.format("Rpc {0} request from {1}", reqType,
-				request.getRemoteAddr()));
-
-		UserModel user = (UserModel) request.getUserPrincipal();
-
-		boolean allowManagement = user != null && user.canAdmin()
-				&& GitBlit.getBoolean(Keys.web.enableRpcManagement, false);
-
-		boolean allowAdmin = user != null && user.canAdmin()
-				&& GitBlit.getBoolean(Keys.web.enableRpcAdministration, false);
-
-		Object result = null;
-		if (RpcRequest.GET_PROTOCOL.equals(reqType)) {
-			// Return the protocol version
-			result = PROTOCOL_VERSION;
-		} else if (RpcRequest.LIST_REPOSITORIES.equals(reqType)) {
-			// Determine the Gitblit clone url
-			String gitblitUrl = HttpUtils.getGitblitURL(request);
-			StringBuilder sb = new StringBuilder();
-			sb.append(gitblitUrl);
-			sb.append(Constants.GIT_PATH);
-			sb.append("{0}");
-			String cloneUrl = sb.toString();
-
-			// list repositories
-			List<RepositoryModel> list = GitBlit.self().getRepositoryModels(user);
-			Map<String, RepositoryModel> repositories = new HashMap<String, RepositoryModel>();
-			for (RepositoryModel model : list) {
-				String url = MessageFormat.format(cloneUrl, model.name);
-				repositories.put(url, model);
-			}
-			result = repositories;
-		} else if (RpcRequest.LIST_BRANCHES.equals(reqType)) {
-			// list all local branches in all repositories accessible to user
-			Map<String, List<String>> localBranches = new HashMap<String, List<String>>();
-			List<RepositoryModel> models = GitBlit.self().getRepositoryModels(user);
-			for (RepositoryModel model : models) {
-				if (!model.hasCommits) {
-					// skip empty repository
-					continue;
-				}
-				if (model.isCollectingGarbage) {
-					// skip garbage collecting repository
-					logger.warn(MessageFormat.format("Temporarily excluding {0} from RPC, busy collecting garbage", model.name));
-					continue;
-				}
-				// get local branches
-				Repository repository = GitBlit.self().getRepository(model.name);
-				List<RefModel> refs = JGitUtils.getLocalBranches(repository, false, -1);
-				if (model.showRemoteBranches) {
-					// add remote branches if repository displays them
-					refs.addAll(JGitUtils.getRemoteBranches(repository, false, -1));
-				}
-				if (refs.size() > 0) {
-					List<String> branches = new ArrayList<String>();
-					for (RefModel ref : refs) {
-						branches.add(ref.getName());
-					}
-					localBranches.put(model.name, branches);
-				}
-				repository.close();
-			}
-			result = localBranches;
-		} else if (RpcRequest.LIST_USERS.equals(reqType)) {
-			// list users
-			List<String> names = GitBlit.self().getAllUsernames();
-			List<UserModel> users = new ArrayList<UserModel>();
-			for (String name : names) {
-				users.add(GitBlit.self().getUserModel(name));
-			}
-			result = users;
-		} else if (RpcRequest.LIST_TEAMS.equals(reqType)) {
-			// list teams
-			List<String> names = GitBlit.self().getAllTeamnames();
-			List<TeamModel> teams = new ArrayList<TeamModel>();
-			for (String name : names) {
-				teams.add(GitBlit.self().getTeamModel(name));
-			}
-			result = teams;
-		} else if (RpcRequest.CREATE_REPOSITORY.equals(reqType)) {
-			// create repository
-			RepositoryModel model = deserialize(request, response, RepositoryModel.class);
-			try {
-				GitBlit.self().updateRepositoryModel(model.name, model, true);
-			} catch (GitBlitException e) {
-				response.setStatus(failureCode);
-			}
-		} else if (RpcRequest.EDIT_REPOSITORY.equals(reqType)) {
-			// edit repository
-			RepositoryModel model = deserialize(request, response, RepositoryModel.class);
-			// name specifies original repository name in event of rename
-			String repoName = objectName;
-			if (repoName == null) {
-				repoName = model.name;
-			}
-			try {
-				GitBlit.self().updateRepositoryModel(repoName, model, false);
-			} catch (GitBlitException e) {
-				response.setStatus(failureCode);
-			}
-		} else if (RpcRequest.DELETE_REPOSITORY.equals(reqType)) {
-			// delete repository
-			RepositoryModel model = deserialize(request, response, RepositoryModel.class);
-			GitBlit.self().deleteRepositoryModel(model);
-		} else if (RpcRequest.CREATE_USER.equals(reqType)) {
-			// create user
-			UserModel model = deserialize(request, response, UserModel.class);
-			try {
-				GitBlit.self().updateUserModel(model.username, model, true);
-			} catch (GitBlitException e) {
-				response.setStatus(failureCode);
-			}
-		} else if (RpcRequest.EDIT_USER.equals(reqType)) {
-			// edit user
-			UserModel model = deserialize(request, response, UserModel.class);
-			// name parameter specifies original user name in event of rename
-			String username = objectName;
-			if (username == null) {
-				username = model.username;
-			}
-			try {
-				GitBlit.self().updateUserModel(username, model, false);
-			} catch (GitBlitException e) {
-				response.setStatus(failureCode);
-			}
-		} else if (RpcRequest.DELETE_USER.equals(reqType)) {
-			// delete user
-			UserModel model = deserialize(request, response, UserModel.class);
-			if (!GitBlit.self().deleteUser(model.username)) {
-				response.setStatus(failureCode);
-			}
-		} else if (RpcRequest.CREATE_TEAM.equals(reqType)) {
-			// create team
-			TeamModel model = deserialize(request, response, TeamModel.class);
-			try {
-				GitBlit.self().updateTeamModel(model.name, model, true);
-			} catch (GitBlitException e) {
-				response.setStatus(failureCode);
-			}
-		} else if (RpcRequest.EDIT_TEAM.equals(reqType)) {
-			// edit team
-			TeamModel model = deserialize(request, response, TeamModel.class);
-			// name parameter specifies original team name in event of rename
-			String teamname = objectName;
-			if (teamname == null) {
-				teamname = model.name;
-			}
-			try {
-				GitBlit.self().updateTeamModel(teamname, model, false);
-			} catch (GitBlitException e) {
-				response.setStatus(failureCode);
-			}
-		} else if (RpcRequest.DELETE_TEAM.equals(reqType)) {
-			// delete team
-			TeamModel model = deserialize(request, response, TeamModel.class);
-			if (!GitBlit.self().deleteTeam(model.name)) {
-				response.setStatus(failureCode);
-			}
-		} else if (RpcRequest.LIST_REPOSITORY_MEMBERS.equals(reqType)) {
-			// get repository members
-			RepositoryModel model = GitBlit.self().getRepositoryModel(objectName);
-			result = GitBlit.self().getRepositoryUsers(model);
-		} else if (RpcRequest.SET_REPOSITORY_MEMBERS.equals(reqType)) {
-			// rejected since 1.2.0
-			response.setStatus(failureCode);
-		} else if (RpcRequest.LIST_REPOSITORY_MEMBER_PERMISSIONS.equals(reqType)) {
-			// get repository member permissions
-			RepositoryModel model = GitBlit.self().getRepositoryModel(objectName);
-			result = GitBlit.self().getUserAccessPermissions(model);
-		} else if (RpcRequest.SET_REPOSITORY_MEMBER_PERMISSIONS.equals(reqType)) {
-			// set the repository permissions for the specified users
-			RepositoryModel model = GitBlit.self().getRepositoryModel(objectName);
-			Collection<RegistrantAccessPermission> permissions = deserialize(request, response, RpcUtils.REGISTRANT_PERMISSIONS_TYPE);
-			result = GitBlit.self().setUserAccessPermissions(model, permissions);
-		} else if (RpcRequest.LIST_REPOSITORY_TEAMS.equals(reqType)) {
-			// get repository teams
-			RepositoryModel model = GitBlit.self().getRepositoryModel(objectName);
-			result = GitBlit.self().getRepositoryTeams(model);
-		} else if (RpcRequest.SET_REPOSITORY_TEAMS.equals(reqType)) {
-			// rejected since 1.2.0
-			response.setStatus(failureCode);
-		} else if (RpcRequest.LIST_REPOSITORY_TEAM_PERMISSIONS.equals(reqType)) {
-			// get repository team permissions
-			RepositoryModel model = GitBlit.self().getRepositoryModel(objectName);
-			result = GitBlit.self().getTeamAccessPermissions(model);
-		} else if (RpcRequest.SET_REPOSITORY_TEAM_PERMISSIONS.equals(reqType)) {
-			// set the repository permissions for the specified teams
-			RepositoryModel model = GitBlit.self().getRepositoryModel(objectName);
-			Collection<RegistrantAccessPermission> permissions = deserialize(request, response, RpcUtils.REGISTRANT_PERMISSIONS_TYPE);
-			result = GitBlit.self().setTeamAccessPermissions(model, permissions);
-		} else if (RpcRequest.LIST_FEDERATION_REGISTRATIONS.equals(reqType)) {
-			// return the list of federation registrations
-			if (allowAdmin) {
-				result = GitBlit.self().getFederationRegistrations();
-			} else {
-				response.sendError(notAllowedCode);
-			}
-		} else if (RpcRequest.LIST_FEDERATION_RESULTS.equals(reqType)) {
-			// return the list of federation result registrations
-			if (allowAdmin && GitBlit.canFederate()) {
-				result = GitBlit.self().getFederationResultRegistrations();
-			} else {
-				response.sendError(notAllowedCode);
-			}
-		} else if (RpcRequest.LIST_FEDERATION_PROPOSALS.equals(reqType)) {
-			// return the list of federation proposals
-			if (allowAdmin && GitBlit.canFederate()) {
-				result = GitBlit.self().getPendingFederationProposals();
-			} else {
-				response.sendError(notAllowedCode);
-			}
-		} else if (RpcRequest.LIST_FEDERATION_SETS.equals(reqType)) {
-			// return the list of federation sets
-			if (allowAdmin && GitBlit.canFederate()) {
-				String gitblitUrl = HttpUtils.getGitblitURL(request);
-				result = GitBlit.self().getFederationSets(gitblitUrl);
-			} else {
-				response.sendError(notAllowedCode);
-			}
-		} else if (RpcRequest.LIST_SETTINGS.equals(reqType)) {
-			// return the server's settings
-			ServerSettings settings = GitBlit.self().getSettingsModel();
-			if (allowAdmin) {
-				// return all settings
-				result = settings;
-			} else {
-				// anonymous users get a few settings to allow browser launching
-				List<String> keys = new ArrayList<String>();
-				keys.add(Keys.web.siteName);
-				keys.add(Keys.web.mountParameters);
-				keys.add(Keys.web.syndicationEntries);
-
-				if (allowManagement) {
-					// keys necessary for repository and/or user management
-					keys.add(Keys.realm.minPasswordLength);
-					keys.add(Keys.realm.passwordStorage);
-					keys.add(Keys.federation.sets);
-				}
-				// build the settings
-				ServerSettings managementSettings = new ServerSettings();
-				for (String key : keys) {
-					managementSettings.add(settings.get(key));
-				}
-				if (allowManagement) {
-					managementSettings.pushScripts = settings.pushScripts;
-				}
-				result = managementSettings;
-			}
-		} else if (RpcRequest.EDIT_SETTINGS.equals(reqType)) {
-			// update settings on the server
-			if (allowAdmin) {
-				Map<String, String> settings = deserialize(request, response,
-						RpcUtils.SETTINGS_TYPE);
-				GitBlit.self().updateSettings(settings);
-			} else {
-				response.sendError(notAllowedCode);
-			}
-		} else if (RpcRequest.LIST_STATUS.equals(reqType)) {
-			// return the server's status information
-			if (allowAdmin) {
-				result = GitBlit.self().getStatus();
-			} else {
-				response.sendError(notAllowedCode);
-			}
-		} else if (RpcRequest.CLEAR_REPOSITORY_CACHE.equals(reqType)) {
-			// clear the repository list cache
-			if (allowManagement) {
-				GitBlit.self().resetRepositoryListCache();
-			} else {
-				response.sendError(notAllowedCode);
-			}
-		}
-
-		// send the result of the request
-		serialize(response, result);
-	}
-}
diff --git a/src/com/gitblit/ServletRequestWrapper.java b/src/com/gitblit/ServletRequestWrapper.java
deleted file mode 100644
index d74a9ec..0000000
--- a/src/com/gitblit/ServletRequestWrapper.java
+++ /dev/null
@@ -1,400 +0,0 @@
-/*
- * Copyright 2011 gitblit.com.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.gitblit;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.UnsupportedEncodingException;
-import java.security.Principal;
-import java.util.Collection;
-import java.util.Enumeration;
-import java.util.Locale;
-import java.util.Map;
-
-import javax.servlet.AsyncContext;
-import javax.servlet.DispatcherType;
-import javax.servlet.RequestDispatcher;
-import javax.servlet.ServletContext;
-import javax.servlet.ServletException;
-import javax.servlet.ServletInputStream;
-import javax.servlet.ServletRequest;
-import javax.servlet.ServletResponse;
-import javax.servlet.http.Cookie;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import javax.servlet.http.HttpSession;
-import javax.servlet.http.Part;
-
-/**
- * ServletRequestWrapper is a pass-through/delegate wrapper class for a servlet
- * request. This class is used in conjunction with ServletFilters, such as the
- * AccessRestrictionFilter.
- * 
- * The original request is wrapped by instances of this class and this class is
- * set as the servlet request in the filter. This allows for specialized
- * implementations of request methods, like getUserPrincipal() with delegation
- * to the original request for any method not overridden.
- * 
- * This class, by itself, is not altogether interesting. Subclasses of this
- * class, however, are of interest.
- * 
- * @author James Moger
- * 
- */
-public abstract class ServletRequestWrapper implements HttpServletRequest {
-
-	protected final HttpServletRequest req;
-
-	public ServletRequestWrapper(HttpServletRequest req) {
-		this.req = req;
-	}
-
-	@Override
-	public Object getAttribute(String name) {
-		return req.getAttribute(name);
-	}
-
-	@Override
-	public Enumeration getAttributeNames() {
-		return req.getAttributeNames();
-	}
-
-	@Override
-	public String getCharacterEncoding() {
-		return req.getCharacterEncoding();
-	}
-
-	@Override
-	public void setCharacterEncoding(String env) throws UnsupportedEncodingException {
-		req.setCharacterEncoding(env);
-	}
-
-	@Override
-	public int getContentLength() {
-		return req.getContentLength();
-	}
-
-	@Override
-	public String getContentType() {
-		return req.getContentType();
-	}
-
-	@Override
-	public ServletInputStream getInputStream() throws IOException {
-		return req.getInputStream();
-	}
-
-	@Override
-	public String getParameter(String name) {
-		return req.getParameter(name);
-	}
-
-	@Override
-	public Enumeration getParameterNames() {
-		return req.getParameterNames();
-	}
-
-	@Override
-	public String[] getParameterValues(String name) {
-		return req.getParameterValues(name);
-	}
-
-	@Override
-	public Map getParameterMap() {
-		return req.getParameterMap();
-	}
-
-	@Override
-	public String getProtocol() {
-		return req.getProtocol();
-	}
-
-	@Override
-	public String getScheme() {
-		return req.getScheme();
-	}
-
-	@Override
-	public String getServerName() {
-		return req.getServerName();
-	}
-
-	@Override
-	public int getServerPort() {
-		return req.getServerPort();
-	}
-
-	@Override
-	public BufferedReader getReader() throws IOException {
-		return req.getReader();
-	}
-
-	@Override
-	public String getRemoteAddr() {
-		return req.getRemoteAddr();
-	}
-
-	@Override
-	public String getRemoteHost() {
-		return req.getRemoteHost();
-	}
-
-	@Override
-	public void setAttribute(String name, Object o) {
-		req.setAttribute(name, o);
-	}
-
-	@Override
-	public void removeAttribute(String name) {
-		req.removeAttribute(name);
-	}
-
-	@Override
-	public Locale getLocale() {
-		return req.getLocale();
-	}
-
-	@Override
-	public Enumeration getLocales() {
-		return req.getLocales();
-	}
-
-	@Override
-	public boolean isSecure() {
-		return req.isSecure();
-	}
-
-	@Override
-	public RequestDispatcher getRequestDispatcher(String path) {
-		return req.getRequestDispatcher(path);
-	}
-
-	@Override
-	@Deprecated
-	public String getRealPath(String path) {
-		return req.getRealPath(path);
-	}
-
-	@Override
-	public int getRemotePort() {
-		return req.getRemotePort();
-	}
-
-	@Override
-	public String getLocalName() {
-		return req.getLocalName();
-	}
-
-	@Override
-	public String getLocalAddr() {
-		return req.getLocalAddr();
-	}
-
-	@Override
-	public int getLocalPort() {
-		return req.getLocalPort();
-	}
-
-	@Override
-	public String getAuthType() {
-		return req.getAuthType();
-	}
-
-	@Override
-	public Cookie[] getCookies() {
-		return req.getCookies();
-	}
-
-	@Override
-	public long getDateHeader(String name) {
-		return req.getDateHeader(name);
-	}
-
-	@Override
-	public String getHeader(String name) {
-		return req.getHeader(name);
-	}
-
-	@Override
-	public Enumeration getHeaders(String name) {
-		return req.getHeaders(name);
-	}
-
-	@Override
-	public Enumeration getHeaderNames() {
-		return req.getHeaderNames();
-	}
-
-	@Override
-	public int getIntHeader(String name) {
-		return req.getIntHeader(name);
-	}
-
-	@Override
-	public String getMethod() {
-		return req.getMethod();
-	}
-
-	@Override
-	public String getPathInfo() {
-		return req.getPathInfo();
-	}
-
-	@Override
-	public String getPathTranslated() {
-		return req.getPathTranslated();
-	}
-
-	@Override
-	public String getContextPath() {
-		return req.getContextPath();
-	}
-
-	@Override
-	public String getQueryString() {
-		return req.getQueryString();
-	}
-
-	@Override
-	public String getRemoteUser() {
-		return req.getRemoteUser();
-	}
-
-	@Override
-	public boolean isUserInRole(String role) {
-		return req.isUserInRole(role);
-	}
-
-	@Override
-	public Principal getUserPrincipal() {
-		return req.getUserPrincipal();
-	}
-
-	@Override
-	public String getRequestedSessionId() {
-		return req.getRequestedSessionId();
-	}
-
-	@Override
-	public String getRequestURI() {
-		return req.getRequestURI();
-	}
-
-	@Override
-	public StringBuffer getRequestURL() {
-		return req.getRequestURL();
-	}
-
-	@Override
-	public String getServletPath() {
-		return req.getServletPath();
-	}
-
-	@Override
-	public HttpSession getSession(boolean create) {
-		return req.getSession(create);
-	}
-
-	@Override
-	public HttpSession getSession() {
-		return req.getSession();
-	}
-
-	@Override
-	public boolean isRequestedSessionIdValid() {
-		return req.isRequestedSessionIdValid();
-	}
-
-	@Override
-	public boolean isRequestedSessionIdFromCookie() {
-		return req.isRequestedSessionIdFromCookie();
-	}
-
-	@Override
-	public boolean isRequestedSessionIdFromURL() {
-		return req.isRequestedSessionIdFromURL();
-	}
-
-	@Override
-	@Deprecated
-	public boolean isRequestedSessionIdFromUrl() {
-		return req.isRequestedSessionIdFromUrl();
-	}
-	
-	/*
-	 * Servlet 3.0 Methods 
-	 */
-	
-	@Override
-	public boolean authenticate(HttpServletResponse response) throws IOException, ServletException {
-		return false;
-	}
-	
-	@Override
-	public void login(String username, String password) throws ServletException {
-	}
-
-	@Override
-	public void logout() throws ServletException {
-	}
-
-		
-	@Override
-	public Part getPart(String arg0) throws IOException, ServletException {
-		return req.getPart(arg0);
-	}
-
-	@Override
-	public Collection<Part> getParts() throws IOException, ServletException {
-		return req.getParts();
-	}
-
-	@Override
-	public AsyncContext getAsyncContext() {
-		return req.getAsyncContext();
-	}
-
-	@Override
-	public DispatcherType getDispatcherType() {
-		return req.getDispatcherType();
-	}
-
-	@Override
-	public ServletContext getServletContext() {
-		return req.getServletContext();
-	}
-
-	@Override
-	public boolean isAsyncStarted() {
-		return req.isAsyncStarted();
-	}
-
-	@Override
-	public boolean isAsyncSupported() {
-		return req.isAsyncStarted();
-	}
-
-	@Override
-	public AsyncContext startAsync() throws IllegalStateException {
-		return req.startAsync();
-	}
-
-	@Override
-	public AsyncContext startAsync(ServletRequest arg0, ServletResponse arg1)
-			throws IllegalStateException {
-		return req.startAsync(arg0, arg1);
-	}
-}
\ No newline at end of file
diff --git a/src/com/gitblit/authority/CertificatesTableModel.java b/src/com/gitblit/authority/CertificatesTableModel.java
deleted file mode 100644
index 44d80e3..0000000
--- a/src/com/gitblit/authority/CertificatesTableModel.java
+++ /dev/null
@@ -1,166 +0,0 @@
-/*
- * 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.authority;
-
-import java.math.BigInteger;
-import java.security.cert.X509Certificate;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.Date;
-
-import javax.swing.table.AbstractTableModel;
-
-import com.gitblit.client.Translation;
-import com.gitblit.utils.X509Utils.RevocationReason;
-
-/**
- * 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;
-
-		@Override
-		public String toString() {
-			return name().replace('_', ' ');
-		}
-	}
-
-	public CertificatesTableModel() {
-	}
-
-	@Override
-	public int getRowCount() {
-		return ucm == null || ucm.certs == null ? 0 : ucm.certs.size();
-	}
-
-	@Override
-	public int getColumnCount() {
-		return Columns.values().length;
-	}
-
-	@Override
-	public String getColumnName(int column) {
-		Columns col = Columns.values()[column];
-		switch (col) {
-		case SerialNumber:
-			return Translation.get("gb.serialNumber");
-		case Issued:
-			return Translation.get("gb.issued");
-		case Expires:
-			return Translation.get("gb.expires");
-		case Status:
-			return Translation.get("gb.status");
-		case Reason:
-			return Translation.get("gb.reason");
-		}
-		return "";
-	}
-
-	/**
-	 * Returns <code>Object.class</code> regardless of <code>columnIndex</code>.
-	 * 
-	 * @param columnIndex
-	 *            the column being queried
-	 * @return the Object.class
-	 */
-	public Class<?> getColumnClass(int columnIndex) {
-		Columns col = Columns.values()[columnIndex];
-		switch (col) {
-		case Status:
-			return CertificateStatus.class;
-		case Issued:
-			return Date.class;
-		case Expires:
-			return Date.class;
-		case SerialNumber:
-			return BigInteger.class;
-		default:
-			return String.class;
-		}
-	}
-
-	@Override
-	public boolean isCellEditable(int rowIndex, int columnIndex) {
-		Columns col = Columns.values()[columnIndex];
-		switch (col) {
-		default:
-			return false;
-		}
-	}
-
-	@Override
-	public Object getValueAt(int rowIndex, int columnIndex) {
-		X509Certificate cert = ucm.certs.get(rowIndex);
-		Columns col = Columns.values()[columnIndex];
-		switch (col) {
-		case Status:
-			return ucm.getStatus(cert);
-		case SerialNumber:
-			return cert.getSerialNumber();
-		case Issued:
-			return cert.getNotBefore();
-		case Expires:
-			return cert.getNotAfter();
-		case Reason:
-			if (ucm.getStatus(cert).equals(CertificateStatus.revoked)) {
-				RevocationReason r = ucm.getRevocationReason(cert.getSerialNumber());
-				return Translation.get("gb." + r.name());
-			}			
-		}
-		return null;
-	}
-
-	public X509Certificate get(int modelRow) {
-		return ucm.certs.get(modelRow);
-	}
-	
-	public void setUserCertificateModel(UserCertificateModel ucm) {
-		this.ucm = ucm;
-		Collections.sort(ucm.certs, new Comparator<X509Certificate>() {
-			@Override
-			public int compare(X509Certificate o1, X509Certificate o2) {
-				// sort by issue date in reverse chronological order
-				int result = o2.getNotBefore().compareTo(o1.getNotBefore());
-				if (result == 0) {
-					// same issue date, show expiring first
-					boolean r1 = CertificatesTableModel.this.ucm.isRevoked(o1.getSerialNumber());
-					boolean r2 = CertificatesTableModel.this.ucm.isRevoked(o2.getSerialNumber());
-					if ((r1 && r2) || (!r1 && !r2)) {
-						// both revoked or both not revoked
-						// chronlogical order by expiration dates
-						result = o1.getNotAfter().compareTo(o2.getNotAfter());
-					} else if (r1) {
-						// r1 is revoked, r2 first
-						return 1;
-					} else {
-						// r2 is revoked, r1 first
-						return -1;
-					}
-				}
-				return result;
-			}
-		});
-	}
-}
diff --git a/src/com/gitblit/authority/GitblitAuthority.java b/src/com/gitblit/authority/GitblitAuthority.java
deleted file mode 100644
index c3d8184..0000000
--- a/src/com/gitblit/authority/GitblitAuthority.java
+++ /dev/null
@@ -1,921 +0,0 @@
-/*
- * 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.authority;
-
-import java.awt.BorderLayout;
-import java.awt.Container;
-import java.awt.Desktop;
-import java.awt.Dimension;
-import java.awt.EventQueue;
-import java.awt.FlowLayout;
-import java.awt.GridLayout;
-import java.awt.Insets;
-import java.awt.Point;
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
-import java.awt.event.KeyAdapter;
-import java.awt.event.KeyEvent;
-import java.awt.event.WindowAdapter;
-import java.awt.event.WindowEvent;
-import java.io.BufferedInputStream;
-import java.io.BufferedWriter;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileWriter;
-import java.io.FilenameFilter;
-import java.io.IOException;
-import java.net.URI;
-import java.security.PrivateKey;
-import java.security.cert.CertificateFactory;
-import java.security.cert.X509Certificate;
-import java.text.MessageFormat;
-import java.util.ArrayList;
-import java.util.Calendar;
-import java.util.Collections;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import javax.activation.DataHandler;
-import javax.activation.FileDataSource;
-import javax.mail.Message;
-import javax.mail.Multipart;
-import javax.mail.internet.MimeBodyPart;
-import javax.mail.internet.MimeMultipart;
-import javax.swing.ImageIcon;
-import javax.swing.InputVerifier;
-import javax.swing.JButton;
-import javax.swing.JComponent;
-import javax.swing.JFrame;
-import javax.swing.JLabel;
-import javax.swing.JOptionPane;
-import javax.swing.JPanel;
-import javax.swing.JPasswordField;
-import javax.swing.JScrollPane;
-import javax.swing.JSplitPane;
-import javax.swing.JTable;
-import javax.swing.JTextArea;
-import javax.swing.JTextField;
-import javax.swing.JToolBar;
-import javax.swing.RowFilter;
-import javax.swing.SwingConstants;
-import javax.swing.UIManager;
-import javax.swing.event.ListSelectionEvent;
-import javax.swing.event.ListSelectionListener;
-import javax.swing.table.TableRowSorter;
-
-import org.eclipse.jgit.errors.ConfigInvalidException;
-import org.eclipse.jgit.lib.StoredConfig;
-import org.eclipse.jgit.storage.file.FileBasedConfig;
-import org.eclipse.jgit.util.FS;
-import org.slf4j.LoggerFactory;
-
-import com.gitblit.ConfigUserService;
-import com.gitblit.Constants;
-import com.gitblit.FileSettings;
-import com.gitblit.IStoredSettings;
-import com.gitblit.IUserService;
-import com.gitblit.Keys;
-import com.gitblit.MailExecutor;
-import com.gitblit.client.HeaderPanel;
-import com.gitblit.client.Translation;
-import com.gitblit.models.UserModel;
-import com.gitblit.utils.ArrayUtils;
-import com.gitblit.utils.FileUtils;
-import com.gitblit.utils.StringUtils;
-import com.gitblit.utils.TimeUtils;
-import com.gitblit.utils.X509Utils;
-import com.gitblit.utils.X509Utils.RevocationReason;
-import com.gitblit.utils.X509Utils.X509Log;
-import com.gitblit.utils.X509Utils.X509Metadata;
-
-/**
- * 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;
-
-	private JButton newSSLCertificate;
-
-	public static void main(String... args) {
-		// filter out the baseFolder parameter
-		String folder = "data";
-		for (int i = 0; i< args.length; i++) {
-			String arg = args[i];
-			if (arg.equals("--baseFolder")) {
-				if (i + 1 == args.length) {
-					System.out.println("Invalid --baseFolder parameter!");
-					System.exit(-1);
-				} else if (args[i + 1] != ".") {
-					folder = args[i+1];
-				}
-				break;
-			}
-		}
-		final String baseFolder = folder;
-		EventQueue.invokeLater(new Runnable() {
-			public void run() {
-				try {
-					UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
-				} catch (Exception e) {
-				}
-				GitblitAuthority authority = new GitblitAuthority();
-				authority.initialize(baseFolder);
-				authority.setLocationRelativeTo(null);
-				authority.setVisible(true);
-			}
-		});
-	}
-
-	public GitblitAuthority() {
-		super();
-		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.VERSION + " (" + Constants.VERSION_DATE + ")");
-		setContentPane(getUI());
-		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
-		addWindowListener(new WindowAdapter() {
-			@Override
-			public void windowClosing(WindowEvent event) {
-				saveSizeAndPosition();
-			}
-
-			@Override
-			public void windowOpened(WindowEvent event) {
-			}
-		});		
-
-		File folder = new File(baseFolder).getAbsoluteFile();
-		load(folder);
-		
-		setSizeAndPosition();
-	}
-	
-	private void setSizeAndPosition() {
-		String sz = null;
-		String pos = null;
-		try {
-			StoredConfig config = getConfig();
-			sz = config.getString("ui", null, "size");
-			pos = config.getString("ui", null, "position");
-			defaultDuration = config.getInt("new",  "duration", 365);
-		} catch (Throwable t) {
-			t.printStackTrace();
-		}
-
-		// try to restore saved window size
-		if (StringUtils.isEmpty(sz)) {
-			setSize(900, 600);
-		} else {
-			String[] chunks = sz.split("x");
-			int width = Integer.parseInt(chunks[0]);
-			int height = Integer.parseInt(chunks[1]);
-			setSize(width, height);
-		}
-
-		// try to restore saved window position
-		if (StringUtils.isEmpty(pos)) {
-			setLocationRelativeTo(null);
-		} else {
-			String[] chunks = pos.split(",");
-			int x = Integer.parseInt(chunks[0]);
-			int y = Integer.parseInt(chunks[1]);
-			setLocation(x, y);
-		}
-	}
-
-	private void saveSizeAndPosition() {
-		try {
-			// save window size and position
-			StoredConfig config = getConfig();
-			Dimension sz = GitblitAuthority.this.getSize();
-			config.setString("ui", null, "size",
-					MessageFormat.format("{0,number,0}x{1,number,0}", sz.width, sz.height));
-			Point pos = GitblitAuthority.this.getLocationOnScreen();
-			config.setString("ui", null, "position",
-					MessageFormat.format("{0,number,0},{1,number,0}", pos.x, pos.y));
-			config.save();
-		} catch (Throwable t) {
-			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()) {
-			return null;
-		}
-		gitblitSettings = new FileSettings(file.getAbsolutePath());
-		mail = new MailExecutor(gitblitSettings);
-		String us = gitblitSettings.getString(Keys.realm.userService, "${baseFolder}/users.conf");
-		String ext = us.substring(us.lastIndexOf(".") + 1).toLowerCase();
-		IUserService service = null;
-		if (!ext.equals("conf") && !ext.equals("properties")) {
-			if (us.equals("com.gitblit.LdapUserService")) {
-				us = gitblitSettings.getString(Keys.realm.ldap.backingUserService, "${baseFolder}/users.conf");		
-			} else if (us.equals("com.gitblit.LdapUserService")) {
-				us = gitblitSettings.getString(Keys.realm.redmine.backingUserService, "${baseFolder}/users.conf");
-			}
-		}
-
-		if (us.endsWith(".conf")) {
-			service = new ConfigUserService(FileUtils.resolveParameter(Constants.baseFolder$, folder, us));
-		} 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);
-		System.out.println(Constants.baseFolder$ + " set to " + folder);
-		if (userService == null) {
-			JOptionPane.showMessageDialog(this, MessageFormat.format("Sorry, {0} doesn't look like a Gitblit GO installation.", folder));
-		} else {
-			// build empty certificate model for all users
-			Map<String, UserCertificateModel> map = new HashMap<String, UserCertificateModel>();
-			for (String user : userService.getAllUsernames()) {
-				UserModel model = userService.getUserModel(user);
-				UserCertificateModel ucm = new UserCertificateModel(model);				
-				map.put(user, ucm);
-			}
-			File certificatesConfigFile = new File(folder, X509Utils.CA_CONFIG);
-			FileBasedConfig config = new FileBasedConfig(certificatesConfigFile, FS.detect());
-			if (certificatesConfigFile.exists()) {
-				try {
-					config.load();
-					// replace user certificate model with actual data
-					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);
-					}
-				} catch (IOException e) {
-					e.printStackTrace();
-				} catch (ConfigInvalidException e) {
-					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"),
-							Translation.get("gb.warning"), JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE);
-					if (res != JOptionPane.YES_OPTION) {
-						if (Desktop.isDesktopSupported()) {
-							if (Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) {
-								try {
-									Desktop.getDesktop().browse(URI.create("http://www.oracle.com/technetwork/java/javase/downloads/index.html"));
-								} catch (IOException e) {
-								}
-							}
-						}
-						System.exit(1);
-					}
-				}
-				
-				// show certificate defaults dialog 
-				certificateDefaultsButton.doClick();
-				
-				// create "localhost" ssl certificate
-				prepareX509Infrastructure();
-			}
-		}
-	}
-	
-	private boolean prepareX509Infrastructure() {
-		if (caKeystorePassword == null) {
-			JPasswordField pass = new JPasswordField(10);
-			pass.setText(caKeystorePassword);
-			pass.addAncestorListener(new RequestFocusListener());
-			JPanel panel = new JPanel(new BorderLayout());
-			panel.add(new JLabel(Translation.get("gb.enterKeystorePassword")), BorderLayout.NORTH);
-			panel.add(pass, BorderLayout.CENTER);
-			int result = JOptionPane.showConfirmDialog(GitblitAuthority.this, panel, Translation.get("gb.password"), JOptionPane.OK_CANCEL_OPTION);
-			if (result == JOptionPane.OK_OPTION) {
-				caKeystorePassword = new String(pass.getPassword());
-			} else {
-				return false;
-			}
-		}
-
-		X509Metadata metadata = new X509Metadata("localhost", caKeystorePassword);
-		setMetadataDefaults(metadata);
-		metadata.notAfter = new Date(System.currentTimeMillis() + 10*TimeUtils.ONEYEAR);
-		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);
-		if (!userFolder.exists()) {
-			return list;
-		}
-		File [] certs = userFolder.listFiles(new FilenameFilter() {
-			@Override
-			public boolean accept(File dir, String name) {
-				return name.toLowerCase().endsWith(".cer") || name.toLowerCase().endsWith(".crt");
-			}
-		});
-		try {
-			CertificateFactory factory = CertificateFactory.getInstance("X.509");
-			for (File cert : certs) {				
-				BufferedInputStream is = new BufferedInputStream(new FileInputStream(cert));
-				X509Certificate x509 = (X509Certificate) factory.generateCertificate(is);
-				is.close();
-				list.add(x509);
-			}
-		} catch (Exception e) {
-			Utils.showException(GitblitAuthority.this, e);
-		}
-		return list;
-	}
-	
-	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();
-			}
-
-			@Override
-			public Date getDefaultExpiration() {
-				Calendar c = Calendar.getInstance();
-				c.add(Calendar.DATE, defaultDuration);
-				c.set(Calendar.HOUR_OF_DAY, 0);
-				c.set(Calendar.MINUTE, 0);
-				c.set(Calendar.SECOND, 0);
-				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()) {
-					return false;
-				}
-
-				Date notAfter = metadata.notAfter;
-				setMetadataDefaults(metadata);
-				metadata.notAfter = notAfter;
-				
-				// set user's specified OID values
-				UserModel user = ucm.user;				
-				if (!StringUtils.isEmpty(user.organizationalUnit)) {
-					metadata.oids.put("OU", user.organizationalUnit);
-				}
-				if (!StringUtils.isEmpty(user.organization)) {
-					metadata.oids.put("O", user.organization);
-				}
-				if (!StringUtils.isEmpty(user.locality)) {
-					metadata.oids.put("L", user.locality);
-				}
-				if (!StringUtils.isEmpty(user.stateProvince)) {
-					metadata.oids.put("ST", user.stateProvince);
-				}
-				if (!StringUtils.isEmpty(user.countryCode)) {
-					metadata.oids.put("C", user.countryCode);
-				}
-
-				File caKeystoreFile = new File(folder, X509Utils.CA_KEY_STORE);
-				File zip = X509Utils.newClientBundle(metadata, caKeystoreFile, caKeystorePassword, GitblitAuthority.this);
-
-				// save latest expiration date
-				if (ucm.expires == null || metadata.notAfter.before(ucm.expires)) {
-					ucm.expires = metadata.notAfter;
-				}
-				
-				updateAuthorityConfig(ucm);
-				
-				// refresh user
-				ucm.certs = null;
-				int modelIndex = table.convertRowIndexToModel(table.getSelectedRow());
-				tableModel.fireTableDataChanged();
-				table.getSelectionModel().setSelectionInterval(modelIndex, modelIndex);
-				
-				if (sendEmail) {
-					sendEmail(user, metadata, zip);
-				}
-				return true;
-			}
-			
-			@Override
-			public boolean revoke(UserCertificateModel ucm, X509Certificate cert, RevocationReason reason) {
-				if (!prepareX509Infrastructure()) {
-					return false;
-				}
-
-				File caRevocationList = new File(folder, X509Utils.CA_REVOCATION_LIST);
-				File caKeystoreFile = new File(folder, X509Utils.CA_KEY_STORE);
-				if (X509Utils.revoke(cert, reason, caRevocationList, caKeystoreFile, caKeystorePassword, GitblitAuthority.this)) {
-					File certificatesConfigFile = new File(folder, X509Utils.CA_CONFIG);
-					FileBasedConfig config = new FileBasedConfig(certificatesConfigFile, FS.detect());
-					if (certificatesConfigFile.exists()) {
-						try {
-							config.load();
-						} catch (Exception e) {
-							Utils.showException(GitblitAuthority.this, e);
-						}
-					}
-					// add serial to revoked list
-					ucm.revoke(cert.getSerialNumber(), reason);
-					ucm.update(config);
-					try {
-						config.save();
-					} 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());
-		table.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
-
-			@Override
-			public void valueChanged(ListSelectionEvent e) {
-				if (e.getValueIsAdjusting()) {
-					return;
-				}
-				int row = table.getSelectedRow();
-				if (row < 0) {
-					return;
-				}
-				int modelIndex = table.convertRowIndexToModel(row);
-				UserCertificateModel ucm = tableModel.get(modelIndex);
-				if (ucm.certs == null) {
-					ucm.certs = findCerts(folder, ucm.user.username);
-				}
-				userCertificatePanel.setUserCertificateModel(ucm);
-			}
-		});
-		
-		JPanel usersPanel = new JPanel(new BorderLayout()) {
-			
-			private static final long serialVersionUID = 1L;
-
-			@Override
-			public Insets getInsets() {
-				return Utils.INSETS;
-			}
-		};
-		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.addActionListener(new ActionListener() {
-			@Override
-			public void actionPerformed(ActionEvent e) {
-				X509Metadata metadata = new X509Metadata("whocares", "whocares");
-				File certificatesConfigFile = new File(folder, X509Utils.CA_CONFIG);
-				FileBasedConfig config = new FileBasedConfig(certificatesConfigFile, FS.detect());
-				NewCertificateConfig certificateConfig = null;
-				if (certificatesConfigFile.exists()) {
-					try {
-						config.load();
-					} catch (Exception x) {
-						Utils.showException(GitblitAuthority.this, x);
-					}
-					certificateConfig = NewCertificateConfig.KEY.parse(config);
-					certificateConfig.update(metadata);
-				}
-				InputVerifier verifier = new InputVerifier() {
-					public boolean verify(JComponent comp) {
-						boolean returnValue;
-						JTextField textField = (JTextField) comp;
-						try {
-							Integer.parseInt(textField.getText());
-							returnValue = true;
-						} catch (NumberFormatException e) {
-							returnValue = false;
-						}
-						return returnValue;
-					}
-				};
-
-				JTextField siteNameTF = new JTextField(20);
-				siteNameTF.setText(gitblitSettings.getString(Keys.web.siteName, "Gitblit"));
-				JPanel siteNamePanel = Utils.newFieldPanel(Translation.get("gb.siteName"),
-						siteNameTF, Translation.get("gb.siteNameDescription"));
-
-				JTextField validityTF = new JTextField(4);
-				validityTF.setInputVerifier(verifier);
-				validityTF.setVerifyInputWhenFocusTarget(true);
-				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, 
-						panel, Translation.get("gb.newCertificateDefaults"), JOptionPane.OK_CANCEL_OPTION,
-						JOptionPane.QUESTION_MESSAGE, new ImageIcon(getClass().getResource("/settings_32x32.png")));
-				if (result == JOptionPane.OK_OPTION) {
-					try {
-						oids.update(metadata);
-						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);
-					} catch (Exception e1) {
-						Utils.showException(GitblitAuthority.this, e1);
-					}
-				}
-			}
-		});
-		
-		newSSLCertificate = new JButton(new ImageIcon(getClass().getResource("/rosette_16x16.png")));
-		newSSLCertificate.setFocusable(false);
-		newSSLCertificate.setToolTipText(Translation.get("gb.newSSLCertificate"));		
-		newSSLCertificate.addActionListener(new ActionListener() {
-			@Override
-			public void actionPerformed(ActionEvent e) {
-				Date defaultExpiration = new Date(System.currentTimeMillis() + 10*TimeUtils.ONEYEAR);
-				NewSSLCertificateDialog dialog = new NewSSLCertificateDialog(GitblitAuthority.this, defaultExpiration);
-				dialog.setModal(true);
-				dialog.setVisible(true);
-				if (dialog.isCanceled()) {
-					return;
-				}
-				final Date expires = dialog.getExpiration();
-				final String hostname = dialog.getHostname();
-				final boolean serveCertificate = dialog.isServeCertificate();
-				
-				AuthorityWorker worker = new AuthorityWorker(GitblitAuthority.this) {
-
-					@Override
-					protected Boolean doRequest() throws IOException {
-						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);
-						metadata.notAfter = expires;
-						File serverKeystoreFile = new File(folder, X509Utils.SERVER_KEY_STORE);
-						X509Certificate cert = X509Utils.newSSLCertificate(metadata, caPrivateKey, caCert, serverKeystoreFile, GitblitAuthority.this);
-						boolean hasCert = cert != null;
-						if (hasCert && serveCertificate) {
-							// update Gitblit https connector alias
-							Map<String, String> updates = new HashMap<String, String>();
-							updates.put(Keys.server.certificateAlias, metadata.commonName);
-							gitblitSettings.saveSettings(updates);
-						}
-						return hasCert;
-					}
-
-					@Override
-					protected void onSuccess() {
-						if (serveCertificate) {
-							JOptionPane.showMessageDialog(GitblitAuthority.this, 
-									MessageFormat.format(Translation.get("gb.sslCertificateGeneratedRestart"), hostname),
-									Translation.get("gb.newSSLCertificate"), JOptionPane.INFORMATION_MESSAGE);
-						} else {
-							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.addActionListener(new ActionListener() {
-			@Override
-			public void actionPerformed(ActionEvent e) {
-				int row = table.getSelectedRow();
-				if (row < 0) {
-					return;
-				}
-				int modelIndex = table.convertRowIndexToModel(row);
-				final UserCertificateModel ucm = tableModel.get(modelIndex);
-				if (ArrayUtils.isEmpty(ucm.certs)) {
-					JOptionPane.showMessageDialog(GitblitAuthority.this, MessageFormat.format(Translation.get("gb.pleaseGenerateClientCertificate"), ucm.user.getDisplayName()));
-				}
-				final File zip = new File(folder, X509Utils.CERTS + File.separator + ucm.user.username + File.separator + ucm.user.username + ".zip");
-				if (!zip.exists()) {
-					return;
-				}
-				
-				AuthorityWorker worker = new AuthorityWorker(GitblitAuthority.this) {
-					@Override
-					protected Boolean doRequest() throws IOException {
-						X509Metadata metadata = new X509Metadata(ucm.user.username, "whocares");
-						metadata.serverHostname = gitblitSettings.getString(Keys.web.siteName, Constants.NAME);
-						if (StringUtils.isEmpty(metadata.serverHostname)) {
-							metadata.serverHostname = Constants.NAME;
-						}
-						metadata.userDisplayname = ucm.user.getDisplayName();
-						return sendEmail(ucm.user, metadata, zip);
-					}
-
-					@Override
-					protected void onSuccess() {
-						JOptionPane.showMessageDialog(GitblitAuthority.this, MessageFormat.format(Translation.get("gb.clientCertificateBundleSent"),
-								ucm.user.getDisplayName()));
-					}
-					
-				};
-				worker.execute();				
-			}
-		});
-		
-		JButton logButton = new JButton(new ImageIcon(getClass().getResource("/script_16x16.png")));
-		logButton.setFocusable(false);
-		logButton.setToolTipText(Translation.get("gb.log"));		
-		logButton.addActionListener(new ActionListener() {
-			@Override
-			public void actionPerformed(ActionEvent e) {
-				File log = new File(folder, X509Utils.CERTS + File.separator + "log.txt");
-				if (log.exists()) {
-					String content = FileUtils.readContent(log,  "\n");
-					JTextArea textarea = new JTextArea(content);
-					JScrollPane scrollPane = new JScrollPane(textarea);
-					scrollPane.setPreferredSize(new Dimension(700, 400));
-					JOptionPane.showMessageDialog(GitblitAuthority.this, scrollPane, log.getAbsolutePath(), JOptionPane.INFORMATION_MESSAGE);
-				}
-			}
-		});
-		
-		final JTextField filterTextfield = new JTextField(15);
-		filterTextfield.addActionListener(new ActionListener() {
-			public void actionPerformed(ActionEvent e) {
-				filterUsers(filterTextfield.getText());
-			}
-		});
-		filterTextfield.addKeyListener(new KeyAdapter() {
-			public void keyReleased(KeyEvent e) {
-				filterUsers(filterTextfield.getText());
-			}
-		});
-		
-		JToolBar buttonControls = new JToolBar(JToolBar.HORIZONTAL);
-		buttonControls.setFloatable(false);
-		buttonControls.add(certificateDefaultsButton);
-		buttonControls.add(newSSLCertificate);
-		buttonControls.add(emailBundle);
-		buttonControls.add(logButton);
-
-		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) {
-			statusLabel.setText("JCE Unlimited Strength Jurisdiction Policy");
-		} else {
-			statusLabel.setText("JCE Standard Encryption Policy");
-		}
-		
-		JPanel root = new JPanel(new BorderLayout()) {
-			private static final long serialVersionUID = 1L;
-			public Insets getInsets() {
-				return Utils.INSETS;
-			}
-		};
-		JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, leftPanel, userCertificatePanel);
-		splitPane.setDividerLocation(1d);
-		root.add(splitPane, BorderLayout.CENTER);
-		root.add(statusLabel, BorderLayout.SOUTH);
-		return root;
-	}
-	
-	private void filterUsers(final String fragment) {
-		if (StringUtils.isEmpty(fragment)) {
-			table.setRowSorter(defaultSorter);
-			return;
-		}
-		RowFilter<UserCertificateTableModel, Object> containsFilter = new RowFilter<UserCertificateTableModel, Object>() {
-			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())) {
-						return true;
-					}
-				}
-				return false;
-			}
-		};
-		TableRowSorter<UserCertificateTableModel> sorter = new TableRowSorter<UserCertificateTableModel>(
-				tableModel);
-		sorter.setRowFilter(containsFilter);
-		table.setRowSorter(sorter);
-	}
-	
-	@Override
-	public void log(String message) {
-		BufferedWriter writer = null;
-		try {
-			writer = new BufferedWriter(new FileWriter(new File(folder, X509Utils.CERTS + File.separator + "log.txt"), true));
-			writer.write(MessageFormat.format("{0,date,yyyy-MM-dd HH:mm}: {1}", new Date(), message));
-			writer.newLine();
-			writer.flush();
-		} catch (Exception e) {
-			LoggerFactory.getLogger(GitblitAuthority.class).error("Failed to append log entry!", e);
-		} finally {
-			if (writer != null) {
-				try {
-					writer.close();
-				} catch (IOException e) {
-				}
-			}
-		}
-	}
-	
-	private boolean sendEmail(UserModel user, X509Metadata metadata, File zip) {
-		// send email
-		try {
-			if (mail.isReady()) {
-				Message message = mail.createMessage(user.emailAddress);
-				message.setSubject("Your Gitblit client certificate for " + metadata.serverHostname);
-
-				// body of email
-				String body = X509Utils.processTemplate(new File(folder, X509Utils.CERTS + File.separator + "mail.tmpl"), metadata);
-				if (StringUtils.isEmpty(body)) {
-					body = MessageFormat.format("Hi {0}\n\nHere is your client certificate bundle.\nInside the zip file are installation instructions.", user.getDisplayName());
-				}
-				Multipart mp = new MimeMultipart();
-				MimeBodyPart messagePart = new MimeBodyPart();
-				messagePart.setText(body);
-				mp.addBodyPart(messagePart);
-
-				// attach zip
-				MimeBodyPart filePart = new MimeBodyPart();
-				FileDataSource fds = new FileDataSource(zip);
-				filePart.setDataHandler(new DataHandler(fds));
-				filePart.setFileName(fds.getName());
-				mp.addBodyPart(filePart);
-
-				message.setContent(mp);
-
-				mail.sendNow(message);
-				return true;
-			} else {
-				JOptionPane.showMessageDialog(GitblitAuthority.this, "Sorry, the mail server settings are not configured properly.\nCan not send email.", Translation.get("gb.error"), JOptionPane.ERROR_MESSAGE);
-			}
-		} catch (Exception e) {
-			Utils.showException(GitblitAuthority.this, e);
-		}
-		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());
-		if (certificatesConfigFile.exists()) {
-			try {
-				config.load();
-			} catch (Exception e) {
-				Utils.showException(GitblitAuthority.this, e);
-			}
-			NewCertificateConfig certificateConfig = NewCertificateConfig.KEY.parse(config);
-			certificateConfig.update(metadata);
-		}
-	}
-	
-	private void updateAuthorityConfig(UserCertificateModel ucm) {
-		File certificatesConfigFile = new File(folder, X509Utils.CA_CONFIG);
-		FileBasedConfig config = new FileBasedConfig(certificatesConfigFile, FS.detect());
-		if (certificatesConfigFile.exists()) {
-			try {
-				config.load();
-			} catch (Exception e) {
-				Utils.showException(GitblitAuthority.this, e);
-			}
-		}
-		ucm.update(config);
-		try {
-			config.save();
-		} catch (Exception e) {
-			Utils.showException(GitblitAuthority.this, e);
-		}
-	}
-}
diff --git a/src/com/gitblit/authority/GitblitAuthorityLauncher.java b/src/com/gitblit/authority/GitblitAuthorityLauncher.java
deleted file mode 100644
index 584ac01..0000000
--- a/src/com/gitblit/authority/GitblitAuthorityLauncher.java
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * 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.authority;
-
-import java.awt.Color;
-import java.awt.EventQueue;
-import java.awt.FontMetrics;
-import java.awt.Graphics2D;
-import java.awt.SplashScreen;
-import java.io.File;
-import java.io.IOException;
-import java.util.Collections;
-import java.util.List;
-
-import com.gitblit.Constants;
-import com.gitblit.Launcher;
-import com.gitblit.build.Build;
-import com.gitblit.build.Build.DownloadListener;
-import com.gitblit.client.Translation;
-
-/**
- * Downloads dependencies and launches Gitblit Authority.
- * 
- * @author James Moger
- * 
- */
-public class GitblitAuthorityLauncher {
-
-	public static void main(String[] args) {
-		final SplashScreen splash = SplashScreen.getSplashScreen();
-		
-		DownloadListener downloadListener = new DownloadListener() {
-			@Override
-			public void downloading(String name) {
-				updateSplash(splash, Translation.get("gb.downloading") + " " + name);				
-			}
-		};
-		
-		// download authority runtime dependencies
-		Build.authority(downloadListener);
-
-		File libFolder = new File("ext");
-		List<File> jars = Launcher.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);
-		Collections.reverse(jars);
-		for (File jar : jars) {
-			try {
-				updateSplash(splash, Translation.get("gb.loading") + " " + jar.getName() + "...");
-				Launcher.addJarFile(jar);
-			} catch (IOException e) {
-
-			}
-		}
-		
-		updateSplash(splash, Translation.get("gb.starting") + " Gitblit Authority...");
-		GitblitAuthority.main(args);
-	}
-
-	private static void updateSplash(final SplashScreen splash, final String string) {
-		if (splash == null) {
-			return;
-		}
-		try {
-			EventQueue.invokeAndWait(new Runnable() {
-				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();
-						int x = 5;
-						int y = 115;
-						int w = 320 - 2 * x;
-						g.fillRect(x, y - h, w, h);
-						g.setColor(Color.lightGray);
-						g.drawRect(x, y - h, w, h);
-						g.setColor(Color.WHITE);
-						int xw = fm.stringWidth(string);
-						g.drawString(string, x + ((w - xw) / 2), y - 5);
-						
-						// paint version
-						String ver = "v" + Constants.VERSION;
-						int vw = g.getFontMetrics().stringWidth(ver);
-						g.drawString(ver, 320 - vw - 5, 34);
-						g.dispose();
-						splash.update();
-					}
-				}
-			});
-		} catch (Throwable t) {
-			t.printStackTrace();
-		}
-	}
-}
diff --git a/src/com/gitblit/authority/RequestFocusListener.java b/src/com/gitblit/authority/RequestFocusListener.java
deleted file mode 100644
index e936868..0000000
--- a/src/com/gitblit/authority/RequestFocusListener.java
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * 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.authority;
-import javax.swing.*;
-import javax.swing.event.*;
-
-/**
- *  Convenience class to request focus on a component.
- *
- *  When the component is added to a realized Window then component will
- *  request focus immediately, since the ancestorAdded event is fired
- *  immediately.
- *
- *  When the component is added to a non realized Window, then the focus
- *  request will be made once the window is realized, since the
- *  ancestorAdded event will not be fired until then.
- *
- *  Using the default constructor will cause the listener to be removed
- *  from the component once the AncestorEvent is generated. A second constructor
- *  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
-{
-	private boolean removeListener;
-
-	/*
-	 *  Convenience constructor. The listener is only used once and then it is
-	 *  removed from the component.
-	 */
-	public RequestFocusListener()
-	{
-		this(true);
-	}
-
-	/*
-	 *  Constructor that controls whether this listen can be used once or
-	 *  multiple times.
-	 *
-	 *  @param removeListener when true this listener is only invoked once
-	 *                        otherwise it can be invoked multiple times.
-	 */
-	public RequestFocusListener(boolean removeListener)
-	{
-		this.removeListener = removeListener;
-	}
-
-	@Override
-	public void ancestorAdded(AncestorEvent e)
-	{
-		JComponent component = e.getComponent();
-		component.requestFocusInWindow();
-
-		if (removeListener)
-			component.removeAncestorListener( this );
-	}
-
-	@Override
-	public void ancestorMoved(AncestorEvent e) {}
-
-	@Override
-	public void ancestorRemoved(AncestorEvent e) {}
-}
diff --git a/src/com/gitblit/authority/UserOidsPanel.java b/src/com/gitblit/authority/UserOidsPanel.java
deleted file mode 100644
index 8c3adf6..0000000
--- a/src/com/gitblit/authority/UserOidsPanel.java
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * 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.authority;
-
-import java.awt.GridLayout;
-
-import javax.swing.JPanel;
-import javax.swing.JTextField;
-
-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;
-	private JTextField organizationalUnit;
-	private JTextField organization;
-	private JTextField locality;
-	private JTextField stateProvince;
-	private JTextField countryCode;
-
-	public UserOidsPanel() {
-		super();
-		
-		displayname = new JTextField(20);
-		username = new JTextField(20);
-		username.setEditable(false);
-		emailAddress = new JTextField(20);
-		organizationalUnit = new JTextField(20);
-		organization = new JTextField(20);
-		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));
-		add(Utils.newFieldPanel(Translation.get("gb.emailAddress") + " (E)", emailAddress));
-		add(Utils.newFieldPanel(Translation.get("gb.organizationalUnit") + " (OU)", organizationalUnit));
-		add(Utils.newFieldPanel(Translation.get("gb.organization") + " (O)", organization));
-		add(Utils.newFieldPanel(Translation.get("gb.locality") + " (L)", locality));
-		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.user.getDisplayName());
-		username.setText(ucm.user.username);
-		emailAddress.setText(ucm.user.emailAddress);
-		organizationalUnit.setText(ucm.user.organizationalUnit);
-		organization.setText(ucm.user.organization);
-		locality.setText(ucm.user.locality);
-		stateProvince.setText(ucm.user.stateProvince);
-		countryCode.setText(ucm.user.countryCode);
-	}
-	
-	public void setEditable(boolean editable) {
-		displayname.setEditable(editable);
-//		username.setEditable(editable);
-		emailAddress.setEditable(editable);
-		organizationalUnit.setEditable(editable);
-		organization.setEditable(editable);
-		locality.setEditable(editable);
-		stateProvince.setEditable(editable);
-		countryCode.setEditable(editable);
-	}
-	
-	protected void updateUser(UserCertificateModel ucm) {
-		ucm.user.displayName = displayname.getText();
-		ucm.user.username = username.getText();
-		ucm.user.emailAddress = emailAddress.getText();
-		ucm.user.organizationalUnit = organizationalUnit.getText();
-		ucm.user.organization = organization.getText();
-		ucm.user.locality = locality.getText();
-		ucm.user.stateProvince = stateProvince.getText();
-		ucm.user.countryCode = countryCode.getText();
-	}
-}
diff --git a/src/com/gitblit/build/Build.java b/src/com/gitblit/build/Build.java
deleted file mode 100644
index 3a9ed75..0000000
--- a/src/com/gitblit/build/Build.java
+++ /dev/null
@@ -1,967 +0,0 @@
-/*
- * 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.build;
-
-import java.io.BufferedInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileWriter;
-import java.io.FilenameFilter;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.RandomAccessFile;
-import java.net.URL;
-import java.text.MessageFormat;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Properties;
-
-import com.gitblit.Constants;
-import com.gitblit.utils.StringUtils;
-
-/**
- * The Build class downloads runtime and compile-time jar files from the Apache
- * or Eclipse Maven repositories.
- * 
- * It also generates the Keys class from the gitblit.properties file.
- * 
- * Its important that this class have minimal compile dependencies since its
- * called very early in the build script.
- * 
- * @author James Moger
- * 
- */
-public class Build {
-
-    private static final String osName = System.getProperty("os.name");
-
-	public interface DownloadListener {
-		public void downloading(String name);
-	}
-
-	/**
-	 * BuildType enumeration representing compile-time or runtime. This is used
-	 * to download dependencies either for Gitblit GO runtime or for setting up
-	 * a development environment.
-	 */
-	public static enum BuildType {
-		RUNTIME, COMPILETIME;
-	}
-
-	private static DownloadListener downloadListener;
-
-	public static void main(String... args) {
-		runtime();
-		compiletime();
-		buildSettingKeys();
-		delete(
-				"bcmail-jdk16-1.46.jar",
-				"bcprov-jdk16-1.46.jar",
-				"src/bcmail-jdk16-1.46-sources.jar",
-				"src/bcprov-jdk16-1.46-sources.jar");
-	}
-
-	public static void runtime() {
-		downloadFromApache(MavenObject.JCOMMANDER, BuildType.RUNTIME);
-		downloadFromApache(MavenObject.JETTY, BuildType.RUNTIME);
-		downloadFromApache(MavenObject.JETTY_AJP, BuildType.RUNTIME);
-		downloadFromApache(MavenObject.SERVLET, BuildType.RUNTIME);
-		downloadFromApache(MavenObject.SLF4JAPI, BuildType.RUNTIME);
-		downloadFromApache(MavenObject.SLF4LOG4J, BuildType.RUNTIME);
-		downloadFromApache(MavenObject.LOG4J, BuildType.RUNTIME);
-		downloadFromApache(MavenObject.WICKET, BuildType.RUNTIME);
-		downloadFromApache(MavenObject.WICKET_EXT, BuildType.RUNTIME);
-		downloadFromApache(MavenObject.WICKET_AUTH_ROLES, BuildType.RUNTIME);
-		downloadFromApache(MavenObject.WICKET_GOOGLE_CHARTS, BuildType.RUNTIME);
-		downloadFromApache(MavenObject.MARKDOWNPAPERS, BuildType.RUNTIME);
-		downloadFromApache(MavenObject.BOUNCYCASTLE, BuildType.RUNTIME);
-		downloadFromApache(MavenObject.BOUNCYCASTLE_MAIL, BuildType.RUNTIME);
-		downloadFromApache(MavenObject.BOUNCYCASTLE_PKIX, BuildType.RUNTIME);
-		downloadFromApache(MavenObject.JSCH, BuildType.RUNTIME);
-		downloadFromApache(MavenObject.ROME, BuildType.RUNTIME);
-		downloadFromApache(MavenObject.JDOM, BuildType.RUNTIME);
-		downloadFromApache(MavenObject.GSON, BuildType.RUNTIME);
-		downloadFromApache(MavenObject.MAIL, BuildType.RUNTIME);
-		downloadFromApache(MavenObject.GROOVY, BuildType.RUNTIME);
-		downloadFromApache(MavenObject.LUCENE, BuildType.RUNTIME);
-		downloadFromApache(MavenObject.LUCENE_HIGHLIGHTER, BuildType.RUNTIME);
-		downloadFromApache(MavenObject.LUCENE_MEMORY, BuildType.RUNTIME);
-		downloadFromApache(MavenObject.LUCENE_QUERIES, BuildType.RUNTIME);
-		downloadFromApache(MavenObject.JAKARTA_REGEXP, BuildType.RUNTIME);
-		downloadFromApache(MavenObject.UNBOUND_ID, BuildType.RUNTIME);
-		downloadFromApache(MavenObject.IVY, BuildType.RUNTIME);
-		downloadFromApache(MavenObject.JCALENDAR, BuildType.RUNTIME);
-		downloadFromApache(MavenObject.COMMONS_COMPRESS, BuildType.RUNTIME);
-		downloadFromApache(MavenObject.XZ, BuildType.RUNTIME);
-
-		//needed for selenium ui tests
-		downloadFromApacheToExtSelenium(MavenObject.SEL_API, BuildType.RUNTIME);
-		downloadFromApacheToExtSelenium(MavenObject.SEL_FF, BuildType.RUNTIME);
-		downloadFromApacheToExtSelenium(MavenObject.SEL_JAVA, BuildType.RUNTIME);
-		downloadFromApacheToExtSelenium(MavenObject.SEL_REMOTE, BuildType.RUNTIME);
-		downloadFromApacheToExtSelenium(MavenObject.SEL_SUPPORT, BuildType.RUNTIME);
-		downloadFromApacheToExtSelenium(MavenObject.GUAVA, BuildType.RUNTIME);
-		downloadFromApacheToExtSelenium(MavenObject.JSON, BuildType.RUNTIME);
-		downloadFromApacheToExtSelenium(MavenObject.COMMONS_EXEC, BuildType.RUNTIME);
-		downloadFromApacheToExtSelenium(MavenObject.HTTPCLIENT, BuildType.RUNTIME);
-		downloadFromApacheToExtSelenium(MavenObject.HTTPCORE, BuildType.RUNTIME);
-		downloadFromApacheToExtSelenium(MavenObject.HTTPMIME, BuildType.RUNTIME);
-		downloadFromApacheToExtSelenium(MavenObject.COMMONS_LOGGING, BuildType.RUNTIME);
-		
-		downloadFromEclipse(MavenObject.JGIT, BuildType.RUNTIME);
-		downloadFromEclipse(MavenObject.JGIT_HTTP, BuildType.RUNTIME);
-	}
-
-	public static void compiletime() {
-		downloadFromApache(MavenObject.JUNIT, BuildType.RUNTIME);
-		downloadFromApache(MavenObject.HAMCREST, BuildType.RUNTIME);
-		downloadFromApache(MavenObject.JCOMMANDER, BuildType.COMPILETIME);
-		downloadFromApache(MavenObject.JETTY, BuildType.COMPILETIME);
-		downloadFromApache(MavenObject.JETTY_AJP, BuildType.COMPILETIME);
-		downloadFromApache(MavenObject.SERVLET, BuildType.COMPILETIME);
-		downloadFromApache(MavenObject.SLF4JAPI, BuildType.COMPILETIME);
-		downloadFromApache(MavenObject.SLF4LOG4J, BuildType.COMPILETIME);
-		downloadFromApache(MavenObject.LOG4J, BuildType.COMPILETIME);
-		downloadFromApache(MavenObject.WICKET, BuildType.COMPILETIME);
-		downloadFromApache(MavenObject.WICKET_EXT, BuildType.COMPILETIME);
-		downloadFromApache(MavenObject.WICKET_AUTH_ROLES, BuildType.COMPILETIME);
-		downloadFromApache(MavenObject.WICKET_GOOGLE_CHARTS, BuildType.COMPILETIME);
-		downloadFromApache(MavenObject.MARKDOWNPAPERS, BuildType.COMPILETIME);
-		downloadFromApache(MavenObject.BOUNCYCASTLE, BuildType.COMPILETIME);
-		downloadFromApache(MavenObject.BOUNCYCASTLE_MAIL, BuildType.COMPILETIME);
-		downloadFromApache(MavenObject.BOUNCYCASTLE_PKIX, BuildType.COMPILETIME);
-		downloadFromApache(MavenObject.JSCH, BuildType.COMPILETIME);
-		downloadFromApache(MavenObject.ROME, BuildType.COMPILETIME);
-		downloadFromApache(MavenObject.JDOM, BuildType.COMPILETIME);
-		downloadFromApache(MavenObject.GSON, BuildType.COMPILETIME);
-		downloadFromApache(MavenObject.MAIL, BuildType.COMPILETIME);
-		downloadFromApache(MavenObject.GROOVY, BuildType.COMPILETIME);
-		downloadFromApache(MavenObject.LUCENE, BuildType.COMPILETIME);
-		downloadFromApache(MavenObject.LUCENE_HIGHLIGHTER, BuildType.COMPILETIME);
-		downloadFromApache(MavenObject.LUCENE_MEMORY, BuildType.COMPILETIME);
-		downloadFromApache(MavenObject.LUCENE_QUERIES, BuildType.COMPILETIME);
-		downloadFromApache(MavenObject.JAKARTA_REGEXP, BuildType.COMPILETIME);
-		downloadFromApache(MavenObject.UNBOUND_ID, BuildType.COMPILETIME);
-		downloadFromApache(MavenObject.IVY, BuildType.COMPILETIME);
-		downloadFromApache(MavenObject.JCALENDAR, BuildType.COMPILETIME);
-		downloadFromApache(MavenObject.COMMONS_COMPRESS, BuildType.COMPILETIME);
-		downloadFromApache(MavenObject.XZ, BuildType.COMPILETIME);
-
-		//needed for selenium ui tests
-		downloadFromApacheToExtSelenium(MavenObject.SEL_API, BuildType.COMPILETIME);
-		downloadFromApacheToExtSelenium(MavenObject.SEL_FF, BuildType.COMPILETIME);
-		downloadFromApacheToExtSelenium(MavenObject.SEL_JAVA, BuildType.COMPILETIME);
-		downloadFromApacheToExtSelenium(MavenObject.SEL_REMOTE, BuildType.COMPILETIME);
-		downloadFromApacheToExtSelenium(MavenObject.SEL_SUPPORT, BuildType.COMPILETIME);
-		downloadFromApacheToExtSelenium(MavenObject.GUAVA, BuildType.COMPILETIME);
-		downloadFromApacheToExtSelenium(MavenObject.JSON, BuildType.COMPILETIME);
-		downloadFromApacheToExtSelenium(MavenObject.COMMONS_EXEC, BuildType.COMPILETIME);
-		downloadFromApacheToExtSelenium(MavenObject.HTTPCLIENT, BuildType.COMPILETIME);
-		downloadFromApacheToExtSelenium(MavenObject.HTTPCORE, BuildType.COMPILETIME);
-		downloadFromApacheToExtSelenium(MavenObject.HTTPMIME, BuildType.COMPILETIME);
-		downloadFromApacheToExtSelenium(MavenObject.COMMONS_LOGGING, BuildType.COMPILETIME);
-		
-		downloadFromEclipse(MavenObject.JGIT, BuildType.COMPILETIME);
-		downloadFromEclipse(MavenObject.JGIT_HTTP, BuildType.COMPILETIME);
-
-		// needed for site publishing
-		downloadFromApache(MavenObject.COMMONSNET, BuildType.RUNTIME);
-	}
-	
-	private static void delete(String... files) {
-		for (String name : files) {
-			File file = new File("ext", name);
-			if (file.exists()) {
-				file.delete();
-			}
-		}
-	}
-
-	public static void federationClient() {
-		downloadFromApache(MavenObject.JCOMMANDER, BuildType.RUNTIME);
-		downloadFromApache(MavenObject.SERVLET, BuildType.RUNTIME);
-		downloadFromApache(MavenObject.MAIL, BuildType.RUNTIME);
-		downloadFromApache(MavenObject.SLF4JAPI, BuildType.RUNTIME);
-		downloadFromApache(MavenObject.SLF4LOG4J, BuildType.RUNTIME);
-		downloadFromApache(MavenObject.LOG4J, BuildType.RUNTIME);
-		downloadFromApache(MavenObject.GSON, BuildType.RUNTIME);
-		downloadFromApache(MavenObject.JSCH, BuildType.RUNTIME);
-		downloadFromApache(MavenObject.LUCENE, BuildType.RUNTIME);
-		downloadFromApache(MavenObject.LUCENE_HIGHLIGHTER, BuildType.RUNTIME);
-		downloadFromApache(MavenObject.LUCENE_MEMORY, BuildType.RUNTIME);
-		downloadFromApache(MavenObject.LUCENE_QUERIES, BuildType.RUNTIME);
-		downloadFromApache(MavenObject.JAKARTA_REGEXP, BuildType.RUNTIME);
-
-		downloadFromEclipse(MavenObject.JGIT, BuildType.RUNTIME);
-	}
-
-	public static void manager(DownloadListener listener) {
-		downloadListener = listener;
-		downloadFromApache(MavenObject.GSON, BuildType.RUNTIME);
-		downloadFromApache(MavenObject.ROME, BuildType.RUNTIME);
-		downloadFromApache(MavenObject.JDOM, BuildType.RUNTIME);
-		downloadFromApache(MavenObject.JSCH, BuildType.RUNTIME);
-
-		downloadFromEclipse(MavenObject.JGIT, BuildType.RUNTIME);
-	}
-	
-	public static void authority(DownloadListener listener) {
-		downloadListener = listener;
-		downloadFromApache(MavenObject.JCOMMANDER, BuildType.RUNTIME);
-		downloadFromApache(MavenObject.JSCH, BuildType.RUNTIME);
-		downloadFromApache(MavenObject.SLF4JAPI, BuildType.RUNTIME);
-		downloadFromApache(MavenObject.SLF4LOG4J, BuildType.RUNTIME);
-		downloadFromApache(MavenObject.LOG4J, BuildType.RUNTIME);
-		downloadFromApache(MavenObject.BOUNCYCASTLE, BuildType.RUNTIME);
-		downloadFromApache(MavenObject.BOUNCYCASTLE_MAIL, BuildType.RUNTIME);
-		downloadFromApache(MavenObject.BOUNCYCASTLE_PKIX, BuildType.RUNTIME);
-		downloadFromApache(MavenObject.JCALENDAR, BuildType.RUNTIME);
-		downloadFromApache(MavenObject.MAIL, BuildType.RUNTIME);
-
-		downloadFromEclipse(MavenObject.JGIT, BuildType.RUNTIME);
-	}
-
-	/**
-	 * Builds the Keys class based on the gitblit.properties file and inserts
-	 * the class source into the project source folder.
-	 */
-	public static void buildSettingKeys() {
-		// Load all keys
-		Properties properties = new Properties();
-		FileInputStream is = null;
-		try {
-			is = new FileInputStream(new File("distrib", Constants.PROPERTIES_FILE));
-			properties.load(is);
-		} catch (Throwable t) {
-			t.printStackTrace();
-		} finally {
-			if (is != null) {
-				try {
-					is.close();
-				} catch (Throwable t) {
-					// IGNORE
-				}
-			}
-		}
-		List<String> keys = new ArrayList<String>(properties.stringPropertyNames());
-		Collections.sort(keys);
-
-		KeyGroup root = new KeyGroup();
-		for (String key : keys) {
-			root.addKey(key);
-		}
-
-		// Save Keys class definition
-		try {
-			File file = new File("src/com/gitblit/Keys.java");
-			FileWriter fw = new FileWriter(file, false);
-			fw.write(root.generateClass("com.gitblit", "Keys"));
-			fw.close();
-		} catch (Throwable t) {
-			t.printStackTrace();
-		}
-	}
-	
-	private static class KeyGroup {
-		final KeyGroup parent;
-		final String namespace;
-		
-		String name;
-		List<KeyGroup> children;		
-		List<String> fields;		
-		
-		KeyGroup() {
-			this.parent = null;
-			this.namespace = "";
-			this.name = "";	
-		}
-		
-		KeyGroup(String namespace, KeyGroup parent) {
-			this.parent = parent;
-			this.namespace = namespace;
-			if (parent.children == null) {
-				parent.children = new ArrayList<KeyGroup>();
-			}
-			parent.children.add(this);
-		}
-		
-		void addKey(String key) {
-			String keyspace = "";
-			String field = key;
-			if (key.indexOf('.') > -1) {
-				keyspace = key.substring(0, key.lastIndexOf('.'));
-				field = key.substring(key.lastIndexOf('.') + 1);
-				KeyGroup group = addKeyGroup(keyspace);
-				group.addKey(field);
-			} else {
-				if (fields == null) {
-					fields = new ArrayList<String>();
-				}
-				fields.add(key);
-			}
-		}
-				
-		KeyGroup addKeyGroup(String keyspace) {
-			KeyGroup parent = this;
-			KeyGroup node = null;			
-			String [] space = keyspace.split("\\.");
-			for (int i = 0; i < space.length; i++) {
-				StringBuilder namespace = new StringBuilder();
-				for (int j = 0; j <= i; j++) {
-					namespace.append(space[j]);
-					if (j < i) {
-						namespace.append('.');
-					}
-				}
-				if (parent.children != null) {
-					for (KeyGroup child : parent.children) {
-						if (child.name.equals(space[i])) {
-							node = child;					
-						}
-					}
-				}
-				if (node == null) {
-					node = new KeyGroup(namespace.toString(), parent);
-					node.name = space[i];
-				}
-				parent = node;
-				node = null;
-			}
-			return parent;
-		}		
-		
-		String fullKey(String field) {
-			if (namespace.equals("")) {
-				return field;
-			}
-			return namespace + "." + field;
-		}
-		
-		String generateClass(String packageName, String className) {
-			StringBuilder sb = new StringBuilder();
-			sb.append("package ").append(packageName).append(";\n");
-			sb.append('\n');
-			sb.append("/*\n");
-			sb.append(" * This class is auto-generated from the properties file.\n");
-			sb.append(" * Do not version control!\n");
-			sb.append(" */\n");
-			sb.append(MessageFormat.format("public final class {0} '{'\n\n", className));
-			sb.append(generateClass(this, 0));
-			sb.append("}\n");
-			return sb.toString();
-		}
-		
-		String generateClass(KeyGroup group, int level) {
-			String classIndent = StringUtils.leftPad("", level, '\t');
-			String fieldIndent = StringUtils.leftPad("", level + 1, '\t');
-			
-			// begin class
-			StringBuilder sb = new StringBuilder();
-			if (!group.namespace.equals("")) {
-				sb.append(classIndent).append(MessageFormat.format("public static final class {0} '{'\n\n", group.name));
-				sb.append(fieldIndent).append(MessageFormat.format("public static final String _ROOT = \"{0}\";\n\n", group.namespace));
-			}
-			
-			if (group.fields != null) {
-				// fields
-				for (String field : group.fields) {					
-					sb.append(fieldIndent).append(MessageFormat.format("public static final String {0} = \"{1}\";\n\n", field, group.fullKey(field)));
-				}
-			}
-			if (group.children != null) {
-				// inner classes
-				for (KeyGroup child : group.children) {
-					sb.append(generateClass(child, level + 1));
-				}
-			}
-			// end class
-			if (!group.namespace.equals("")) {
-				sb.append(classIndent).append("}\n\n");
-			}
-			return sb.toString();			
-		}
-	}
-
-	/**
-	 * Download a file from the official Apache Maven repository.
-	 * 
-	 * @param mo
-	 *            the maven object to download.
-	 * @return
-	 */
-	private static List<File> downloadFromApache(MavenObject mo, BuildType type) {
-		return downloadFromMaven("http://repo1.maven.org/maven2/", mo, type);
-	}
-
-	/**
-	 * Download a file from the official Eclipse Maven repository.
-	 * 
-	 * @param mo
-	 *            the maven object to download.
-	 * @return
-	 */
-	private static List<File> downloadFromEclipse(MavenObject mo, BuildType type) {
-		return downloadFromMaven("http://download.eclipse.org/jgit/maven/", mo, type);
-	}
-
-	/**
-	 * Download a file from a Maven repository.
-	 * 
-	 * @param mo
-	 *            the maven object to download.
-	 * @return
-	 */
-	private static List<File> downloadFromMaven(String mavenRoot, MavenObject mo, BuildType type, String targetFolder) {
-		List<File> downloads = new ArrayList<File>();
-		String[] jars = { "" };
-		if (BuildType.RUNTIME.equals(type)) {
-			jars = new String[] { "" };
-		} else if (BuildType.COMPILETIME.equals(type)) {
-			jars = new String[] { "-sources" };
-		}
-		for (String jar : jars) {
-			File targetFile = mo.getLocalFile(targetFolder, jar);
-			if ("-sources".equals(jar)) {
-				File relocated = new File(targetFolder+"/src", targetFile.getName());
-				if (targetFile.exists()) {
-					// move -sources jar to ext/src folder
-					targetFile.renameTo(relocated);
-				}
-				// -sources jars are located in ext/src
-				targetFile = relocated;
-			}
-			
-			if (targetFile.exists()) {
-				downloads.add(targetFile);
-				removeObsoleteArtifacts(mo, type, targetFile.getParentFile());
-				continue;
-			}
-			String expectedSHA1 = mo.getSHA1(jar);
-			if (expectedSHA1 == null) {
-				// skip this jar
-				continue;
-			}
-			float approximateLength = mo.getApproximateLength(jar);
-			String mavenURL = mavenRoot + mo.getRepositoryPath(jar);
-			if (!targetFile.getAbsoluteFile().getParentFile().exists()) {
-				boolean success = targetFile.getAbsoluteFile().getParentFile().mkdirs();
-				if (!success) {
-					throw new RuntimeException("Failed to create destination folder structure!");
-				}
-			}
-			if (downloadListener != null) {
-				downloadListener.downloading(mo.name + "...");
-			}
-			ByteArrayOutputStream buff = new ByteArrayOutputStream();
-			try {
-				URL url = new URL(mavenURL);
-				InputStream in = new BufferedInputStream(url.openStream());
-				byte[] buffer = new byte[4096];
-				int downloadedLen = 0;
-				float lastProgress = 0f;
-
-				updateDownload(0, targetFile);
-				while (true) {
-					int len = in.read(buffer);
-					if (len < 0) {
-						break;
-					}
-					downloadedLen += len;
-					buff.write(buffer, 0, len);
-					float progress = downloadedLen / approximateLength;
-					if (progress - lastProgress >= 0.1f) {
-						lastProgress = progress;
-						updateDownload(progress, targetFile);
-						if (downloadListener != null) {
-							int percent = Math.min(100, Math.round(100 * progress));
-							downloadListener.downloading(mo.name + " (" + percent + "%)");
-						}
-					}
-				}
-				in.close();
-				updateDownload(1f, targetFile);
-				if (downloadListener != null) {
-					downloadListener.downloading(mo.name + " (100%)");
-				}
-
-			} catch (IOException e) {
-				throw new RuntimeException("Error downloading " + mavenURL + " to " + targetFile, e);
-			}
-			byte[] data = buff.toByteArray();
-			String calculatedSHA1 = StringUtils.getSHA1(data);
-
-			System.out.println();
-
-			if (expectedSHA1.length() == 0) {
-				updateProgress(0, "sha: " + calculatedSHA1);
-				System.out.println();
-			} else {
-				if (!calculatedSHA1.equals(expectedSHA1)) {
-					throw new RuntimeException("SHA1 checksum mismatch; got: " + calculatedSHA1);
-				}
-			}
-			try {
-				RandomAccessFile ra = new RandomAccessFile(targetFile, "rw");
-				ra.write(data);
-				ra.setLength(data.length);
-				ra.close();
-			} catch (IOException e) {
-				throw new RuntimeException("Error writing to file " + targetFile, e);
-			}
-			downloads.add(targetFile);
-			
-			removeObsoleteArtifacts(mo, type, targetFile.getParentFile());
-		}
-		return downloads;
-	}
-	
-	/**
-	 * Download a file from the official Apache Maven repository.
-	 * 
-	 * @param mo
-	 *            the maven object to download.
-	 * @return
-	 */
-	private static List<File> downloadFromApacheToExtSelenium(MavenObject mo,
-			BuildType type) {
-		return downloadFromMaven("http://repo1.maven.org/maven2/", mo, type,
-				"ext/seleniumhq");
-	}
-	
-	/**
-	 * Download a file from the official Apache Maven repository.
-	 * 
-	 * @param mo
-	 *            the maven object to download.
-	 * @return
-	 */
-	private static List<File> downloadFromMaven(String mavenRoot,
-			MavenObject mo, BuildType type) {
-		return downloadFromMaven(mavenRoot, mo, type, "ext");
-	}
-	
-	private static void removeObsoleteArtifacts(final MavenObject mo, final BuildType type, File folder) {
-		File [] removals = folder.listFiles(new FilenameFilter() {
-			@Override
-			public boolean accept(File dir, String name) {
-				String n = name.toLowerCase();
-				String dep = mo.artifact.toLowerCase();
-				if (n.startsWith(dep)) {
-					String suffix = "-" + mo.version;
-					if (type.equals(BuildType.COMPILETIME)) {
-						suffix += "-sources.jar";
-					} else {
-						suffix += ".jar";
-					}
-					if (!n.endsWith(suffix)) {
-						return true;
-					}
-				}
-				return false;
-			}
-		});
-		
-		// delete any matches
-		if (removals != null) {
-			for (File file : removals) {
-				System.out.println("deleting " + file);
-				file.delete();
-			}
-		}
-	}
-
-	private static void updateDownload(float progress, File file) {
-		updateProgress(progress, "d/l: " + file.getName());
-	}
-
-	private static void updateProgress(float progress, String url) {
-        boolean isWindows = osName.contains("Windows");
-        String anim = "==========";
-		int width = Math.round(anim.length() * progress);
-        if (isWindows) System.out.print("\r");
-		System.out.print("[");
-		System.out.print(anim.substring(0, Math.min(width, anim.length())));
-		for (int i = 0; i < anim.length() - width; i++) {
-			System.out.print(' ');
-		}
-		System.out.print("] " + url);
-        if (!isWindows) System.out.println();
-	}
-
-	/**
-	 * MavenObject represents a complete maven artifact (binary, sources, and
-	 * javadoc). MavenObjects can be downloaded and checksummed to confirm
-	 * authenticity.
-	 */
-	private static class MavenObject {
-
-		public static final MavenObject JCOMMANDER = new MavenObject(
-                "jCommander", "com/beust", "jcommander", "1.17",
-                34000, 32000, 141000,
-				"219a3540f3b27d7cc3b1d91d6ea046cd8723290e",
-				"0bb50eec177acf0e94d58e0cf07262fe5164331d",
-				"c7adc475ca40c288c93054e0f4fe58f3a98c0cb5");
-
-		public static final MavenObject JETTY = new MavenObject(
-                "Jetty", "org/eclipse/jetty/aggregate", "jetty-webapp", "7.6.8.v20121106",
-                1000000, 680000, 2720000,
-                "6333969b4d509c4b681e05302ca7ebccb9c3efb5",
-				"354f2752ed6544296bc0fc92e533d68a5b03045b",
-				"");
-
-		public static final MavenObject JETTY_AJP = new MavenObject(
-                "Jetty-AJP", "org/eclipse/jetty", "jetty-ajp", "7.6.8.v20121106",
-                32000, 22000, 97000,
-                "95bd1c89bb2afd4eeaabc6f4b0183a9f26a522d7",
-                "e1fc2539202ebb240a87a080bc44a24c93d7318b",
-                "");
-		
-		public static final MavenObject SERVLET = new MavenObject(
-                "Servlet 3.0", "javax/servlet", "javax.servlet-api", "3.0.1",
-                84000, 211000, 0,
-				"6bf0ebb7efd993e222fc1112377b5e92a13b38dd",
-				"01952f91d84016a39e31346c9d18bd8c9c4a128a",
-                null);
-
-		public static final MavenObject SLF4JAPI = new MavenObject(
-                "SLF4J API", "org/slf4j", "slf4j-api", "1.6.6",
-                25500, 45000, 182000,
-				"ce53b0a0e2cfbb27e8a59d38f79a18a5c6a8d2b0",
-				"bcd0e21b1572960cefd449f8a16efab5b6b8e644",
-				"4253b52aabf1c5a5f20c191a261e6ada0fcf621d");
-
-		public static final MavenObject SLF4LOG4J = new MavenObject(
-                "SLF4J LOG4J", "org/slf4j", "slf4j-log4j12", "1.6.6",
-                9800, 9500, 52400,
-				"5cd9b4fbc3ff6a97beaade3206137d76f65df805",
-				"497bfac9a678118e7ff75d1f3b8c3bcc06dc9c8c",
-				"69855e2a85d9249bb577df3c5076bc2cb34975d7");
-
-		public static final MavenObject LOG4J = new MavenObject(
-                "Apache LOG4J", "log4j", "log4j", "1.2.17",
-                481000, 471000, 1455000,
-                "5af35056b4d257e4b64b9e8069c0746e8b08629f",
-				"677abe279b68c5e7490d6d50c6951376238d7d3e",
-				"c10c20168206896442f3192d5417815df7fcbf9a");
-
-		public static final MavenObject WICKET = new MavenObject(
-                "Apache Wicket", "org/apache/wicket", "wicket", "1.4.21",
-                1960000, 1906000, 6818000,
-				"cce9dfd3088e18a3cdcf9be33b5b76caa48dc4c6",
-				"e8c2bfe2c96a2da7a0eca947a2f60dc3242e7229",
-				"");
-
-		public static final MavenObject WICKET_EXT = new MavenObject(
-                "Apache Wicket Extensions", "org/apache/wicket", "wicket-extensions", "1.4.21",
-                1180000, 1118000, 1458000,
-				"fac510c7ee4399a29b927405ec3de40b67d105d8",
-				"ee3409ce9ed64ad8cc8d69abbd7d63f07e10851a",
-				"");
-
-		public static final MavenObject WICKET_AUTH_ROLES = new MavenObject(
-				"Apache Wicket Auth Roles", "org/apache/wicket", "wicket-auth-roles", "1.4.21",
-				44000, 45000, 166000,
-                "e78df70ca942e2e15287c393f236b32fbe6f9a30",
-				"47c301212cce43a70caa72f41a9a1aefcf26a533",
-				"");
-
-		public static final MavenObject WICKET_GOOGLE_CHARTS = new MavenObject(
-				"Apache Wicket Google Charts Add-On", "org/wicketstuff", "googlecharts", "1.4.21",
-				34000, 18750, 161000,
-                "73d7540267afc3a0e91ca6148d3073e050dba180",
-				"627b125cc6029d4d5c59c3a910c1bef347384d97",
-				"");
-
-		public static final MavenObject JUNIT = new MavenObject(
-                "JUnit", "junit", "junit", "4.10",
-				253000, 141000, 0, "e4f1766ce7404a08f45d859fb9c226fc9e41a861",
-                "6c98d6766e72d5575f96c9479d1c1d3b865c6e25", "");
-
-		public static final MavenObject HAMCREST = new MavenObject(
-                "Hamcrest Core", "org/hamcrest", "hamcrest-core", "1.1",
-				77000, 0, 0,
-                "860340562250678d1a344907ac75754e259cdb14",
-                null,
-                "");
-
-		public static final MavenObject MARKDOWNPAPERS = new MavenObject(
-                "MarkdownPapers", "org/tautua/markdownpapers", "markdownpapers-core", "1.3.2",
-                92000, 60000, 268000,
-				"da22db6660e90b9a677bbdfc2c511c619ea5c249",
-				"6a7228280a229144afe6c01351a8f44675d8524d",
-				"");
-
-		public static final MavenObject BOUNCYCASTLE = new MavenObject(
-                "BouncyCastle", "org/bouncycastle", "bcprov-jdk15on", "1.47",
-                1900000, 1400000, 4670000,
-				"b6f5d9926b0afbde9f4dbe3db88c5247be7794bb",
-				"85e6e1ad449d5a3f09624bf4038fc8d2b02de81c",
-				"");
-
-		public static final MavenObject BOUNCYCASTLE_MAIL = new MavenObject(
-                "BouncyCastle Mail", "org/bouncycastle", "bcmail-jdk15on", "1.47",
-                502000, 420000, 482000,
-				"a35ccec640177d0de5815568529021af5546d6a7",
-				"f742330cfe1e7365dbdf773c24b92382172164a7",
-				"");
-
-		public static final MavenObject BOUNCYCASTLE_PKIX = new MavenObject(
-                "BouncyCastle PKIX", "org/bouncycastle", "bcpkix-jdk15on", "1.47",
-                502000, 420000, 482000,
-				"cd204e6f26d2bbf65ff3a30de8831d3a1344e851",
-				"80e774a73d0e6a6b40ddf35fff613f9f30fe2a98",
-				"");
-
-		public static final MavenObject JGIT = new MavenObject(
-                "JGit", "org/eclipse/jgit", "org.eclipse.jgit", "2.2.0.201212191850-r",
-                1600000, 1565000, 3460000,
-				"97d0761b9dd618d1f9f6c16c35c3ddf045ba536c",
-				"08dcf9546f4d61e1b8a50df5da5513006023b64b",
-				"");
-
-		public static final MavenObject JGIT_HTTP = new MavenObject(
-                "JGit", "org/eclipse/jgit", "org.eclipse.jgit.http.server", "2.2.0.201212191850-r",
-                68000, 62000, 110000,
-				"8ad4fc4fb9529d645249bb46ad7e54d98436cb65",
-				"3385cf294957d1d34c1270b468853aea347b36ca",
-				"");
-
-		public static final MavenObject JSCH = new MavenObject(
-                "JSch", "com/jcraft", "jsch", "0.1.44-1",
-                214000, 211000, 413000,
-                "2e9ae08de5a71bd0e0d3ba2558598181bfa71d4e",
-				"e528f593b19b04d500992606f58b87fcfded8883",
-				"d0ffadd0a4ab909d94a577b5aad43c13b617ddcb");
-
-		public static final MavenObject COMMONSNET = new MavenObject(
-                "commons-net", "commons-net", "commons-net", "1.4.0",
-                181000, 0, 0,
-                "eb47e8cad2dd7f92fd7e77df1d1529cae87361f7",
-				"",
-                "");
-
-		public static final MavenObject ROME = new MavenObject(
-                "rome", "rome", "rome", "0.9",
-				208000, 196000, 407000,
-                "dee2705dd01e79a5a96a17225f5a1ae30470bb18",
-				"226f851dc44fd94fe70b9c471881b71f88949cbf",
-				"8d7d867b97eeb3a9196c3926da550ad042941c1b");
-
-		public static final MavenObject JDOM = new MavenObject(
-                "jdom", "jdom", "jdom", "1.0",
-				153000, 235000, 445000,
-                "a2ac1cd690ab4c80defe7f9bce14d35934c35cec",
-				"662abe0196cf554d4d7374f5d6382034171b652c",
-				"");
-
-		public static final MavenObject GSON = new MavenObject(
-                "gson", "com/google/code/gson", "gson", "1.7.2",
-                174000, 142000, 247000,
-				"112366d8158749e25532ebce162232c6e0fb20a5",
-				"a6fe3006df46174a9c1c56b3c51357b9bfde5874",
-				"537f729ac63b6132a795a3c1f2e13b327e872333");
-
-		public static final MavenObject MAIL = new MavenObject(
-                "javax.mail", "javax/mail", "mail", "1.4.3",
-                462000, 642000, 0,
-                "8154bf8d666e6db154c548dc31a8d512c273f5ee",
-				"5875e2729de83a4e46391f8f979ec8bd03810c10", null);
-
-		public static final MavenObject GROOVY = new MavenObject(
-                "groovy", "org/codehaus/groovy", "groovy-all", "1.8.8",
-                6143000, 2290000, 4608000,
-                "98a489343d3c30da817d36cbea5de11ed07bef31",
-				"5f847ed18009f8a034bad3906e39f771c01728c1", "");
-
-		public static final MavenObject LUCENE = new MavenObject(
-                "lucene", "org/apache/lucene", "lucene-core", "3.6.1",
-                1540000, 1431000, 3608000,
-                "6ae2c83c77a1ffa5840b9151a271ab3f451f6e0c",
-				"6925deb6b78e63bbcac382004f00b98133327057", "");
-
-		public static final MavenObject LUCENE_HIGHLIGHTER = new MavenObject(
-                "lucene highlighter", "org/apache/lucene", "lucene-highlighter", "3.6.1",
-                89200, 85000, 0,
-                "2bd49695e9891697c5f290aa94c3412dfb95b096",
-				"20ae81816ce9c27186ef0f2e92a57812c9ee3b6c", "");
-
-		public static final MavenObject LUCENE_MEMORY = new MavenObject(
-                "lucene memory", "org/apache/lucene", "lucene-memory", "3.6.1",
-                30000, 23000, 0,
-                "8c7ca5572edea50973dc0d26cf75c27047eebe7e",
-				"2e291e96d25132e002b1c8240e361d1272d113e1", "");
-
-		public static final MavenObject LUCENE_QUERIES = new MavenObject(
-                "lucene queries", "org/apache/lucene", "lucene-queries", "3.6.1",
-                47400, 48600, 0,
-                "4ed6022dd4aa80b932a1546e7e39e3b8bbe7acb7",
-				"dc425c75d988e4975d314772035a46b6a17dcc8d", "");
-
-		public static final MavenObject JAKARTA_REGEXP = new MavenObject(
-                "jakarta regexp", "jakarta-regexp", "jakarta-regexp", "1.4",
-                28500, 0, 0,
-                "0ea514a179ac1dd7e81c7e6594468b9b9910d298",
-				null, "");
-		
-		public static final MavenObject UNBOUND_ID = new MavenObject(
-                "unbound id", "com/unboundid", "unboundid-ldapsdk", "2.3.0",
-                1383417, 1439721, 0,
-                "6fde8d9fb4ee3e7e3d7e764e3ea57195971e2eb2",
-				"5276d3d29630693dba99ab9f7ea54f4c471d3af1",
-                "");
-		
-		public static final MavenObject IVY = new MavenObject(
-                "ivy", "org/apache/ivy", "ivy",	"2.2.0",
-                948000, 744000, 0,
-                "f9d1e83e82fc085093510f7d2e77d81d52bc2081",
-				"0312527950ad0e8fbab37228fbed3bf41a6fe0a1", "");
-
-		public static final MavenObject JCALENDAR = new MavenObject(
-                "jcalendar", "com/toedter", "jcalendar", "1.3.2",
-                127000, 0, 0,
-                "323a672aeacb5f5f4461be3b7f7d9d3e4bda80d4",
-				null, "");
-
-		public static final MavenObject COMMONS_COMPRESS = new MavenObject(
-                "commons-compress", "org/apache/commons", "commons-compress", "1.4.1",
-                242000, 265000, 0,
-                "b02e84a993d88568417536240e970c4b809126fd",
-				"277d39267403965a7a192474794a29bac6760a25", "");
-
-		public static final MavenObject XZ = new MavenObject(
-                "xz", "org/tukaani", "xz", "1.0",
-                95000, 120000, 0,
-                "ecff5cb8b1189514c9d1d8d68eb77ac372e000c9",
-				"f95e32a5d2dd8da643c4419814415b9704312993", "");
-
-		public static final MavenObject SEL_JAVA = new MavenObject(
-				"selenium-java", "org/seleniumhq/selenium", "selenium-java",
-				"2.28.0", 984098, 0, 0,
-				"7606286989ac9cb942cc206d975ffe187c18d605", "4ede08d293dc153989a337cd0d31d26421433af5", "");
-
-		public static final MavenObject SEL_API = new MavenObject(
-				"selenium-api", "org/seleniumhq/selenium", "selenium-api",
-				"2.28.0", 984098, 0, 0,
-				"c4044c40fff65cd25135a5f443638a2b1ccaeac5", "35fc6ec0804ae32b16a56627e69bdcb69995c515", "");
-
-		public static final MavenObject SEL_REMOTE = new MavenObject(
-				"selenium-remote-driver", "org/seleniumhq/selenium",
-				"selenium-remote-driver", "2.28.0", 984098, 0, 0,
-				"c67f97cd94e02afec92b0ac881844febb4fc90be", "51a9c30de3c8c203cb7a474a10842443005a5fb4", "");
-		public static final MavenObject SEL_SUPPORT = new MavenObject(
-				"selenium-support", "org/seleniumhq/selenium",
-				"selenium-support", "2.28.0", 984098, 0, 0,
-				"caf68d6310425f583bc592c08e43066b35eb94f6", "ce3831a601f5f50fda2f4604decde409b6c735a7", "");
-		public static final MavenObject SEL_FF = new MavenObject(
-				"selenium-firefox-driver", "org/seleniumhq/selenium",
-				"selenium-firefox-driver", "2.28.0", 984098, 0, 0,
-				"a7c34e45dba39e65467b900aa67611aaa039692d", "aa8cd5fb49ca75a53d5b143406ea3d81ab3eddfd", "");
-
-		public static final MavenObject GUAVA = new MavenObject("guava",
-				"com/google/guava", "guava", "12.0", 984098, 0, 0,
-				"5bc66dd95b79db1e437eb08adba124a3e4088dc0", "f8b98e61865bed3c39b978ee3bf5c7fb990c4032", "");
-
-		public static final MavenObject JSON = new MavenObject("json",
-				"org/json", "json", "20080701", 984098, 0, 0,
-				"d652f102185530c93b66158b1859f35d45687258", "71bd54221e701df9d112bf9ba2918e13b0671f3a", "");
-
-		public static final MavenObject COMMONS_EXEC = new MavenObject(
-				"commons-exec", "org/apache/commons", "commons-exec", "1.1",
-				984098, 0, 0, "07dfdf16fade726000564386825ed6d911a44ba1", "f60bea898e18b308099862e8634d589b06a8b0be",
-				"");
-
-		public static final MavenObject HTTPCORE = new MavenObject("httpcore",
-				"org/apache/httpcomponents", "httpcore", "4.2.1", 984098, 0, 0,
-				"2d503272bf0a8b5f92d64db78b4ba9abbaccc6fd", "3f6caf5334fa83607b82e2f32dd128a9d8a0ea5e", "");
-		
-		public static final MavenObject HTTPMIME = new MavenObject("httpmime",
-				"org/apache/httpcomponents", "httpmime", "4.2.1", 984098, 0, 0,
-				"7c772bace9aa31a728c39a88c6ff66a7cd177e89", "", "4e453843ae47f1c2d70e2eb2c13c037de4b614c4");
-		
-		public static final MavenObject HTTPCLIENT = new MavenObject(
-				"httpclient", "org/apache/httpcomponents", "httpclient",
-				"4.2.1", 984098, 0, 0,
-				"b69bd03af60bf487b3ae1209a644ecac587bf6fc", "6b27312b9c28b59aaeb6c21f3490045690c703d3", "");
-		public static final MavenObject COMMONS_LOGGING = new MavenObject(
-				"commons-logging", "commons-logging", "commons-logging",
-				"1.1.1", 984098, 0, 0,
-				"5043bfebc3db072ed80fbd362e7caf00e885d8ae", "f3f156cbff0e0fb0d64bfce31a352cce4a33bc19", "");
-		
-		public final String name;
-		public final String group;
-		public final String artifact;
-		public final String version;
-		public final int approxLibraryLen;
-		public final int approxSourcesLen;
-		public final int approxJavadocLen;
-		public final String librarySHA1;
-		public final String sourcesSHA1;
-		public final String javadocSHA1;
-
-		private MavenObject(String name, String group, String artifact, String version,
-				int approxLibraryLen, int approxSourcesLen, int approxJavadocLen,
-				String librarySHA1, String sourcesSHA1, String javadocSHA1) {
-			this.name = name;
-			this.group = group;
-			this.artifact = artifact;
-			this.version = version;
-			this.approxLibraryLen = approxLibraryLen;
-			this.approxSourcesLen = approxSourcesLen;
-			this.approxJavadocLen = approxJavadocLen;
-			this.librarySHA1 = librarySHA1;
-			this.sourcesSHA1 = sourcesSHA1;
-			this.javadocSHA1 = javadocSHA1;
-		}
-
-		private String getRepositoryPath(String jar) {
-			return group + "/" + artifact + "/" + version + "/" + artifact + "-" + version + jar
-					+ ".jar";
-		}
-
-		private File getLocalFile(String basePath, String jar) {
-			return new File(basePath, artifact + "-" + version + jar + ".jar");
-		}
-
-		private String getSHA1(String jar) {
-			if (jar.equals("")) {
-				return librarySHA1;
-			} else if (jar.equals("-sources")) {
-				return sourcesSHA1;
-			} else if (jar.equals("-javadoc")) {
-				return javadocSHA1;
-			}
-			return librarySHA1;
-		}
-
-		private int getApproximateLength(String jar) {
-			if (jar.equals("")) {
-				return approxLibraryLen;
-			} else if (jar.equals("-sources")) {
-				return approxSourcesLen;
-			} else if (jar.equals("-javadoc")) {
-				return approxJavadocLen;
-			}
-			return approxLibraryLen;
-		}
-
-		@Override
-		public String toString() {
-			return name;
-		}
-	}
-}
diff --git a/src/com/gitblit/build/BuildGhPages.java b/src/com/gitblit/build/BuildGhPages.java
deleted file mode 100644
index 5982ac3..0000000
--- a/src/com/gitblit/build/BuildGhPages.java
+++ /dev/null
@@ -1,259 +0,0 @@
-/*
- * 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.build;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.text.MessageFormat;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Set;
-import java.util.TreeSet;
-
-import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException;
-import org.eclipse.jgit.api.errors.JGitInternalException;
-import org.eclipse.jgit.dircache.DirCache;
-import org.eclipse.jgit.dircache.DirCacheBuilder;
-import org.eclipse.jgit.dircache.DirCacheEntry;
-import org.eclipse.jgit.internal.JGitText;
-import org.eclipse.jgit.lib.CommitBuilder;
-import org.eclipse.jgit.lib.Constants;
-import org.eclipse.jgit.lib.FileMode;
-import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.ObjectInserter;
-import org.eclipse.jgit.lib.PersonIdent;
-import org.eclipse.jgit.lib.RefUpdate;
-import org.eclipse.jgit.lib.RefUpdate.Result;
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.lib.RepositoryCache.FileKey;
-import org.eclipse.jgit.revwalk.RevCommit;
-import org.eclipse.jgit.revwalk.RevWalk;
-import org.eclipse.jgit.storage.file.FileRepository;
-import org.eclipse.jgit.treewalk.CanonicalTreeParser;
-import org.eclipse.jgit.treewalk.TreeWalk;
-import org.eclipse.jgit.util.FS;
-
-import com.beust.jcommander.JCommander;
-import com.beust.jcommander.Parameter;
-import com.beust.jcommander.ParameterException;
-import com.beust.jcommander.Parameters;
-import com.gitblit.models.RefModel;
-import com.gitblit.utils.JGitUtils;
-import com.gitblit.utils.StringUtils;
-
-/**
- * Creates or updates a gh-pages branch with the specified content.
- * 
- * @author James Moger
- * 
- */
-public class BuildGhPages {
-
-	public static void main(String[] args) {
-		Params params = new Params();
-		JCommander jc = new JCommander(params);
-		try {
-			jc.parse(args);
-		} catch (ParameterException t) {
-			System.err.println(t.getMessage());
-			jc.usage();
-		}
-
-		File source = new File(params.sourceFolder);
-		String ghpages = "refs/heads/gh-pages";
-		try {			
-			File gitDir = FileKey.resolve(new File(params.repositoryFolder), FS.DETECTED);
-			Repository repository = new FileRepository(gitDir);
-
-			RefModel issuesBranch = JGitUtils.getPagesBranch(repository);
-			if (issuesBranch == null) {
-				JGitUtils.createOrphanBranch(repository, "gh-pages", null);
-			}
-
-			System.out.println("Updating gh-pages branch...");
-			ObjectId headId = repository.resolve(ghpages + "^{commit}");
-			ObjectInserter odi = repository.newObjectInserter();
-			try {
-				// Create the in-memory index of the new/updated issue.
-				DirCache index = createIndex(repository, headId, source, params.obliterate);
-				ObjectId indexTreeId = index.writeTree(odi);
-
-				// Create a commit object
-				PersonIdent author = new PersonIdent("Gitblit", "gitblit@localhost");
-				CommitBuilder commit = new CommitBuilder();
-				commit.setAuthor(author);
-				commit.setCommitter(author);
-				commit.setEncoding(Constants.CHARACTER_ENCODING);
-				commit.setMessage("updated pages");
-				commit.setParentId(headId);
-				commit.setTreeId(indexTreeId);
-
-				// Insert the commit into the repository
-				ObjectId commitId = odi.insert(commit);
-				odi.flush();
-
-				RevWalk revWalk = new RevWalk(repository);
-				try {
-					RevCommit revCommit = revWalk.parseCommit(commitId);
-					RefUpdate ru = repository.updateRef(ghpages);
-					ru.setNewObjectId(commitId);
-					ru.setExpectedOldObjectId(headId);
-					ru.setRefLogMessage("commit: " + revCommit.getShortMessage(), false);
-					Result rc = ru.forceUpdate();
-					switch (rc) {
-					case NEW:
-					case FORCED:
-					case FAST_FORWARD:
-						break;
-					case REJECTED:
-					case LOCK_FAILURE:
-						throw new ConcurrentRefUpdateException(JGitText.get().couldNotLockHEAD,
-								ru.getRef(), rc);
-					default:
-						throw new JGitInternalException(MessageFormat.format(
-								JGitText.get().updatingRefFailed, ghpages, commitId.toString(), rc));
-					}
-				} finally {
-					revWalk.release();
-				}
-			} finally {
-				odi.release();
-			}
-			System.out.println("gh-pages updated.");
-		} catch (Throwable t) {
-			t.printStackTrace();
-		}
-	}
-
-	/**
-	 * Creates an in-memory index of the issue change.
-	 * 
-	 * @param repo
-	 * @param headId
-	 * @param sourceFolder
-	 * @param obliterate
-	 *            if true the source folder tree is used as the new tree for
-	 *            gh-pages and non-existent files are considered deleted
-	 * @return an in-memory index
-	 * @throws IOException
-	 */
-	private static DirCache createIndex(Repository repo, ObjectId headId, File sourceFolder,
-			boolean obliterate) throws IOException {
-
-		DirCache inCoreIndex = DirCache.newInCore();
-		DirCacheBuilder dcBuilder = inCoreIndex.builder();
-		ObjectInserter inserter = repo.newObjectInserter();
-
-		try {
-			// Add all files to the temporary index
-			Set<String> ignorePaths = new TreeSet<String>();
-			List<File> files = listFiles(sourceFolder);
-			for (File file : files) {
-				// create an index entry for the file
-				final DirCacheEntry dcEntry = new DirCacheEntry(StringUtils.getRelativePath(
-						sourceFolder.getPath(), file.getPath()));
-				dcEntry.setLength(file.length());
-				dcEntry.setLastModified(file.lastModified());
-				dcEntry.setFileMode(FileMode.REGULAR_FILE);
-
-				// add this entry to the ignore paths set
-				ignorePaths.add(dcEntry.getPathString());
-
-				// insert object
-				InputStream inputStream = new FileInputStream(file);
-				try {
-					dcEntry.setObjectId(inserter.insert(Constants.OBJ_BLOB, file.length(),
-							inputStream));
-				} finally {
-					inputStream.close();
-				}
-
-				// add to temporary in-core index
-				dcBuilder.add(dcEntry);
-			}
-
-			if (!obliterate) {
-				// Traverse HEAD to add all other paths
-				TreeWalk treeWalk = new TreeWalk(repo);
-				int hIdx = -1;
-				if (headId != null)
-					hIdx = treeWalk.addTree(new RevWalk(repo).parseTree(headId));
-				treeWalk.setRecursive(true);
-
-				while (treeWalk.next()) {
-					String path = treeWalk.getPathString();
-					CanonicalTreeParser hTree = null;
-					if (hIdx != -1)
-						hTree = treeWalk.getTree(hIdx, CanonicalTreeParser.class);
-					if (!ignorePaths.contains(path)) {
-						// add entries from HEAD for all other paths
-						if (hTree != null) {
-							// create a new DirCacheEntry with data retrieved
-							// from
-							// HEAD
-							final DirCacheEntry dcEntry = new DirCacheEntry(path);
-							dcEntry.setObjectId(hTree.getEntryObjectId());
-							dcEntry.setFileMode(hTree.getEntryFileMode());
-
-							// add to temporary in-core index
-							dcBuilder.add(dcEntry);
-						}
-					}
-				}
-
-				// release the treewalk
-				treeWalk.release();
-			}
-			
-			// finish temporary in-core index used for this commit
-			dcBuilder.finish();
-		} finally {
-			inserter.release();
-		}
-		return inCoreIndex;
-	}
-
-	private static List<File> listFiles(File folder) {
-		List<File> files = new ArrayList<File>();
-		for (File file : folder.listFiles()) {
-			if (file.isDirectory()) {
-				files.addAll(listFiles(file));
-			} else {
-				files.add(file);
-			}
-		}
-		return files;
-	}
-
-	/**
-	 * JCommander Parameters class for BuildGhPages.
-	 */
-	@Parameters(separators = " ")
-	private static class Params {
-
-		@Parameter(names = { "--sourceFolder" }, description = "Source folder for pages", required = true)
-		public String sourceFolder;
-
-		@Parameter(names = { "--repository" }, description = "Repository folder", required = true)
-		public String repositoryFolder;
-
-		@Parameter(names = { "--obliterate" }, description = "Replace gh-pages tree with only the content in your sourcefolder")
-		public boolean obliterate;
-
-	}
-}
diff --git a/src/com/gitblit/build/BuildSite.java b/src/com/gitblit/build/BuildSite.java
deleted file mode 100644
index efff5a3..0000000
--- a/src/com/gitblit/build/BuildSite.java
+++ /dev/null
@@ -1,385 +0,0 @@
-/*
- * 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.build;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.FileReader;
-import java.io.FilenameFilter;
-import java.io.OutputStreamWriter;
-import java.nio.charset.Charset;
-import java.text.MessageFormat;
-import java.text.ParseException;
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Vector;
-
-import com.beust.jcommander.JCommander;
-import com.beust.jcommander.Parameter;
-import com.beust.jcommander.ParameterException;
-import com.beust.jcommander.Parameters;
-import com.gitblit.Constants;
-import com.gitblit.utils.FileUtils;
-import com.gitblit.utils.MarkdownUtils;
-import com.gitblit.utils.StringUtils;
-
-/**
- * Builds the web site or deployment documentation from Markdown source files.
- * 
- * All Markdown source files must have the .mkd extension.
- * 
- * Natural string sort order of the Markdown source filenames is the order of
- * page links. "##_" prefixes are used to control the sort order.
- * 
- * @author James Moger
- * 
- */
-public class BuildSite {
-
-	private static final String SPACE_DELIMITED = "SPACE-DELIMITED";
-
-	private static final String CASE_SENSITIVE = "CASE-SENSITIVE";
-
-	private static final String RESTART_REQUIRED = "RESTART REQUIRED";
-
-	private static final String SINCE = "SINCE";
-
-	public static void main(String... args) {
-		Params params = new Params();
-		JCommander jc = new JCommander(params);
-		try {
-			jc.parse(args);
-		} catch (ParameterException t) {
-			usage(jc, t);
-		}
-
-		File sourceFolder = new File(params.sourceFolder);
-		File destinationFolder = new File(params.outputFolder);
-		File[] markdownFiles = sourceFolder.listFiles(new FilenameFilter() {
-
-			@Override
-			public boolean accept(File dir, String name) {
-				return name.toLowerCase().endsWith(".mkd");
-			}
-		});
-		Arrays.sort(markdownFiles);
-
-		Map<String, String> aliasMap = new HashMap<String, String>();
-		for (String alias : params.aliases) {
-			String[] values = alias.split("=");
-			aliasMap.put(values[0], values[1]);
-		}
-
-		System.out.println(MessageFormat.format("Generating site from {0} Markdown Docs in {1} ",
-				markdownFiles.length, sourceFolder.getAbsolutePath()));
-
-		String htmlHeader = FileUtils.readContent(new File(params.pageHeader), "\n");
-
-		String htmlAdSnippet = null;
-		if (!StringUtils.isEmpty(params.adSnippet)) {
-			File snippet = new File(params.adSnippet);
-			if (snippet.exists()) {
-				htmlAdSnippet = FileUtils.readContent(snippet, "\n");
-			}
-		}
-		String htmlFooter = FileUtils.readContent(new File(params.pageFooter), "\n");
-		final String date = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
-		final String footer = MessageFormat.format(htmlFooter, "generated " + date);
-		for (File file : markdownFiles) {
-			String documentName = getDocumentName(file);
-			if (params.skips.contains(documentName)) {
-				continue;
-			}
-			try {
-				String links = createLinks(file, markdownFiles, aliasMap, params.skips);
-				String header = MessageFormat.format(htmlHeader, Constants.FULL_NAME, links);
-				if (!StringUtils.isEmpty(params.analyticsSnippet)) {
-					File snippet = new File(params.analyticsSnippet);
-					if (snippet.exists()) {
-						String htmlSnippet = FileUtils.readContent(snippet, "\n");
-						header = header.replace("<!-- ANALYTICS -->", htmlSnippet);
-					}
-				}
-
-				String fileName = documentName + ".html";
-				System.out.println(MessageFormat.format("  {0} => {1}", file.getName(), fileName));
-				String rawContent = FileUtils.readContent(file, "\n");
-				String markdownContent = rawContent;
-
-				Map<String, List<String>> nomarkdownMap = new HashMap<String, List<String>>();
-
-				// extract sections marked as no-markdown
-				int nmd = 0;
-				for (String token : params.nomarkdown) {
-					StringBuilder strippedContent = new StringBuilder();
-
-					String nomarkdownKey = "%NOMARKDOWN" + nmd + "%";
-					String[] kv = token.split(":", 2);
-					String beginToken = kv[0];
-					String endToken = kv[1];
-
-					// strip nomarkdown chunks from markdown and cache them
-					List<String> chunks = new Vector<String>();
-					int beginCode = 0;
-					int endCode = 0;
-					while ((beginCode = markdownContent.indexOf(beginToken, endCode)) > -1) {
-						if (endCode == 0) {
-							strippedContent.append(markdownContent.substring(0, beginCode));
-						} else {
-							strippedContent.append(markdownContent.substring(endCode, beginCode));
-						}
-						strippedContent.append(nomarkdownKey);
-						endCode = markdownContent.indexOf(endToken, beginCode);
-						chunks.add(markdownContent.substring(beginCode, endCode));
-						nomarkdownMap.put(nomarkdownKey, chunks);
-					}
-
-					// get remainder of text
-					if (endCode < markdownContent.length()) {
-						strippedContent.append(markdownContent.substring(endCode,
-								markdownContent.length()));
-					}
-					markdownContent = strippedContent.toString();
-					nmd++;
-				}
-
-				// transform markdown to html
-				String content = transformMarkdown(markdownContent.toString());
-
-				// reinsert nomarkdown chunks
-				for (Map.Entry<String, List<String>> nomarkdown : nomarkdownMap.entrySet()) {
-					for (String chunk : nomarkdown.getValue()) {
-						content = content.replaceFirst(nomarkdown.getKey(), chunk);
-					}
-				}
-
-				for (String token : params.substitutions) {
-					String[] kv = token.split("=", 2);
-					content = content.replace(kv[0], kv[1]);
-				}
-				for (String token : params.regex) {
-					String[] kv = token.split("!!!", 2);
-					content = content.replaceAll(kv[0], kv[1]);
-				}
-				for (String alias : params.properties) {
-					String[] kv = alias.split("=", 2);
-					String loadedContent = generatePropertiesContent(new File(kv[1]));
-					content = content.replace(kv[0], loadedContent);
-				}
-				for (String alias : params.loads) {
-					String[] kv = alias.split("=", 2);
-					String loadedContent = FileUtils.readContent(new File(kv[1]), "\n");
-					loadedContent = StringUtils.escapeForHtml(loadedContent, false);
-					loadedContent = StringUtils.breakLinesForHtml(loadedContent);
-					content = content.replace(kv[0], loadedContent);
-				}
-				OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(new File(
-						destinationFolder, fileName)), Charset.forName("UTF-8"));
-				writer.write(header);
-				if (!StringUtils.isEmpty(htmlAdSnippet)) {
-					writer.write(htmlAdSnippet);
-				}
-				writer.write(content);
-				writer.write(footer);
-				writer.close();
-			} catch (Throwable t) {
-				System.err.println("Failed to transform " + file.getName());
-				t.printStackTrace();
-			}
-		}
-	}
-
-	private static String getDocumentName(File file) {
-		String displayName = file.getName().substring(0, file.getName().lastIndexOf('.'))
-				.toLowerCase();
-		int underscore = displayName.indexOf('_') + 1;
-		if (underscore > -1) {
-			// trim leading ##_ which is to control display order
-			return displayName.substring(underscore);
-		}
-		return displayName;
-	}
-
-	private static String createLinks(File currentFile, File[] markdownFiles,
-			Map<String, String> aliasMap, List<String> skips) {
-		String linkPattern = "<li><a href=''{0}''>{1}</a></li>";
-		String currentLinkPattern = "<li class=''active''><a href=''{0}''>{1}</a></li>";
-		StringBuilder sb = new StringBuilder();
-		for (File file : markdownFiles) {
-			String documentName = getDocumentName(file);
-			if (!skips.contains(documentName)) {
-				String displayName = documentName;
-				if (aliasMap.containsKey(documentName)) {
-					displayName = aliasMap.get(documentName);
-				} else {
-					displayName = displayName.replace('_', ' ');
-				}
-				String fileName = documentName + ".html";
-				if (currentFile.getName().equals(file.getName())) {
-					sb.append(MessageFormat.format(currentLinkPattern, fileName, displayName));
-				} else {
-					sb.append(MessageFormat.format(linkPattern, fileName, displayName));
-				}
-			}
-		}
-		sb.setLength(sb.length() - 3);
-		sb.trimToSize();
-		return sb.toString();
-	}
-
-	private static String generatePropertiesContent(File propertiesFile) throws Exception {
-		// Read the current Gitblit properties
-		BufferedReader propertiesReader = new BufferedReader(new FileReader(propertiesFile));
-
-		Vector<Setting> settings = new Vector<Setting>();
-		List<String> comments = new ArrayList<String>();
-		String line = null;
-		while ((line = propertiesReader.readLine()) != null) {
-			if (line.length() == 0) {
-				Setting s = new Setting("", "", comments);
-				settings.add(s);
-				comments.clear();
-			} else {
-				if (line.charAt(0) == '#') {
-					comments.add(line.substring(1).trim());
-				} else {
-					String[] kvp = line.split("=", 2);
-					String key = kvp[0].trim();
-					Setting s = new Setting(key, kvp[1].trim(), comments);
-					settings.add(s);
-					comments.clear();
-				}
-			}
-		}
-		propertiesReader.close();
-
-		StringBuilder sb = new StringBuilder();
-		for (Setting setting : settings) {
-			for (String comment : setting.comments) {
-				if (comment.contains(SINCE) || comment.contains(RESTART_REQUIRED)
-						|| comment.contains(CASE_SENSITIVE) || comment.contains(SPACE_DELIMITED)) {
-					sb.append(MessageFormat.format(
-							"<span style=\"color:#004000;\"># <i>{0}</i></span>",
-							transformMarkdown(comment)));
-				} else {
-					sb.append(MessageFormat.format("<span style=\"color:#004000;\"># {0}</span>",
-							transformMarkdown(comment)));
-				}
-				sb.append("<br/>\n");
-			}
-			if (!StringUtils.isEmpty(setting.name)) {
-				sb.append(MessageFormat
-						.format("<span style=\"color:#000080;\">{0}</span> = <span style=\"color:#800000;\">{1}</span>",
-								setting.name, StringUtils.escapeForHtml(setting.value, false)));
-			}
-			sb.append("<br/>\n");
-		}
-
-		return sb.toString();
-	}
-
-	private static String transformMarkdown(String comment) throws ParseException {
-		String md = MarkdownUtils.transformMarkdown(comment);
-		if (md.startsWith("<p>")) {
-			md = md.substring(3);
-		}
-		if (md.endsWith("</p>")) {
-			md = md.substring(0, md.length() - 4);
-		}
-		return md;
-	}
-
-	private static void usage(JCommander jc, ParameterException t) {
-		System.out.println(Constants.getGitBlitVersion());
-		System.out.println();
-		if (t != null) {
-			System.out.println(t.getMessage());
-			System.out.println();
-		}
-		if (jc != null) {
-			jc.usage();
-		}
-		System.exit(0);
-	}
-
-	/**
-	 * Setting represents a setting with its comments from the properties file.
-	 */
-	private static class Setting {
-		final String name;
-		final String value;
-		final List<String> comments;
-
-		Setting(String name, String value, List<String> comments) {
-			this.name = name;
-			this.value = value;
-			this.comments = new ArrayList<String>(comments);
-		}
-	}
-
-	/**
-	 * JCommander Parameters class for BuildSite.
-	 */
-	@Parameters(separators = " ")
-	private static class Params {
-
-		@Parameter(names = { "--sourceFolder" }, description = "Markdown Source Folder", required = true)
-		public String sourceFolder;
-
-		@Parameter(names = { "--outputFolder" }, description = "HTML Ouptut Folder", required = true)
-		public String outputFolder;
-
-		@Parameter(names = { "--pageHeader" }, description = "Page Header HTML Snippet", required = true)
-		public String pageHeader;
-
-		@Parameter(names = { "--pageFooter" }, description = "Page Footer HTML Snippet", required = true)
-		public String pageFooter;
-
-		@Parameter(names = { "--adSnippet" }, description = "Ad HTML Snippet", required = false)
-		public String adSnippet;
-
-		@Parameter(names = { "--analyticsSnippet" }, description = "Analytics HTML Snippet", required = false)
-		public String analyticsSnippet;
-
-		@Parameter(names = { "--skip" }, description = "Filename to skip", required = false)
-		public List<String> skips = new ArrayList<String>();
-
-		@Parameter(names = { "--alias" }, description = "Filename=Linkname aliases", required = false)
-		public List<String> aliases = new ArrayList<String>();
-
-		@Parameter(names = { "--substitute" }, description = "%TOKEN%=value", required = false)
-		public List<String> substitutions = new ArrayList<String>();
-
-		@Parameter(names = { "--load" }, description = "%TOKEN%=filename", required = false)
-		public List<String> loads = new ArrayList<String>();
-
-		@Parameter(names = { "--properties" }, description = "%TOKEN%=filename", required = false)
-		public List<String> properties = new ArrayList<String>();
-
-		@Parameter(names = { "--nomarkdown" }, description = "%STARTTOKEN%:%ENDTOKEN%", required = false)
-		public List<String> nomarkdown = new ArrayList<String>();
-
-		@Parameter(names = { "--regex" }, description = "searchPattern!!!replacePattern", required = false)
-		public List<String> regex = new ArrayList<String>();
-
-	}
-}
diff --git a/src/com/gitblit/build/BuildThumbnails.java b/src/com/gitblit/build/BuildThumbnails.java
deleted file mode 100644
index fe06c6c..0000000
--- a/src/com/gitblit/build/BuildThumbnails.java
+++ /dev/null
@@ -1,162 +0,0 @@
-/*
- * 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.build;
-
-import java.awt.Dimension;
-import java.awt.Image;
-import java.awt.image.BufferedImage;
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.FilenameFilter;
-import java.io.IOException;
-import java.text.MessageFormat;
-import java.util.Iterator;
-
-import javax.imageio.ImageIO;
-import javax.imageio.ImageReader;
-import javax.imageio.stream.ImageInputStream;
-
-import com.beust.jcommander.JCommander;
-import com.beust.jcommander.Parameter;
-import com.beust.jcommander.ParameterException;
-import com.beust.jcommander.Parameters;
-
-/**
- * Generates PNG thumbnails of the PNG images from the specified source folder.
- * 
- * @author James Moger
- * 
- */
-public class BuildThumbnails {
-
-	public static void main(String[] args) {
-		Params params = new Params();
-		JCommander jc = new JCommander(params);
-		try {
-			jc.parse(args);
-		} catch (ParameterException t) {
-			System.err.println(t.getMessage());
-			jc.usage();
-		}
-		createImageThumbnail(params.sourceFolder, params.destinationFolder, params.maximumDimension);
-	}
-
-	/**
-	 * Generates thumbnails from all PNG images in the source folder and saves
-	 * them to the destination folder.
-	 * 
-	 * @param sourceFolder
-	 * @param destinationFolder
-	 * @param maxDimension
-	 *            the maximum height or width of the image.
-	 */
-	public static void createImageThumbnail(String sourceFolder, String destinationFolder,
-			int maxDimension) {
-		if (maxDimension <= 0) {
-			return;
-		}
-		File source = new File(sourceFolder);
-		File destination = new File(destinationFolder);
-		destination.mkdirs();
-		File[] sourceFiles = source.listFiles(new FilenameFilter() {
-			@Override
-			public boolean accept(File dir, String name) {
-				return name.toLowerCase().endsWith(".png");
-			}
-		});
-
-		for (File sourceFile : sourceFiles) {
-			File destinationFile = new File(destination, sourceFile.getName());
-			try {
-				Dimension sz = getImageDimensions(sourceFile);
-				int w = 0;
-				int h = 0;
-				if (sz.width > maxDimension) {
-					// Scale to Width
-					w = maxDimension;
-					float f = maxDimension;
-					// normalize height
-					h = (int) ((f / sz.width) * sz.height);
-				} else if (sz.height > maxDimension) {
-					// Scale to Height
-					h = maxDimension;
-					float f = maxDimension;
-					// normalize width
-					w = (int) ((f / sz.height) * sz.width);
-				}
-				System.out.println(MessageFormat.format(
-						"Generating thumbnail for {0} as ({1,number,#}, {2,number,#})",
-						sourceFile.getName(), w, h));
-				BufferedImage image = ImageIO.read(sourceFile);
-				Image scaledImage = image.getScaledInstance(w, h, BufferedImage.SCALE_SMOOTH);
-				BufferedImage destImage = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
-				destImage.createGraphics().drawImage(scaledImage, 0, 0, null);
-				FileOutputStream fos = new FileOutputStream(destinationFile);
-				ImageIO.write(destImage, "png", fos);
-				fos.flush();
-				fos.getFD().sync();
-				fos.close();
-			} catch (Throwable t) {
-				t.printStackTrace();
-			}
-		}
-	}
-
-	/**
-	 * Return the dimensions of the specified image file.
-	 * 
-	 * @param file
-	 * @return dimensions of the image
-	 * @throws IOException
-	 */
-	public static Dimension getImageDimensions(File file) throws IOException {
-		ImageInputStream in = ImageIO.createImageInputStream(file);
-		try {
-			final Iterator<ImageReader> readers = ImageIO.getImageReaders(in);
-			if (readers.hasNext()) {
-				ImageReader reader = readers.next();
-				try {
-					reader.setInput(in);
-					return new Dimension(reader.getWidth(0), reader.getHeight(0));
-				} finally {
-					reader.dispose();
-				}
-			}
-		} finally {
-			if (in != null) {
-				in.close();
-			}
-		}
-		return null;
-	}
-
-	/**
-	 * JCommander Parameters class for BuildThumbnails.
-	 */
-	@Parameters(separators = " ")
-	private static class Params {
-
-		@Parameter(names = { "--sourceFolder" }, description = "Source folder for raw images", required = true)
-		public String sourceFolder;
-
-		@Parameter(names = { "--destinationFolder" }, description = "Destination folder for thumbnails", required = true)
-		public String destinationFolder;
-
-		@Parameter(names = { "--maximumDimension" }, description = "Maximum width or height for thumbnail", required = true)
-		public int maximumDimension;
-
-	}
-}
diff --git a/src/com/gitblit/build/BuildWebXml.java b/src/com/gitblit/build/BuildWebXml.java
deleted file mode 100644
index 49a12ab..0000000
--- a/src/com/gitblit/build/BuildWebXml.java
+++ /dev/null
@@ -1,160 +0,0 @@
-/*
- * 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.build;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.FileReader;
-import java.text.MessageFormat;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Vector;
-
-import com.beust.jcommander.JCommander;
-import com.beust.jcommander.Parameter;
-import com.beust.jcommander.ParameterException;
-import com.beust.jcommander.Parameters;
-import com.gitblit.Keys;
-import com.gitblit.utils.StringUtils;
-
-/**
- * Builds the Gitblit WAR web.xml file by merging the Gitblit GO web.xml file
- * with the gitblit.properties comments, settings, and values.
- * 
- * @author James Moger
- * 
- */
-public class BuildWebXml {
-	private static final String PARAMS = "<!-- PARAMS -->";
-
-	private static final String[] STRIP_TOKENS = { "<!-- STRIP", "STRIP -->" };
-
-	private static final String COMMENT_PATTERN = "\n\t<!-- {0} -->";
-
-	private static final String PARAM_PATTERN = "\n\t<context-param>\n\t\t<param-name>{0}</param-name>\n\t\t<param-value>{1}</param-value>\n\t</context-param>\n";
-
-	public static void main(String[] args) throws Exception {
-		Params params = new Params();
-		JCommander jc = new JCommander(params);
-		try {
-			jc.parse(args);
-		} catch (ParameterException t) {
-			System.err.println(t.getMessage());
-			jc.usage();
-		}
-		generateWebXml(params);
-	}
-
-	private static void generateWebXml(Params params) throws Exception {
-		StringBuilder parameters = new StringBuilder();
-		// Read the current Gitblit properties
-		if (params.propertiesFile != null) {
-			BufferedReader propertiesReader = new BufferedReader(new FileReader(new File(
-					params.propertiesFile)));
-
-			Vector<Setting> settings = new Vector<Setting>();
-			List<String> comments = new ArrayList<String>();
-			String line = null;
-			while ((line = propertiesReader.readLine()) != null) {
-				if (line.length() == 0) {
-					comments.clear();
-				} else {
-					if (line.charAt(0) == '#') {
-						if (line.length() > 1) {
-							comments.add(line.substring(1).trim());
-						}
-					} else {
-						String[] kvp = line.split("=", 2);
-						String key = kvp[0].trim();
-						if (!skipKey(key)) {
-							Setting s = new Setting(key, kvp[1].trim(), comments);
-							settings.add(s);
-						}
-						comments.clear();
-					}
-				}
-			}
-			propertiesReader.close();
-
-			for (Setting setting : settings) {
-				for (String comment : setting.comments) {
-					parameters.append(MessageFormat.format(COMMENT_PATTERN, comment));
-				}
-				parameters.append(MessageFormat.format(PARAM_PATTERN, setting.name,
-						StringUtils.escapeForHtml(setting.value, false)));
-			}
-		}
-		// Read the prototype web.xml file
-		File webxml = new File(params.sourceFile);
-		char[] buffer = new char[(int) webxml.length()];
-		FileReader webxmlReader = new FileReader(webxml);
-		webxmlReader.read(buffer);
-		webxmlReader.close();
-		String webXmlContent = new String(buffer);
-
-		// Insert the Gitblit properties into the prototype web.xml
-		for (String stripToken : STRIP_TOKENS) {
-			webXmlContent = webXmlContent.replace(stripToken, "");
-		}
-		int idx = webXmlContent.indexOf(PARAMS);
-		StringBuilder sb = new StringBuilder();
-		sb.append(webXmlContent.substring(0, idx));
-		sb.append(parameters.toString());
-		sb.append(webXmlContent.substring(idx + PARAMS.length()));
-
-		// Save the merged web.xml to the war build folder
-		FileOutputStream os = new FileOutputStream(new File(params.destinationFile), false);
-		os.write(sb.toString().getBytes());
-		os.close();
-	}
-
-	private static boolean skipKey(String key) {
-		return key.startsWith(Keys.server._ROOT);
-	}
-
-	/**
-	 * Setting represents a setting and its comments from the properties file.
-	 */
-	private static class Setting {
-		final String name;
-		final String value;
-		final List<String> comments;
-
-		Setting(String name, String value, List<String> comments) {
-			this.name = name;
-			this.value = value;
-			this.comments = new ArrayList<String>(comments);
-		}
-	}
-
-	/**
-	 * JCommander Parameters class for BuildWebXml.
-	 */
-	@Parameters(separators = " ")
-	private static class Params {
-
-		@Parameter(names = { "--sourceFile" }, description = "Source web.xml file", required = true)
-		public String sourceFile;
-
-		@Parameter(names = { "--propertiesFile" }, description = "Properties settings file")
-		public String propertiesFile;
-
-		@Parameter(names = { "--destinationFile" }, description = "Destination web.xml file", required = true)
-		public String destinationFile;
-		
-	}
-}
diff --git a/src/com/gitblit/client/BranchRenderer.java b/src/com/gitblit/client/BranchRenderer.java
deleted file mode 100644
index 9a303c3..0000000
--- a/src/com/gitblit/client/BranchRenderer.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * 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.client;
-
-import java.awt.Color;
-import java.awt.Component;
-
-import javax.swing.JList;
-import javax.swing.JTable;
-import javax.swing.ListCellRenderer;
-import javax.swing.table.DefaultTableCellRenderer;
-
-/**
- * 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 {
-
-	private static final long serialVersionUID = 1L;
-
-	private static final String R_HEADS = "refs/heads/";
-
-	private static final String R_REMOTES = "refs/remotes/";
-
-	public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
-			boolean hasFocus, int row, int column) {
-		super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
-		setText(value == null ? "" : value.toString());
-		if (isSelected) {
-			setForeground(table.getSelectionForeground());
-		}
-		return this;
-	}
-
-	@Override
-	public Component getListCellRendererComponent(JList list, Object value, int index,
-			boolean isSelected, boolean cellHasFocus) {
-		setText(value == null ? "" : value.toString());
-		if (isSelected) {
-			setBackground(list.getSelectionBackground());
-			setForeground(list.getSelectionForeground());
-		} else {
-			setBackground(list.getBackground());
-		}
-		return this;
-	}
-
-	@Override
-	public void setText(String text) {
-		String name = text;
-		Color fg = getForeground();
-		if (name.startsWith(R_HEADS)) {
-			name = name.substring(R_HEADS.length());
-			fg = new Color(0, 0x80, 0);
-		} else if (name.startsWith(R_REMOTES)) {
-			name = name.substring(R_REMOTES.length());
-			fg = Color.decode("#6C6CBF");
-		}
-		setForeground(fg);
-		super.setText(name);
-	}
-}
\ No newline at end of file
diff --git a/src/com/gitblit/client/EditRepositoryDialog.java b/src/com/gitblit/client/EditRepositoryDialog.java
deleted file mode 100644
index 8851de4..0000000
--- a/src/com/gitblit/client/EditRepositoryDialog.java
+++ /dev/null
@@ -1,804 +0,0 @@
-/*
- * 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.client;
-
-import java.awt.BorderLayout;
-import java.awt.Component;
-import java.awt.Dimension;
-import java.awt.FlowLayout;
-import java.awt.Font;
-import java.awt.GridLayout;
-import java.awt.Insets;
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
-import java.awt.event.ItemEvent;
-import java.awt.event.ItemListener;
-import java.awt.event.KeyEvent;
-import java.text.MessageFormat;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import javax.swing.BoxLayout;
-import javax.swing.ButtonGroup;
-import javax.swing.DefaultListCellRenderer;
-import javax.swing.ImageIcon;
-import javax.swing.JButton;
-import javax.swing.JCheckBox;
-import javax.swing.JComboBox;
-import javax.swing.JComponent;
-import javax.swing.JDialog;
-import javax.swing.JLabel;
-import javax.swing.JList;
-import javax.swing.JOptionPane;
-import javax.swing.JPanel;
-import javax.swing.JRadioButton;
-import javax.swing.JRootPane;
-import javax.swing.JScrollPane;
-import javax.swing.JTabbedPane;
-import javax.swing.JTextField;
-import javax.swing.KeyStroke;
-import javax.swing.ListCellRenderer;
-import javax.swing.ScrollPaneConstants;
-
-import com.gitblit.Constants.AccessRestrictionType;
-import com.gitblit.Constants.AuthorizationControl;
-import com.gitblit.Constants.FederationStrategy;
-import com.gitblit.Constants.RegistrantType;
-import com.gitblit.models.RegistrantAccessPermission;
-import com.gitblit.models.RepositoryModel;
-import com.gitblit.utils.ArrayUtils;
-import com.gitblit.utils.StringUtils;
-
-/**
- * Dialog to create/edit a repository.
- * 
- * @author James Moger
- */
-public class EditRepositoryDialog extends JDialog {
-
-	private static final long serialVersionUID = 1L;
-
-	private final String repositoryName;
-
-	private final RepositoryModel repository;
-
-	private boolean isCreate;
-
-	private boolean canceled = true;
-
-	private JTextField nameField;
-
-	private JTextField descriptionField;
-
-	private JCheckBox useTickets;
-
-	private JCheckBox useDocs;
-
-	private JCheckBox showRemoteBranches;
-
-	private JCheckBox showReadme;
-
-	private JCheckBox skipSizeCalculation;
-
-	private JCheckBox skipSummaryMetrics;
-
-	private JCheckBox isFrozen;
-
-	private JTextField mailingListsField;
-
-	private JComboBox accessRestriction;
-	
-	private JRadioButton allowAuthenticated;
-	
-	private JRadioButton allowNamed;
-	
-	private JCheckBox allowForks;
-
-	private JCheckBox verifyCommitter;
-
-	private JComboBox federationStrategy;
-
-	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;
-
-	private JLabel preReceiveInherited;
-
-	private JPalette<String> postReceivePalette;
-
-	private JLabel postReceiveInherited;
-
-	private Set<String> repositoryNames;
-	
-	private JPanel customFieldsPanel;
-	
-	private List<JTextField> customTextfields;
-
-	public EditRepositoryDialog(int protocolVersion) {
-		this(protocolVersion, new RepositoryModel());
-		this.isCreate = true;
-		setTitle(Translation.get("gb.newRepository"));
-	}
-
-	public EditRepositoryDialog(int protocolVersion, RepositoryModel aRepository) {
-		super();
-		this.repositoryName = aRepository.name;
-		this.repository = new RepositoryModel();
-		this.repositoryNames = new HashSet<String>();
-		this.isCreate = false;
-		initialize(protocolVersion, aRepository);
-		setModal(true);
-		setResizable(false);
-		setTitle(Translation.get("gb.edit") + ": " + aRepository.name);
-		setIconImage(new ImageIcon(getClass()
-				.getResource("/gitblt-favicon.png")).getImage());
-	}
-
-	@Override
-	protected JRootPane createRootPane() {
-		KeyStroke stroke = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0);
-		JRootPane rootPane = new JRootPane();
-		rootPane.registerKeyboardAction(new ActionListener() {
-			public void actionPerformed(ActionEvent actionEvent) {
-				setVisible(false);
-			}
-		}, stroke, JComponent.WHEN_IN_FOCUSED_WINDOW);
-		return rootPane;
-	}
-
-	private void initialize(int protocolVersion, RepositoryModel anRepository) {
-		nameField = new JTextField(anRepository.name == null ? ""
-				: anRepository.name, 35);
-		descriptionField = new JTextField(anRepository.description == null ? ""
-				: anRepository.description, 35);
-
-		JTextField originField = new JTextField(
-				anRepository.origin == null ? "" : anRepository.origin, 40);
-		originField.setEditable(false);
-
-		if (ArrayUtils.isEmpty(anRepository.availableRefs)) {
-			headRefField = new JComboBox();
-			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);
-
-		ownersPalette = new JPalette<String>(true);
-
-		useTickets = new JCheckBox(Translation.get("gb.useTicketsDescription"),
-				anRepository.useTickets);
-		useDocs = new JCheckBox(Translation.get("gb.useDocsDescription"),
-				anRepository.useDocs);
-		showRemoteBranches = new JCheckBox(
-				Translation.get("gb.showRemoteBranchesDescription"),
-				anRepository.showRemoteBranches);
-		showReadme = new JCheckBox(Translation.get("gb.showReadmeDescription"),
-				anRepository.showReadme);
-		skipSizeCalculation = new JCheckBox(
-				Translation.get("gb.skipSizeCalculationDescription"),
-				anRepository.skipSizeCalculation);
-		skipSummaryMetrics = new JCheckBox(
-				Translation.get("gb.skipSummaryMetricsDescription"),
-				anRepository.skipSummaryMetrics);
-		isFrozen = new JCheckBox(Translation.get("gb.isFrozenDescription"),
-				anRepository.isFrozen);
-
-		maxActivityCommits = new JComboBox(new Integer [] { -1, 0, 25, 50, 75, 100, 150, 250, 500 });
-		maxActivityCommits.setSelectedItem(anRepository.maxActivityCommits);
-
-		mailingListsField = new JTextField(
-				ArrayUtils.isEmpty(anRepository.mailingLists) ? ""
-						: StringUtils.flattenStrings(anRepository.mailingLists,
-								" "), 50);
-
-		accessRestriction = new JComboBox(AccessRestrictionType.values());
-		accessRestriction.setRenderer(new AccessRestrictionRenderer());
-		accessRestriction.setSelectedItem(anRepository.accessRestriction);
-		accessRestriction.addItemListener(new ItemListener() {
-			@Override
-			public void itemStateChanged(ItemEvent e) {
-				if (e.getStateChange() == ItemEvent.SELECTED) {
-					AccessRestrictionType art = (AccessRestrictionType) accessRestriction.getSelectedItem();
-					EditRepositoryDialog.this.setupAccessPermissions(art);
-				}
-			}
-		});
-		
-		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) {					
-					usersPalette.setEnabled(false);
-					teamsPalette.setEnabled(false);
-				}
-			}
-		});
-		
-		allowNamed = new JRadioButton(Translation.get("gb.allowNamedDescription"));
-		allowNamed.setSelected(!authenticated);
-		allowNamed.addItemListener(new ItemListener() {
-			@Override
-			public void itemStateChanged(ItemEvent e) {
-				if (e.getStateChange() == ItemEvent.SELECTED) {
-					usersPalette.setEnabled(true);
-					teamsPalette.setEnabled(true);
-				}
-			}
-		});
-		
-		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);
-
-		// federation strategies - remove ORIGIN choice if this repository has
-		// no origin.
-		List<FederationStrategy> federationStrategies = new ArrayList<FederationStrategy>(
-				Arrays.asList(FederationStrategy.values()));
-		if (StringUtils.isEmpty(anRepository.origin)) {
-			federationStrategies.remove(FederationStrategy.FEDERATE_ORIGIN);
-		}
-		federationStrategy = new JComboBox(federationStrategies.toArray());
-		federationStrategy.setRenderer(new FederationStrategyRenderer());
-		federationStrategy.setSelectedItem(anRepository.federationStrategy);
-
-		JPanel fieldsPanel = new JPanel(new GridLayout(0, 1));
-		fieldsPanel.add(newFieldPanel(Translation.get("gb.name"), nameField));
-		fieldsPanel.add(newFieldPanel(Translation.get("gb.description"),
-				descriptionField));
-		fieldsPanel
-				.add(newFieldPanel(Translation.get("gb.origin"), originField));
-		fieldsPanel.add(newFieldPanel(Translation.get("gb.headRef"), headRefField));
-		fieldsPanel.add(newFieldPanel(Translation.get("gb.gcPeriod"), gcPeriod));
-		fieldsPanel.add(newFieldPanel(Translation.get("gb.gcThreshold"), gcThreshold));
-
-		fieldsPanel.add(newFieldPanel(Translation.get("gb.enableTickets"),
-				useTickets));
-		fieldsPanel
-				.add(newFieldPanel(Translation.get("gb.enableDocs"), useDocs));
-		fieldsPanel.add(newFieldPanel(Translation.get("gb.showRemoteBranches"),
-				showRemoteBranches));
-		fieldsPanel.add(newFieldPanel(Translation.get("gb.showReadme"),
-				showReadme));
-		fieldsPanel
-				.add(newFieldPanel(Translation.get("gb.skipSizeCalculation"),
-						skipSizeCalculation));
-		fieldsPanel.add(newFieldPanel(Translation.get("gb.skipSummaryMetrics"),
-				skipSummaryMetrics));
-		fieldsPanel.add(newFieldPanel(Translation.get("gb.maxActivityCommits"),
-				maxActivityCommits));
-		fieldsPanel.add(newFieldPanel(Translation.get("gb.mailingLists"),
-				mailingListsField));
-
-		JPanel clonePushPanel = new JPanel(new GridLayout(0, 1));
-		clonePushPanel
-		.add(newFieldPanel(Translation.get("gb.isFrozen"), isFrozen));
-		clonePushPanel
-		.add(newFieldPanel(Translation.get("gb.allowForks"), allowForks));
-		clonePushPanel
-		.add(newFieldPanel(Translation.get("gb.verifyCommitter"), verifyCommitter));
-
-		usersPalette = new RegistrantPermissionsPanel(RegistrantType.USER);
-
-		JPanel northFieldsPanel = new JPanel(new BorderLayout(0, 5));
-		northFieldsPanel.add(newFieldPanel(Translation.get("gb.owners"), ownersPalette), BorderLayout.NORTH);
-		northFieldsPanel.add(newFieldPanel(Translation.get("gb.accessRestriction"),
-				accessRestriction), BorderLayout.CENTER);
-
-		JPanel northAccessPanel = new JPanel(new BorderLayout(5, 5));
-		northAccessPanel.add(northFieldsPanel, BorderLayout.NORTH);
-		northAccessPanel.add(newFieldPanel(Translation.get("gb.authorizationControl"),
-				authorizationPanel), BorderLayout.CENTER);
-		northAccessPanel.add(clonePushPanel, BorderLayout.SOUTH);
-
-		JPanel accessPanel = new JPanel(new BorderLayout(5, 5));
-		accessPanel.add(northAccessPanel, BorderLayout.NORTH);
-		accessPanel.add(newFieldPanel(Translation.get("gb.userPermissions"),
-						usersPalette), BorderLayout.CENTER);
-
-		teamsPalette = new RegistrantPermissionsPanel(RegistrantType.TEAM);
-		JPanel teamsPanel = new JPanel(new BorderLayout(5, 5));
-		teamsPanel.add(
-				newFieldPanel(Translation.get("gb.teamPermissions"),
-						teamsPalette), BorderLayout.CENTER);
-
-		setsPalette = new JPalette<String>();
-		JPanel federationPanel = new JPanel(new BorderLayout(5, 5));
-		federationPanel.add(
-				newFieldPanel(Translation.get("gb.federationStrategy"),
-						federationStrategy), BorderLayout.NORTH);
-		federationPanel
-				.add(newFieldPanel(Translation.get("gb.federationSets"),
-						setsPalette), BorderLayout.CENTER);
-
-		indexedBranchesPalette = new JPalette<String>();
-		JPanel indexedBranchesPanel = new JPanel(new BorderLayout(5, 5));
-		indexedBranchesPanel
-				.add(newFieldPanel(Translation.get("gb.indexedBranches"),
-						indexedBranchesPalette), BorderLayout.CENTER);
-
-		preReceivePalette = new JPalette<String>(true);
-		preReceiveInherited = new JLabel();
-		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));
-		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);
-		customFieldsScrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
-		customFieldsScrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
-
-		JTabbedPane panel = new JTabbedPane(JTabbedPane.TOP);
-		panel.addTab(Translation.get("gb.general"), fieldsPanel);
-		panel.addTab(Translation.get("gb.accessRestriction"), accessPanel);
-		if (protocolVersion >= 2) {
-			panel.addTab(Translation.get("gb.teams"), teamsPanel);
-		}
-		panel.addTab(Translation.get("gb.federation"), federationPanel);
-		if (protocolVersion >= 3) {
-			panel.addTab(Translation.get("gb.indexedBranches"), indexedBranchesPanel);
-		}
-		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() {
-			public void actionPerformed(ActionEvent event) {
-				if (validateFields()) {
-					canceled = false;
-					setVisible(false);
-				}
-			}
-		});
-
-		JButton cancelButton = new JButton(Translation.get("gb.cancel"));
-		cancelButton.addActionListener(new ActionListener() {
-			public void actionPerformed(ActionEvent event) {
-				canceled = true;
-				setVisible(false);
-			}
-		});
-
-		JPanel controls = new JPanel();
-		controls.add(cancelButton);
-		controls.add(createButton);
-
-		final Insets _insets = new Insets(5, 5, 5, 5);
-		JPanel centerPanel = new JPanel(new BorderLayout(5, 5)) {
-
-			private static final long serialVersionUID = 1L;
-
-			@Override
-			public Insets getInsets() {
-				return _insets;
-			}
-		};
-		centerPanel.add(panel, BorderLayout.CENTER);
-		centerPanel.add(controls, BorderLayout.SOUTH);
-
-		getContentPane().setLayout(new BorderLayout(5, 5));
-		getContentPane().add(centerPanel, BorderLayout.CENTER);
-		pack();
-		nameField.requestFocus();
-	}
-	
-	private JPanel newFieldPanel(String label, JComponent comp) {
-		return newFieldPanel(label, 150, comp);
-	}
-
-	private JPanel newFieldPanel(String label, int labelSize, JComponent comp) {
-		JLabel fieldLabel = new JLabel(label);
-		fieldLabel.setFont(fieldLabel.getFont().deriveFont(Font.BOLD));
-		fieldLabel.setPreferredSize(new Dimension(labelSize, 20));
-		JPanel panel = new JPanel(new FlowLayout(FlowLayout.LEFT, 10, 0));
-		panel.add(fieldLabel);
-		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);
-		} else {
-			allowAuthenticated.setEnabled(true);
-			allowNamed.setEnabled(true);
-			verifyCommitter.setEnabled(true);
-			
-			if (allowNamed.isSelected()) {
-				usersPalette.setEnabled(true);
-				teamsPalette.setEnabled(true);
-			}
-		}
-
-	}
-
-	private boolean validateFields() {
-		String rname = nameField.getText();
-		if (StringUtils.isEmpty(rname)) {
-			error("Please enter a repository name!");
-			return false;
-		}
-
-		// automatically convert backslashes to forward slashes
-		rname = rname.replace('\\', '/');
-		// Automatically replace // with /
-		rname = rname.replace("//", "/");
-
-		// prohibit folder paths
-		if (rname.startsWith("/")) {
-			error("Leading root folder references (/) are prohibited.");
-			return false;
-		}
-		if (rname.startsWith("../")) {
-			error("Relative folder references (../) are prohibited.");
-			return false;
-		}
-		if (rname.contains("/../")) {
-			error("Relative folder references (../) are prohibited.");
-			return false;
-		}
-		if (rname.endsWith("/")) {
-			rname = rname.substring(0, rname.length() - 1);
-		}
-
-		// confirm valid characters in repository name
-		Character c = StringUtils.findInvalidCharacter(rname);
-		if (c != null) {
-			error(MessageFormat.format(
-					"Illegal character ''{0}'' in repository name!", c));
-			return false;
-		}
-
-		// verify repository name uniqueness on create
-		if (isCreate) {
-			// force repo names to lowercase
-			// this means that repository name checking for rpc creation
-			// is case-insensitive, regardless of the Gitblit server's
-			// filesystem
-			if (repositoryNames.contains(rname.toLowerCase())) {
-				error(MessageFormat
-						.format("Can not create repository ''{0}'' because it already exists.",
-								rname));
-				return false;
-			}
-		} else {
-			// check rename collision
-			if (!repositoryName.equalsIgnoreCase(rname)) {
-				if (repositoryNames.contains(rname.toLowerCase())) {
-					error(MessageFormat
-							.format("Failed to rename ''{0}'' because ''{1}'' already exists.",
-									repositoryName, rname));
-					return false;
-				}
-			}
-		}
-
-		if (accessRestriction.getSelectedItem() == null) {
-			error("Please select access restriction!");
-			return false;
-		}
-
-		if (federationStrategy.getSelectedItem() == null) {
-			error("Please select federation strategy!");
-			return false;
-		}
-
-		repository.name = rname;
-		repository.description = descriptionField.getText();
-		repository.owners.clear();
-		repository.owners.addAll(ownersPalette.getSelections());
-		repository.HEAD = headRefField.getSelectedItem() == null ? null
-				: headRefField.getSelectedItem().toString();
-		repository.gcPeriod = (Integer) gcPeriod.getSelectedItem();
-		repository.gcThreshold = gcThreshold.getText();
-		repository.useTickets = useTickets.isSelected();
-		repository.useDocs = useDocs.isSelected();
-		repository.showRemoteBranches = showRemoteBranches.isSelected();
-		repository.showReadme = showReadme.isSelected();
-		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();
-
-		String ml = mailingListsField.getText();
-		if (!StringUtils.isEmpty(ml)) {
-			Set<String> list = new HashSet<String>();
-			for (String address : ml.split("(,|\\s)")) {
-				if (StringUtils.isEmpty(address)) {
-					continue;
-				}
-				list.add(address.toLowerCase());
-			}
-			repository.mailingLists = new ArrayList<String>(list);
-		}
-
-		repository.accessRestriction = (AccessRestrictionType) accessRestriction
-				.getSelectedItem();
-		repository.authorizationControl = allowAuthenticated.isSelected() ? 
-				AuthorizationControl.AUTHENTICATED : AuthorizationControl.NAMED;
-		repository.federationStrategy = (FederationStrategy) federationStrategy
-				.getSelectedItem();
-
-		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) {
-			for (JTextField field : customTextfields) {
-				String key = field.getName();
-				String value = field.getText();
-				repository.customFields.put(key, value);
-			}
-		}
-		return true;
-	}
-
-	private void error(String message) {
-		JOptionPane.showMessageDialog(EditRepositoryDialog.this, message,
-				Translation.get("gb.error"), JOptionPane.ERROR_MESSAGE);
-	}
-	
-	public void setAccessRestriction(AccessRestrictionType restriction) {
-		this.accessRestriction.setSelectedItem(restriction);
-		setupAccessPermissions(restriction);
-	}
-
-	public void setAuthorizationControl(AuthorizationControl authorization) {
-		boolean authenticated = authorization != null && AuthorizationControl.AUTHENTICATED.equals(authorization);
-		this.allowAuthenticated.setSelected(authenticated);
-		this.allowNamed.setSelected(!authenticated);
-	}
-
-	public void setUsers(List<String> owners, List<String> all, List<RegistrantAccessPermission> permissions) {
-		ownersPalette.setObjects(all, owners);
-		usersPalette.setObjects(all, permissions);
-	}
-
-	public void setTeams(List<String> all, List<RegistrantAccessPermission> permissions) {
-		teamsPalette.setObjects(all, permissions);
-	}
-
-	public void setRepositories(List<RepositoryModel> repositories) {
-		repositoryNames.clear();
-		for (RepositoryModel repository : repositories) {
-			// force repo names to lowercase
-			// this means that repository name checking for rpc creation
-			// is case-insensitive, regardless of the Gitblit server's
-			// filesystem
-			repositoryNames.add(repository.name.toLowerCase());
-		}
-	}
-
-	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);
-	}
-
-	public void setPreReceiveScripts(List<String> all, List<String> inherited,
-			List<String> selected) {
-		preReceivePalette.setObjects(all, selected);
-		showInherited(inherited, preReceiveInherited);
-	}
-
-	public void setPostReceiveScripts(List<String> all, List<String> inherited,
-			List<String> selected) {
-		postReceivePalette.setObjects(all, selected);
-		showInherited(inherited, postReceiveInherited);
-	}
-
-	private void showInherited(List<String> list, JLabel label) {
-		StringBuilder sb = new StringBuilder();
-		if (list != null && list.size() > 0) {
-			sb.append("<html><body><b>INHERITED</b><ul style=\"margin-left:5px;list-style-type: none;\">");
-			for (String script : list) {
-				sb.append("<li>").append(script).append("</li>");
-			}
-			sb.append("</ul></body></html>");
-		}
-		label.setText(sb.toString());
-	}
-
-	public RepositoryModel getRepository() {
-		if (canceled) {
-			return null;
-		}
-		return repository;
-	}
-
-	public List<RegistrantAccessPermission> getUserAccessPermissions() {
-		return usersPalette.getPermissions();
-	}
-
-	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)) {
-
-			private static final long serialVersionUID = 1L;
-
-			@Override
-			public Insets getInsets() {
-				return insets;
-			}
-		};		
-		
-		for (Map.Entry<String, String> entry : customFields.entrySet()) {
-			String field = entry.getKey();
-			String value = "";
-			if (repository.customFields != null && repository.customFields.containsKey(field)) {
-				value = repository.customFields.get(field);
-			}
-			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);		
-		jsp.getVerticalScrollBar().setBlockIncrement(100);
-		jsp.getVerticalScrollBar().setUnitIncrement(100);
-		jsp.setViewportBorder(null);
-		customFieldsPanel.setLayout(new FlowLayout(FlowLayout.LEFT));
-		customFieldsPanel.add(jsp);
-	}
-
-	/**
-	 * ListCellRenderer to display descriptive text about the access
-	 * restriction.
-	 * 
-	 */
-	private class AccessRestrictionRenderer extends DefaultListCellRenderer {
-
-		private static final long serialVersionUID = 1L;
-
-		@Override
-		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) {
-				case NONE:
-					setText(Translation.get("gb.notRestricted"));
-					break;
-				case PUSH:
-					setText(Translation.get("gb.pushRestricted"));
-					break;
-				case CLONE:
-					setText(Translation.get("gb.cloneRestricted"));
-					break;
-				case VIEW:
-					setText(Translation.get("gb.viewRestricted"));
-					break;
-				}
-			} else {
-				setText(value.toString());
-			}
-			return this;
-		}
-	}
-
-	/**
-	 * ListCellRenderer to display descriptive text about the federation
-	 * strategy.
-	 */
-	private class FederationStrategyRenderer extends JLabel implements
-			ListCellRenderer {
-
-		private static final long serialVersionUID = 1L;
-
-		@Override
-		public Component getListCellRendererComponent(JList list, Object value,
-				int index, boolean isSelected, boolean cellHasFocus) {
-			if (value instanceof FederationStrategy) {
-				FederationStrategy strategy = (FederationStrategy) value;
-				switch (strategy) {
-				case EXCLUDE:
-					setText(Translation.get("gb.excludeFromFederation"));
-					break;
-				case FEDERATE_THIS:
-					setText(Translation.get("gb.federateThis"));
-					break;
-				case FEDERATE_ORIGIN:
-					setText(Translation.get("gb.federateOrigin"));
-					break;
-				}
-			} else {
-				setText(value.toString());
-			}
-			return this;
-		}
-	}
-}
diff --git a/src/com/gitblit/client/GitblitManager.java b/src/com/gitblit/client/GitblitManager.java
deleted file mode 100644
index dd0315f..0000000
--- a/src/com/gitblit/client/GitblitManager.java
+++ /dev/null
@@ -1,458 +0,0 @@
-/*
- * 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.client;
-
-import java.awt.BorderLayout;
-import java.awt.Cursor;
-import java.awt.Dimension;
-import java.awt.EventQueue;
-import java.awt.Point;
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
-import java.awt.event.KeyEvent;
-import java.awt.event.WindowAdapter;
-import java.awt.event.WindowEvent;
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileReader;
-import java.io.FileWriter;
-import java.io.IOException;
-import java.net.ConnectException;
-import java.text.MessageFormat;
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Set;
-import java.util.TimeZone;
-
-import javax.swing.ImageIcon;
-import javax.swing.JFrame;
-import javax.swing.JMenu;
-import javax.swing.JMenuBar;
-import javax.swing.JMenuItem;
-import javax.swing.JOptionPane;
-import javax.swing.JPanel;
-import javax.swing.JTabbedPane;
-import javax.swing.KeyStroke;
-import javax.swing.SwingWorker;
-import javax.swing.UIManager;
-
-import org.eclipse.jgit.errors.ConfigInvalidException;
-import org.eclipse.jgit.lib.StoredConfig;
-import org.eclipse.jgit.storage.file.FileBasedConfig;
-import org.eclipse.jgit.util.FS;
-
-import com.gitblit.Constants;
-import com.gitblit.GitBlitException.ForbiddenException;
-import com.gitblit.models.FeedModel;
-import com.gitblit.utils.Base64;
-import com.gitblit.utils.StringUtils;
-
-/**
- * Gitblit Manager issues JSON RPC requests to a Gitblit server.
- * 
- * @author James Moger
- * 
- */
-public class GitblitManager extends JFrame implements RegistrationsDialog.RegistrationListener {
-
-	private static final long serialVersionUID = 1L;
-	private static final String SERVER = "server";
-	private static final String FEED = "feed";
-	private final SimpleDateFormat dateFormat;
-	private JTabbedPane serverTabs;
-	private File configFile = new File(System.getProperty("user.home"), ".gitblit/config");
-
-	private Map<String, GitblitRegistration> registrations = new LinkedHashMap<String, GitblitRegistration>();
-	private JMenu recentMenu;
-	private int maxRecentCount = 5;
-
-	private GitblitManager() {
-		super();
-		dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US);
-		dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
-	}
-
-	private void initialize() {
-		setContentPane(getCenterPanel());
-		setIconImage(new ImageIcon(getClass().getResource("/gitblt-favicon.png")).getImage());
-		setTitle("Gitblit Manager v" + Constants.VERSION + " (" + Constants.VERSION_DATE + ")");
-		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
-		addWindowListener(new WindowAdapter() {
-			@Override
-			public void windowClosing(WindowEvent event) {
-				saveSizeAndPosition();
-			}
-
-			@Override
-			public void windowOpened(WindowEvent event) {
-				manageRegistrations();
-			}
-		});
-
-		setSizeAndPosition();
-		loadRegistrations();
-		rebuildRecentMenu();
-	}
-
-	private void setSizeAndPosition() {
-		String sz = null;
-		String pos = null;
-		try {
-			StoredConfig config = getConfig();
-			sz = config.getString("ui", null, "size");
-			pos = config.getString("ui", null, "position");
-		} catch (Throwable t) {
-			t.printStackTrace();
-		}
-
-		// try to restore saved window size
-		if (StringUtils.isEmpty(sz)) {
-			setSize(850, 500);
-		} else {
-			String[] chunks = sz.split("x");
-			int width = Integer.parseInt(chunks[0]);
-			int height = Integer.parseInt(chunks[1]);
-			setSize(width, height);
-		}
-
-		// try to restore saved window position
-		if (StringUtils.isEmpty(pos)) {
-			setLocationRelativeTo(null);
-		} else {
-			String[] chunks = pos.split(",");
-			int x = Integer.parseInt(chunks[0]);
-			int y = Integer.parseInt(chunks[1]);
-			setLocation(x, y);
-		}
-	}
-
-	private void saveSizeAndPosition() {
-		try {
-			// save window size and position
-			StoredConfig config = getConfig();
-			Dimension sz = GitblitManager.this.getSize();
-			config.setString("ui", null, "size",
-					MessageFormat.format("{0,number,0}x{1,number,0}", sz.width, sz.height));
-			Point pos = GitblitManager.this.getLocationOnScreen();
-			config.setString("ui", null, "position",
-					MessageFormat.format("{0,number,0},{1,number,0}", pos.x, pos.y));
-			config.save();
-		} catch (Throwable t) {
-			Utils.showException(GitblitManager.this, t);
-		}
-	}
-
-	private JMenuBar setupMenu() {
-		JMenuBar menuBar = new JMenuBar();
-		JMenu serversMenu = new JMenu(Translation.get("gb.servers"));
-		menuBar.add(serversMenu);
-		recentMenu = new JMenu(Translation.get("gb.recent"));
-		serversMenu.add(recentMenu);
-
-		JMenuItem manage = new JMenuItem(Translation.get("gb.manage") + "...");
-		manage.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_M, KeyEvent.CTRL_DOWN_MASK, false));
-		manage.addActionListener(new ActionListener() {
-			public void actionPerformed(ActionEvent event) {
-				manageRegistrations();
-			}
-		});
-		serversMenu.add(manage);
-
-		return menuBar;
-	}
-
-	private JPanel getCenterPanel() {
-		serverTabs = new JTabbedPane(JTabbedPane.TOP);
-		JMenuBar menubar = setupMenu();
-		JPanel panel = new JPanel(new BorderLayout());
-		panel.add(menubar, BorderLayout.NORTH);
-		panel.add(serverTabs, BorderLayout.CENTER);
-		return panel;
-	}
-
-	private void manageRegistrations() {
-		RegistrationsDialog dialog = new RegistrationsDialog(new ArrayList<GitblitRegistration>(
-				registrations.values()), this);
-		dialog.setLocationRelativeTo(GitblitManager.this);
-		dialog.setVisible(true);
-	}
-
-	@Override
-	public void login(GitblitRegistration reg) {
-		if (!reg.savePassword && (reg.password == null || reg.password.length == 0)) {
-			// prompt for password
-			EditRegistrationDialog dialog = new EditRegistrationDialog(this, reg, true);
-			dialog.setLocationRelativeTo(GitblitManager.this);
-			dialog.setVisible(true);
-			GitblitRegistration newReg = dialog.getRegistration();
-			if (newReg == null) {
-				// user canceled
-				return;
-			}
-			// preserve feeds
-			newReg.feeds.addAll(reg.feeds);
-
-			// use new reg
-			reg = newReg;
-		}
-
-		// login
-		setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
-		final GitblitRegistration registration = reg;
-		final GitblitPanel panel = new GitblitPanel(registration, this);
-		SwingWorker<Boolean, Void> worker = new SwingWorker<Boolean, Void>() {
-
-			@Override
-			protected Boolean doInBackground() throws IOException {
-				panel.login();
-				return true;
-			}
-
-			@Override
-			protected void done() {
-				try {
-					boolean success = get();
-					serverTabs.addTab(registration.name, panel);
-					int idx = serverTabs.getTabCount() - 1;
-					serverTabs.setSelectedIndex(idx);
-					serverTabs.setTabComponentAt(idx, new ClosableTabComponent(registration.name,
-							null, serverTabs, panel));
-					registration.lastLogin = new Date();
-					saveRegistration(registration.name, registration);
-					registrations.put(registration.name, registration);
-					rebuildRecentMenu();
-					if (!registration.savePassword) {
-						// clear password
-						registration.password = null;
-					}
-				} catch (Throwable t) {
-					Throwable cause = t.getCause();
-					if (cause instanceof ConnectException) {
-						JOptionPane.showMessageDialog(GitblitManager.this, cause.getMessage(),
-								Translation.get("gb.error"), JOptionPane.ERROR_MESSAGE);
-					} else if (cause instanceof ForbiddenException) {
-						JOptionPane
-								.showMessageDialog(
-										GitblitManager.this,
-										"This Gitblit server does not allow RPC Management or Administration",
-										Translation.get("gb.error"), JOptionPane.ERROR_MESSAGE);
-					} else {
-						Utils.showException(GitblitManager.this, t);
-					}
-				} finally {
-					setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
-				}
-			}
-		};
-		worker.execute();
-	}
-
-	private void rebuildRecentMenu() {
-		recentMenu.removeAll();
-		ImageIcon icon = new ImageIcon(getClass().getResource("/gitblt-favicon.png"));
-		List<GitblitRegistration> list = new ArrayList<GitblitRegistration>(registrations.values());
-		Collections.sort(list, new Comparator<GitblitRegistration>() {
-			@Override
-			public int compare(GitblitRegistration o1, GitblitRegistration o2) {
-				return o2.lastLogin.compareTo(o1.lastLogin);
-			}
-		});
-		if (list.size() > maxRecentCount) {
-			list = list.subList(0, maxRecentCount);
-		}
-		for (int i = 0; i < list.size(); i++) {
-			final GitblitRegistration reg = list.get(i);
-			JMenuItem item = new JMenuItem(reg.name, icon);
-			item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_1 + i, KeyEvent.CTRL_DOWN_MASK,
-					false));
-			item.addActionListener(new ActionListener() {
-				public void actionPerformed(ActionEvent e) {
-					login(reg);
-				}
-			});
-			recentMenu.add(item);
-		}
-	}
-
-	private void loadRegistrations() {
-		try {
-			StoredConfig config = getConfig();
-			Set<String> servers = config.getSubsections(SERVER);
-			for (String server : servers) {
-				Date lastLogin = new Date(0);
-				String date = config.getString(SERVER, server, "lastLogin");
-				if (!StringUtils.isEmpty(date)) {
-					lastLogin = dateFormat.parse(date);
-				}
-				String url = config.getString(SERVER, server, "url");
-				String account = config.getString(SERVER, server, "account");
-				char[] password;
-				String pw = config.getString(SERVER, server, "password");
-				if (StringUtils.isEmpty(pw)) {
-					password = new char[0];
-				} else {
-					password = new String(Base64.decode(pw)).toCharArray();
-				}
-				GitblitRegistration reg = new GitblitRegistration(server, url, account, password) {
-					private static final long serialVersionUID = 1L;
-
-					protected void cacheFeeds() {
-						writeFeedCache(this);
-					}
-				};
-				String[] feeds = config.getStringList(SERVER, server, FEED);
-				if (feeds != null) {
-					// deserialize the field definitions
-					for (String definition : feeds) {
-						FeedModel feed = new FeedModel(definition);
-						reg.feeds.add(feed);
-					}
-				}
-				reg.lastLogin = lastLogin;
-				loadFeedCache(reg);
-				registrations.put(reg.name, reg);
-			}
-		} catch (Throwable t) {
-			Utils.showException(GitblitManager.this, t);
-		}
-	}
-
-	@Override
-	public boolean saveRegistration(String name, GitblitRegistration reg) {
-		try {
-			StoredConfig config = getConfig();
-			if (!StringUtils.isEmpty(name) && !name.equals(reg.name)) {
-				// delete old registration
-				registrations.remove(name);
-				config.unsetSection(SERVER, name);
-			}
-
-			// update registration
-			config.setString(SERVER, reg.name, "url", reg.url);
-			config.setString(SERVER, reg.name, "account", reg.account);
-			if (reg.savePassword) {
-				config.setString(SERVER, reg.name, "password",
-						Base64.encodeBytes(new String(reg.password).getBytes("UTF-8")));
-			} else {
-				config.setString(SERVER, reg.name, "password", "");
-			}
-			if (reg.lastLogin != null) {
-				config.setString(SERVER, reg.name, "lastLogin", dateFormat.format(reg.lastLogin));
-			}
-			// serialize the feed definitions
-			List<String> definitions = new ArrayList<String>();
-			for (FeedModel feed : reg.feeds) {
-				definitions.add(feed.toString());
-			}
-			if (definitions.size() > 0) {
-				config.setStringList(SERVER, reg.name, FEED, definitions);
-			}
-			config.save();
-			return true;
-		} catch (Throwable t) {
-			Utils.showException(GitblitManager.this, t);
-		}
-		return false;
-	}
-
-	@Override
-	public boolean deleteRegistrations(List<GitblitRegistration> list) {
-		boolean success = false;
-		try {
-			StoredConfig config = getConfig();
-			for (GitblitRegistration reg : list) {
-				config.unsetSection(SERVER, reg.name);
-				registrations.remove(reg.name);
-			}
-			config.save();
-			success = true;
-		} catch (Throwable t) {
-			Utils.showException(GitblitManager.this, t);
-		}
-		return success;
-	}
-
-	private StoredConfig getConfig() throws IOException, ConfigInvalidException {
-		FileBasedConfig config = new FileBasedConfig(configFile, FS.detect());
-		config.load();
-		return config;
-	}
-
-	private void loadFeedCache(GitblitRegistration reg) {
-		File feedCache = new File(configFile.getParentFile(), StringUtils.getSHA1(reg.toString())
-				+ ".cache");
-		if (!feedCache.exists()) {
-			// no cache for this registration
-			return;
-		}
-		try {
-			BufferedReader reader = new BufferedReader(new FileReader(feedCache));
-			Map<String, Date> cache = new HashMap<String, Date>();
-			SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
-			String line = null;
-			while ((line = reader.readLine()) != null) {
-				String[] kvp = line.split("=");
-				cache.put(kvp[0], df.parse(kvp[1]));
-			}
-			reader.close();
-			for (FeedModel feed : reg.feeds) {
-				String name = feed.toString();
-				if (cache.containsKey(name)) {
-					feed.currentRefreshDate = cache.get(name);
-				}
-			}
-		} catch (Exception e) {
-			Utils.showException(GitblitManager.this, e);
-		}
-	}
-
-	private void writeFeedCache(GitblitRegistration reg) {
-		try {
-			File feedCache = new File(configFile.getParentFile(), StringUtils.getSHA1(reg
-					.toString()) + ".cache");
-			FileWriter writer = new FileWriter(feedCache);
-			for (FeedModel feed : reg.feeds) {
-				writer.append(MessageFormat.format("{0}={1,date,yyyy-MM-dd'T'HH:mm:ss}\n",
-						feed.toString(), feed.currentRefreshDate));
-			}
-			writer.close();
-		} catch (Exception e) {
-			Utils.showException(GitblitManager.this, e);
-		}
-	}
-
-	public static void main(String[] args) {
-		EventQueue.invokeLater(new Runnable() {
-			public void run() {
-				try {
-					UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
-				} catch (Exception e) {
-				}
-				GitblitManager frame = new GitblitManager();
-				frame.initialize();
-				frame.setVisible(true);
-			}
-		});
-	}
-}
diff --git a/src/com/gitblit/client/GitblitManagerLauncher.java b/src/com/gitblit/client/GitblitManagerLauncher.java
deleted file mode 100644
index 9b6ee96..0000000
--- a/src/com/gitblit/client/GitblitManagerLauncher.java
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- * 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.client;
-
-import java.awt.Color;
-import java.awt.EventQueue;
-import java.awt.FontMetrics;
-import java.awt.Graphics2D;
-import java.awt.SplashScreen;
-import java.io.File;
-import java.io.IOException;
-import java.util.Collections;
-import java.util.List;
-
-import com.gitblit.Constants;
-import com.gitblit.Launcher;
-import com.gitblit.build.Build;
-import com.gitblit.build.Build.DownloadListener;
-
-/**
- * Downloads dependencies and launches Gitblit Manager.
- * 
- * @author James Moger
- * 
- */
-public class GitblitManagerLauncher {
-
-	public static void main(String[] args) {
-		final SplashScreen splash = SplashScreen.getSplashScreen();
-		
-		DownloadListener downloadListener = new DownloadListener() {
-			@Override
-			public void downloading(String name) {
-				updateSplash(splash, Translation.get("gb.downloading") + " " + name);				
-			}
-		};
-		
-		// download rpc client runtime dependencies
-		Build.manager(downloadListener);
-
-		File libFolder = new File("ext");
-		List<File> jars = Launcher.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);
-		Collections.reverse(jars);
-		for (File jar : jars) {
-			try {
-				updateSplash(splash, Translation.get("gb.loading") + " " + jar.getName() + "...");
-				Launcher.addJarFile(jar);
-			} catch (IOException e) {
-
-			}
-		}
-		
-		updateSplash(splash, Translation.get("gb.starting") + " Gitblit Manager...");
-		GitblitManager.main(args);
-	}
-
-	private static void updateSplash(final SplashScreen splash, final String string) {
-		if (splash == null) {
-			return;
-		}
-		try {
-			EventQueue.invokeAndWait(new Runnable() {
-				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();
-						int x = 5;
-						int y = 115;
-						int w = 320 - 2 * x;
-						g.fillRect(x, y - h, w, h);
-						g.setColor(Color.lightGray);
-						g.drawRect(x, y - h, w, h);
-						g.setColor(Color.WHITE);
-						int xw = fm.stringWidth(string);
-						g.drawString(string, x + ((w - xw) / 2), y - 5);
-						
-						// paint version
-						String ver = "v" + Constants.VERSION;
-						int vw = g.getFontMetrics().stringWidth(ver);
-						g.drawString(ver, 320 - vw - 5, 34);
-						g.dispose();
-						splash.update();
-					}
-				}
-			});
-		} catch (Throwable t) {
-			t.printStackTrace();
-		}
-	}
-}
diff --git a/src/com/gitblit/client/Translation.java b/src/com/gitblit/client/Translation.java
deleted file mode 100644
index 16ef20d..0000000
--- a/src/com/gitblit/client/Translation.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * 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.client;
-
-import java.util.MissingResourceException;
-import java.util.ResourceBundle;
-
-import com.gitblit.utils.TimeUtils;
-
-/**
- * Loads the Gitblit language resource file.
- * 
- * @author James Moger
- * 
- */
-public class Translation {
-
-	private final static ResourceBundle translation;
-	
-	private final static TimeUtils timeUtils;
-
-	static {
-		ResourceBundle bundle;
-		try {
-			// development location
-			bundle = ResourceBundle.getBundle("com/gitblit/wicket/GitBlitWebApp");
-		} catch (MissingResourceException e) {
-			// runtime location
-			bundle = ResourceBundle.getBundle("GitBlitWebApp");
-		}
-		translation = bundle;
-		
-		timeUtils = new TimeUtils(translation);
-	}
-
-	public static String get(String key) {
-		if (translation.containsKey(key)) {
-			return translation.getString(key).trim();
-		}
-		return key;
-	}
-	
-	public static TimeUtils getTimeUtils() {
-		return timeUtils;
-	}
-}
diff --git a/src/com/gitblit/models/Activity.java b/src/com/gitblit/models/Activity.java
deleted file mode 100644
index 59405c7..0000000
--- a/src/com/gitblit/models/Activity.java
+++ /dev/null
@@ -1,129 +0,0 @@
-/*
- * 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.models;
-
-import java.io.Serializable;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import org.eclipse.jgit.revwalk.RevCommit;
-
-import com.gitblit.utils.StringUtils;
-import com.gitblit.utils.TimeUtils;
-
-/**
- * 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> {
-
-	private static final long serialVersionUID = 1L;
-
-	public final Date startDate;
-
-	public final Date endDate;
-
-	private final Set<RepositoryCommit> commits;
-
-	private final Map<String, Metric> authorMetrics;
-
-	private final Map<String, Metric> repositoryMetrics;
-
-	/**
-	 * Constructor for one day of activity.
-	 * 
-	 * @param date
-	 */
-	public Activity(Date date) {
-		this(date, TimeUtils.ONEDAY - 1);
-	}
-
-	/**
-	 * Constructor for specified duration of activity from start date.
-	 * 
-	 * @param date
-	 *            the start date of the activity
-	 * @param duration
-	 *            the duration of the period in milliseconds
-	 */
-	public Activity(Date date, long duration) {
-		startDate = date;
-		endDate = new Date(date.getTime() + duration);
-		commits = new LinkedHashSet<RepositoryCommit>();
-		authorMetrics = new HashMap<String, Metric>();
-		repositoryMetrics = new HashMap<String, Metric>();
-	}
-
-	/**
-	 * Adds a commit to the activity object as long as the commit is not a
-	 * duplicate.
-	 * 
-	 * @param repository
-	 * @param branch
-	 * @param commit
-	 * @return a RepositoryCommit, if one was added. Null if this is duplicate
-	 *         commit
-	 */
-	public RepositoryCommit addCommit(String repository, String branch, RevCommit commit) {
-		RepositoryCommit commitModel = new RepositoryCommit(repository, branch, commit);
-		if (commits.add(commitModel)) {
-			if (!repositoryMetrics.containsKey(repository)) {
-				repositoryMetrics.put(repository, new Metric(repository));
-			}
-			repositoryMetrics.get(repository).count++;
-
-			String author = StringUtils.removeNewlines(commit.getAuthorIdent().getEmailAddress()).toLowerCase();			
-			if (!authorMetrics.containsKey(author)) {
-				authorMetrics.put(author, new Metric(author));
-			}
-			authorMetrics.get(author).count++;
-			return commitModel;
-		}
-		return null;
-	}
-	
-	public int getCommitCount() {
-		return commits.size();
-	}
-	
-	public List<RepositoryCommit> getCommits() {
-		List<RepositoryCommit> list = new ArrayList<RepositoryCommit>(commits);
-		Collections.sort(list);
-		return list;
-	}
-
-	public Map<String, Metric> getAuthorMetrics() {
-		return authorMetrics;
-	}
-
-	public Map<String, Metric> getRepositoryMetrics() {
-		return repositoryMetrics;
-	}
-
-	@Override
-	public int compareTo(Activity o) {
-		// reverse chronological order
-		return o.startDate.compareTo(startDate);
-	}
-}
diff --git a/src/com/gitblit/models/AnnotatedLine.java b/src/com/gitblit/models/AnnotatedLine.java
deleted file mode 100644
index 69b55bc..0000000
--- a/src/com/gitblit/models/AnnotatedLine.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * 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.models;
-
-import java.io.Serializable;
-import java.util.Date;
-
-import org.eclipse.jgit.revwalk.RevCommit;
-
-/**
- * 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 {
-
-	private static final long serialVersionUID = 1L;
-
-	public final String commitId;
-	public final String author;
-	public final Date when;
-	public final int lineNumber;
-	public final String data;
-
-	public AnnotatedLine(RevCommit commit, int lineNumber, String data) {
-		this.commitId = commit.getName();
-		this.author = commit.getAuthorIdent().getName();
-		this.when = commit.getAuthorIdent().getWhen();
-		this.lineNumber = lineNumber;
-		this.data = data;
-	}
-}
\ No newline at end of file
diff --git a/src/com/gitblit/models/PathModel.java b/src/com/gitblit/models/PathModel.java
deleted file mode 100644
index 84571cb..0000000
--- a/src/com/gitblit/models/PathModel.java
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * 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.models;
-
-import java.io.Serializable;
-
-import org.eclipse.jgit.diff.DiffEntry.ChangeType;
-import org.eclipse.jgit.lib.FileMode;
-
-/**
- * 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> {
-
-	private static final long serialVersionUID = 1L;
-
-	public final String name;
-	public final String path;
-	public final long size;
-	public final int mode;
-	public final String objectId;
-	public final String commitId;
-	public boolean isParentPath;
-
-	public PathModel(String name, String path, long size, int mode, String objectId, String commitId) {
-		this.name = name;
-		this.path = path;
-		this.size = size;
-		this.mode = mode;
-		this.objectId = objectId;
-		this.commitId = commitId;
-	}
-
-	public boolean isSymlink() {
-		return FileMode.SYMLINK.equals(mode);
-	}
-
-	public boolean isSubmodule() {
-		return FileMode.GITLINK.equals(mode);
-	}
-	
-	public boolean isTree() {
-		return FileMode.TREE.equals(mode);
-	}
-
-	@Override
-	public int hashCode() {
-		return commitId.hashCode() + path.hashCode();
-	}
-
-	@Override
-	public boolean equals(Object o) {
-		if (o instanceof PathModel) {
-			PathModel other = (PathModel) o;
-			return this.path.equals(other.path);
-		}
-		return super.equals(o);
-	}
-
-	@Override
-	public int compareTo(PathModel o) {
-		boolean isTree = isTree();
-		boolean otherTree = o.isTree();
-		if (isTree && otherTree) {
-			return path.compareTo(o.path);
-		} else if (!isTree && !otherTree) {
-			if (isSubmodule() && o.isSubmodule()) {
-				return path.compareTo(o.path);
-			} else if (isSubmodule()) {
-				return -1;
-			} else if (o.isSubmodule()) {
-				return 1;
-			}
-			return path.compareTo(o.path);
-		} else if (isTree && !otherTree) {
-			return -1;
-		}
-		return 1;
-	}
-
-	/**
-	 * 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 final ChangeType changeType;
-
-		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;
-		}
-
-		@Override
-		public int hashCode() {
-			return super.hashCode();
-		}
-
-		@Override
-		public boolean equals(Object o) {
-			return super.equals(o);
-		}
-	}
-}
diff --git a/src/com/gitblit/models/PushLogEntry.java b/src/com/gitblit/models/PushLogEntry.java
deleted file mode 100644
index f625c2a..0000000
--- a/src/com/gitblit/models/PushLogEntry.java
+++ /dev/null
@@ -1,208 +0,0 @@
-/*
- * Copyright 2013 gitblit.com.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.gitblit.models;
-
-import java.io.Serializable;
-import java.text.MessageFormat;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import org.eclipse.jgit.lib.Constants;
-import org.eclipse.jgit.revwalk.RevCommit;
-import org.eclipse.jgit.transport.ReceiveCommand;
-
-/**
- * Model class to represent a push into a repository.
- * 
- * @author James Moger
- */
-public class PushLogEntry implements Serializable, Comparable<PushLogEntry> {
-
-	private static final long serialVersionUID = 1L;
-
-	public final String repository;
-	
-	public final Date date;
-	
-	public final UserModel user;
-
-	private final Set<RepositoryCommit> commits;
-	
-	private final Map<String, ReceiveCommand.Type> refUpdates;
-
-	/**
-	 * Constructor for specified duration of push from start date.
-	 * 
-	 * @param repository
-	 *            the repository that received the push
-	 * @param date
-	 *            the date of the push
-	 * @param user
-	 *            the user who pushed
-	 */
-	public PushLogEntry(String repository, Date date, UserModel user) {
-		this.repository = repository;
-		this.date = date;
-		this.user = user;
-		this.commits = new LinkedHashSet<RepositoryCommit>();
-		this.refUpdates = new HashMap<String, ReceiveCommand.Type>();
-	}
-	
-	/**
-	 * Tracks the change type for the specified ref.
-	 * 
-	 * @param ref
-	 * @param type
-	 */
-	public void updateRef(String ref, ReceiveCommand.Type type) {
-		if (!refUpdates.containsKey(ref)) {
-			refUpdates.put(ref, type);
-		}
-	}
-
-	/**
-	 * 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
-	 *         commit
-	 */
-	public RepositoryCommit addCommit(String branch, RevCommit commit) {
-		RepositoryCommit commitModel = new RepositoryCommit(repository, branch, commit);
-		if (commits.add(commitModel)) {
-			return commitModel;
-		}
-		return null;
-	}
-	
-	/**
-	 * Returns true if this push contains a non-fastforward ref update.
-	 * 
-	 * @return true if this is a non-fastforward push
-	 */
-	public boolean isNonFastForward() {
-		for (Map.Entry<String, ReceiveCommand.Type> entry : refUpdates.entrySet()) {
-			if (ReceiveCommand.Type.UPDATE_NONFASTFORWARD.equals(entry.getValue())) {
-				return true;
-			}
-		}
-		return false;
-	}
-	
-	/**
-	 * 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() {
-		return getChangedRefs(Constants.R_TAGS);
-	}
-
-	/**
-	 * Gets the changed refs in the push.
-	 * 
-	 * @param baseRef
-	 * @return the changed refs
-	 */
-	protected List<String> getChangedRefs(String baseRef) {
-		Set<String> refs = new HashSet<String>();
-		for (String ref : refUpdates.keySet()) {
-			if (baseRef == null || ref.startsWith(baseRef)) {
-				refs.add(ref);
-			}
-		}
-		List<String> list = new ArrayList<String>(refs);
-		Collections.sort(list);
-		return list;
-	}
-	
-	/**
-	 * 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() {
-		List<RepositoryCommit> list = new ArrayList<RepositoryCommit>(commits);
-		Collections.sort(list);
-		return list;
-	}
-	
-	/**
-	 * Returns all commits that belong to a particular ref
-	 * 
-	 * @param ref
-	 * @return a list of commits
-	 */
-	public List<RepositoryCommit> getCommits(String ref) {
-		List<RepositoryCommit> list = new ArrayList<RepositoryCommit>();
-		for (RepositoryCommit commit : commits) {
-			if (commit.branch.equals(ref)) {
-				list.add(commit);
-			}
-		}
-		Collections.sort(list);
-		return list;
-	}
-
-	@Override
-	public int compareTo(PushLogEntry o) {
-		// reverse chronological order
-		return o.date.compareTo(date);
-	}
-	
-	@Override
-	public String toString() {
-		StringBuilder sb = new StringBuilder();
-		sb.append(MessageFormat.format("{0,date,yyyy-MM-dd HH:mm}: {1} pushed {2,number,0} commit{3} to {4} ",
-				date, user.getDisplayName(), commits.size(), commits.size() == 1 ? "":"s", repository));
-		for (Map.Entry<String, ReceiveCommand.Type> entry : refUpdates.entrySet()) {
-			String ref = entry.getKey();
-			ReceiveCommand.Type type = entry.getValue();
-			sb.append("\n  ").append(ref).append(' ').append(type.name()).append('\n');
-			for (RepositoryCommit commit : getCommits(ref)) {
-				sb.append("    ").append(commit.toString()).append('\n');
-			}
-		}
-		return sb.toString();
-	}
-}
diff --git a/src/com/gitblit/models/RepositoryCommit.java b/src/com/gitblit/models/RepositoryCommit.java
deleted file mode 100644
index e68e861..0000000
--- a/src/com/gitblit/models/RepositoryCommit.java
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- * 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.models;
-
-import java.io.Serializable;
-import java.text.MessageFormat;
-import java.util.List;
-
-import org.eclipse.jgit.lib.PersonIdent;
-import org.eclipse.jgit.revwalk.RevCommit;
-
-/**
- * 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> {
-
-	private static final long serialVersionUID = 1L;
-
-	public final String repository;
-
-	public final String branch;
-
-	private final RevCommit commit;
-
-	private List<RefModel> refs;
-
-	public RepositoryCommit(String repository, String branch, RevCommit commit) {
-		this.repository = repository;
-		this.branch = branch;
-		this.commit = commit;
-	}
-
-	public void setRefs(List<RefModel> refs) {
-		this.refs = refs;
-	}
-
-	public List<RefModel> getRefs() {
-		return refs;
-	}
-
-	public String getName() {
-		return commit.getName();
-	}
-
-	public String getShortName() {
-		return commit.getName().substring(0, 8);
-	}
-
-	public String getShortMessage() {
-		return commit.getShortMessage();
-	}
-
-	public int getParentCount() {
-		return commit.getParentCount();
-	}
-
-	public PersonIdent getAuthorIdent() {
-		return commit.getAuthorIdent();
-	}
-
-	public PersonIdent getCommitterIdent() {
-		return commit.getCommitterIdent();
-	}
-
-	@Override
-	public boolean equals(Object o) {
-		if (o instanceof RepositoryCommit) {
-			RepositoryCommit commit = (RepositoryCommit) o;
-			return repository.equals(commit.repository) && getName().equals(commit.getName());
-		}
-		return false;
-	}
-
-	@Override
-	public int hashCode() {
-		return (repository + commit).hashCode();
-	}
-
-	@Override
-	public int compareTo(RepositoryCommit o) {
-		// reverse-chronological order
-		if (commit.getCommitTime() > o.commit.getCommitTime()) {
-			return -1;
-		} else if (commit.getCommitTime() < o.commit.getCommitTime()) {
-			return 1;
-		}
-		return 0;
-	}
-	
-	@Override
-	public String toString() {
-		return MessageFormat.format("{0} {1} {2,date,yyyy-MM-dd HH:mm} {3} {4}", 
-				getShortName(), branch, getCommitterIdent().getWhen(), getAuthorIdent().getName(),
-				getShortMessage());
-	}
-}
\ No newline at end of file
diff --git a/src/com/gitblit/models/RepositoryModel.java b/src/com/gitblit/models/RepositoryModel.java
deleted file mode 100644
index a2dab3c..0000000
--- a/src/com/gitblit/models/RepositoryModel.java
+++ /dev/null
@@ -1,243 +0,0 @@
-/*
- * 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.models;
-
-import java.io.Serializable;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Date;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.TreeSet;
-
-import com.gitblit.Constants.AccessRestrictionType;
-import com.gitblit.Constants.AuthorizationControl;
-import com.gitblit.Constants.FederationStrategy;
-import com.gitblit.utils.ArrayUtils;
-import com.gitblit.utils.StringUtils;
-
-/**
- * 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> {
-
-	private static final long serialVersionUID = 1L;
-
-	// field names are reflectively mapped in EditRepository page
-	public String name;
-	public String description;
-	public List<String> owners;
-	public Date lastChange;
-	public boolean hasCommits;
-	public boolean showRemoteBranches;
-	public boolean useTickets;
-	public boolean useDocs;
-	public AccessRestrictionType accessRestriction;
-	public AuthorizationControl authorizationControl;
-	public boolean allowAuthenticated;
-	public boolean isFrozen;
-	public boolean showReadme;
-	public FederationStrategy federationStrategy;
-	public List<String> federationSets;
-	public boolean isFederated;
-	public boolean skipSizeCalculation;
-	public boolean skipSummaryMetrics;
-	public String frequency;
-	public boolean isBare;
-	public String origin;
-	public String HEAD;
-	public List<String> availableRefs;
-	public List<String> indexedBranches;
-	public String size;
-	public List<String> preReceiveScripts;
-	public List<String> postReceiveScripts;
-	public List<String> mailingLists;
-	public Map<String, String> customFields;
-	public String projectPath;
-	private String displayName;
-	public boolean allowForks;
-	public Set<String> forks;
-	public String originRepository;
-	public boolean verifyCommitter;
-	public String gcThreshold;
-	public int gcPeriod;
-	public int maxActivityCommits;
-	
-	public transient boolean isCollectingGarbage;
-	public Date lastGC;
-	public String sparkleshareId;
-	
-	public RepositoryModel() {
-		this("", "", "", new Date(0));
-	}
-
-	public RepositoryModel(String name, String description, String owner, Date lastchange) {
-		this.name = name;
-		this.description = description;
-		this.lastChange = lastchange;
-		this.accessRestriction = AccessRestrictionType.NONE;
-		this.authorizationControl = AuthorizationControl.NAMED;
-		this.federationSets = new ArrayList<String>();
-		this.federationStrategy = FederationStrategy.FEDERATE_THIS;	
-		this.projectPath = StringUtils.getFirstPathElement(name);
-		this.owners = new ArrayList<String>();
-		
-		addOwner(owner);
-	}
-	
-	public List<String> getLocalBranches() {
-		if (ArrayUtils.isEmpty(availableRefs)) {
-			return new ArrayList<String>();
-		}
-		List<String> localBranches = new ArrayList<String>();
-		for (String ref : availableRefs) {
-			if (ref.startsWith("refs/heads")) {
-				localBranches.add(ref);
-			}
-		}
-		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) {
-			return name.equals(((RepositoryModel) o).name);
-		}
-		return false;
-	}
-
-	@Override
-	public String toString() {
-		if (displayName == null) {
-			displayName = StringUtils.stripDotGit(name);
-		}
-		return displayName;
-	}
-
-	@Override
-	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) && projectPath.charAt(0) == '~';
-	}
-	
-	public boolean isUsersPersonalRepository(String username) {
-		return !StringUtils.isEmpty(projectPath) && projectPath.equalsIgnoreCase("~" + username);
-	}
-	
-	public boolean allowAnonymousView() {
-		return !accessRestriction.atLeast(AccessRestrictionType.VIEW);
-	}
-	
-	public boolean isSparkleshared() {
-		return !StringUtils.isEmpty(sparkleshareId);
-	}
-	
-	public RepositoryModel cloneAs(String cloneName) {
-		RepositoryModel clone = new RepositoryModel();
-		clone.originRepository = name;
-		clone.name = cloneName;
-		clone.projectPath = StringUtils.getFirstPathElement(cloneName);
-		clone.isBare = true;
-		clone.description = description;
-		clone.accessRestriction = AccessRestrictionType.PUSH;
-		clone.authorizationControl = AuthorizationControl.NAMED;
-		clone.federationStrategy = federationStrategy;
-		clone.showReadme = showReadme;
-		clone.showRemoteBranches = false;
-		clone.allowForks = false;
-		clone.useDocs = useDocs;
-		clone.useTickets = useTickets;
-		clone.skipSizeCalculation = skipSizeCalculation;
-		clone.skipSummaryMetrics = skipSummaryMetrics;
-		clone.sparkleshareId = sparkleshareId; 
-		return clone;
-	}
-
-	public void addOwner(String username) {
-		if (!StringUtils.isEmpty(username)) {
-			String name = username.toLowerCase();
-			// a set would be more efficient, but this complicates JSON
-			// deserialization so we enforce uniqueness with an arraylist
-			if (!owners.contains(name)) {
-				owners.add(name);
-			}
-		}
-	}
-
-	public void removeOwner(String username) {
-		if (!StringUtils.isEmpty(username)) {
-			owners.remove(username.toLowerCase());
-		}
-	}
-
-	public void addOwners(Collection<String> usernames) {
-		if (!ArrayUtils.isEmpty(usernames)) {
-			for (String username : usernames) {
-				addOwner(username);
-			}
-		}
-	}
-
-	public void removeOwners(Collection<String> usernames) {
-		if (!ArrayUtils.isEmpty(owners)) {
-			for (String username : usernames) {
-				removeOwner(username);
-			}
-		}
-	}
-}
diff --git a/src/com/gitblit/models/ServerStatus.java b/src/com/gitblit/models/ServerStatus.java
deleted file mode 100644
index f1650c8..0000000
--- a/src/com/gitblit/models/ServerStatus.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * 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.models;
-
-import java.io.Serializable;
-import java.util.Date;
-import java.util.Map;
-import java.util.TreeMap;
-
-import com.gitblit.Constants;
-
-/**
- * ServerStatus encapsulates runtime status information about the server
- * including some information about the system environment.
- * 
- * @author James Moger
- * 
- */
-public class ServerStatus implements Serializable {
-
-	private static final long serialVersionUID = 1L;
-
-	public final Date bootDate;
-
-	public final String version;
-
-	public final String releaseDate;
-
-	public final boolean isGO;
-
-	public final Map<String, String> systemProperties;
-
-	public final long heapMaximum;
-
-	public volatile long heapAllocated;
-
-	public volatile long heapFree;
-
-	public String servletContainer;
-
-	public ServerStatus(boolean isGO) {
-		this.bootDate = new Date();
-		this.version = Constants.VERSION;
-		this.releaseDate = Constants.VERSION_DATE;
-		this.isGO = isGO;
-
-		this.heapMaximum = Runtime.getRuntime().maxMemory();
-
-		this.systemProperties = new TreeMap<String, String>();
-		put("file.encoding");
-		put("java.home");
-		put("java.awt.headless");
-		put("java.io.tmpdir");
-		put("java.runtime.name");
-		put("java.runtime.version");
-		put("java.vendor");
-		put("java.version");
-		put("java.vm.info");
-		put("java.vm.name");
-		put("java.vm.vendor");
-		put("java.vm.version");
-		put("os.arch");
-		put("os.name");
-		put("os.version");
-	}
-
-	private void put(String key) {
-		systemProperties.put(key, System.getProperty(key));
-	}
-}
diff --git a/src/com/gitblit/models/TeamModel.java b/src/com/gitblit/models/TeamModel.java
deleted file mode 100644
index 9587ca7..0000000
--- a/src/com/gitblit/models/TeamModel.java
+++ /dev/null
@@ -1,310 +0,0 @@
-/*
- * 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.models;
-
-import java.io.Serializable;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import com.gitblit.Constants.AccessPermission;
-import com.gitblit.Constants.AccessRestrictionType;
-import com.gitblit.Constants.PermissionType;
-import com.gitblit.Constants.RegistrantType;
-import com.gitblit.Constants.Unused;
-import com.gitblit.utils.StringUtils;
-
-/**
- * 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> {
-
-	private static final long serialVersionUID = 1L;
-
-	// field names are reflectively mapped in EditTeam page
-	public String name;
-	public boolean canAdmin;
-	public boolean canFork;
-	public boolean canCreate;
-	public final Set<String> users = new HashSet<String>();
-	// retained for backwards-compatibility with RPC clients
-	@Deprecated
-	public final Set<String> repositories = new HashSet<String>();
-	public final Map<String, AccessPermission> permissions = new LinkedHashMap<String, AccessPermission>();
-	public final Set<String> mailingLists = new HashSet<String>();
-	public final List<String> preReceiveScripts = new ArrayList<String>();
-	public final List<String> postReceiveScripts = new ArrayList<String>();
-
-	public TeamModel(String name) {
-		this.name = name;
-	}
-
-	/**
-	 * @use hasRepositoryPermission
-	 * @param name
-	 * @return
-	 */
-	@Deprecated
-	@Unused
-	public boolean hasRepository(String name) {
-		return hasRepositoryPermission(name);
-	}
-
-	@Deprecated
-	@Unused
-	public void addRepository(String name) {
-		addRepositoryPermission(name);
-	}
-	
-	@Deprecated
-	@Unused
-	public void addRepositories(Collection<String> names) {
-		addRepositoryPermissions(names);
-	}
-
-	@Deprecated
-	@Unused
-	public void removeRepository(String name) {
-		removeRepositoryPermission(name);
-	}
-
-	
-	/**
-	 * Returns a list of repository permissions for this team.
-	 * 
-	 * @return the team's list of permissions
-	 */
-	public List<RegistrantAccessPermission> getRepositoryPermissions() {
-		List<RegistrantAccessPermission> list = new ArrayList<RegistrantAccessPermission>();
-		if (canAdmin) {
-			// team has REWIND access to all repositories
-			return list;
-		}
-		for (Map.Entry<String, AccessPermission> entry : permissions.entrySet()) {
-			String registrant = entry.getKey();
-			String source = null;
-			boolean editable = true;
-			PermissionType pType = PermissionType.EXPLICIT;
-			if (StringUtils.findInvalidCharacter(registrant) != null) {
-				// a regex will have at least 1 invalid character
-				pType = PermissionType.REGEX;
-				source = registrant;
-			}
-			list.add(new RegistrantAccessPermission(registrant, entry.getValue(), pType, RegistrantType.REPOSITORY, source, editable));
-		}
-		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
-	 */
-	public boolean hasRepositoryPermission(String name) {
-		String repository = AccessPermission.repositoryFromRole(name).toLowerCase();
-		if (permissions.containsKey(repository)) {
-			// exact repository permission specified
-			return true;
-		} else {
-			// search for regex permission match
-			for (String key : permissions.keySet()) {
-				if (name.matches(key)) {
-					AccessPermission p = permissions.get(key);
-					if (p != null) {
-						return true;
-					}
-				}
-			}
-		}
-		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
-	 */
-	public boolean hasExplicitRepositoryPermission(String name) {
-		String repository = AccessPermission.repositoryFromRole(name).toLowerCase();
-		return permissions.containsKey(repository);
-	}
-	
-	/**
-	 * Adds a repository permission to the team.
-	 * <p>
-	 * Role may be formatted as:
-	 * <ul>
-	 * <li> myrepo.git <i>(this is implicitly RW+)</i>
-	 * <li> RW+:myrepo.git
-	 * </ul>
-	 * @param role
-	 */
-	public void addRepositoryPermission(String role) {
-		AccessPermission permission = AccessPermission.permissionFromRole(role);
-		String repository = AccessPermission.repositoryFromRole(role).toLowerCase();
-		repositories.add(repository);
-		permissions.put(repository, permission);
-	}
-
-	public void addRepositoryPermissions(Collection<String> roles) {
-		for (String role:roles) {
-			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) {
-		permissions.put(repository.toLowerCase(), permission);
-		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;
-		
-		if (canAdmin) {
-			ap.permissionType = PermissionType.ADMINISTRATOR;
-			ap.permission = AccessPermission.REWIND;
-			return ap;
-		}
-		
-		if (permissions.containsKey(repository.name.toLowerCase())) {
-			// exact repository permission specified
-			AccessPermission p = permissions.get(repository.name.toLowerCase());
-			if (p != null) {
-				ap.permissionType = PermissionType.EXPLICIT;
-				ap.permission = p;
-				ap.mutable = true;
-				return ap;
-			}
-		} else {
-			// search for case-insensitive regex permission match
-			for (String key : permissions.keySet()) {
-				if (StringUtils.matchesIgnoreCase(repository.name, key)) {
-					AccessPermission p = permissions.get(key);
-					if (p != null) {
-						// take first match
-						ap.permissionType = PermissionType.REGEX;
-						ap.permission = p;
-						ap.source = key;
-						return ap;
-					}
-				}
-			}
-		}
-		return ap;
-	}
-	
-	protected boolean canAccess(RepositoryModel repository, AccessRestrictionType ifRestriction, AccessPermission requirePermission) {
-		if (repository.accessRestriction.atLeast(ifRestriction)) {
-			RegistrantAccessPermission ap = getRepositoryPermission(repository);
-			return ap.permission.atLeast(requirePermission);
-		}
-		return true;
-	}
-	
-	public boolean canView(RepositoryModel repository) {
-		return canAccess(repository, AccessRestrictionType.VIEW, AccessPermission.VIEW);
-	}
-
-	public boolean canClone(RepositoryModel repository) {
-		return canAccess(repository, AccessRestrictionType.CLONE, AccessPermission.CLONE);
-	}
-
-	public boolean canPush(RepositoryModel repository) {
-		if (repository.isFrozen) {
-			return false;
-		}
-		return canAccess(repository, AccessRestrictionType.PUSH, AccessPermission.PUSH);
-	}
-
-	public boolean canCreateRef(RepositoryModel repository) {
-		if (repository.isFrozen) {
-			return false;
-		}
-		return canAccess(repository, AccessRestrictionType.PUSH, AccessPermission.CREATE);
-	}
-
-	public boolean canDeleteRef(RepositoryModel repository) {
-		if (repository.isFrozen) {
-			return false;
-		}
-		return canAccess(repository, AccessRestrictionType.PUSH, AccessPermission.DELETE);
-	}
-
-	public boolean canRewindRef(RepositoryModel repository) {
-		if (repository.isFrozen) {
-			return false;
-		}
-		return canAccess(repository, AccessRestrictionType.PUSH, AccessPermission.REWIND);
-	}
-
-	public boolean hasUser(String name) {
-		return users.contains(name.toLowerCase());
-	}
-
-	public void addUser(String name) {
-		users.add(name.toLowerCase());
-	}
-
-	public void addUsers(Collection<String> names) {
-		for (String name:names) {
-			users.add(name.toLowerCase());
-		}
-	}
-
-	public void removeUser(String name) {
-		users.remove(name.toLowerCase());
-	}
-
-	public void addMailingLists(Collection<String> addresses) {
-		for (String address:addresses) {
-			mailingLists.add(address.toLowerCase());
-		}
-	}
-
-	@Override
-	public String toString() {
-		return name;
-	}
-
-	@Override
-	public int compareTo(TeamModel o) {
-		return name.compareTo(o.name);
-	}
-}
diff --git a/src/com/gitblit/models/UserModel.java b/src/com/gitblit/models/UserModel.java
deleted file mode 100644
index bec011d..0000000
--- a/src/com/gitblit/models/UserModel.java
+++ /dev/null
@@ -1,613 +0,0 @@
-/*
- * 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.models;
-
-import java.io.Serializable;
-import java.security.Principal;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.LinkedHashMap;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.TreeSet;
-
-import com.gitblit.Constants.AccessPermission;
-import com.gitblit.Constants.AccessRestrictionType;
-import com.gitblit.Constants.AccountType;
-import com.gitblit.Constants.AuthorizationControl;
-import com.gitblit.Constants.PermissionType;
-import com.gitblit.Constants.RegistrantType;
-import com.gitblit.Constants.Unused;
-import com.gitblit.utils.ArrayUtils;
-import com.gitblit.utils.StringUtils;
-
-/**
- * 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;
-	public String cookie;
-	public String displayName;
-	public String emailAddress;
-	public String organizationalUnit;
-	public String organization;
-	public String locality;
-	public String stateProvince;
-	public String countryCode;
-	public boolean canAdmin;
-	public boolean canFork;
-	public boolean canCreate;
-	public boolean excludeFromFederation;
-	// retained for backwards-compatibility with RPC clients
-	@Deprecated
-	public final Set<String> repositories = new HashSet<String>();
-	public final Map<String, AccessPermission> permissions = new LinkedHashMap<String, AccessPermission>();
-	public final Set<TeamModel> teams = new TreeSet<TeamModel>();
-
-	// non-persisted fields
-	public boolean isAuthenticated;
-	public AccountType accountType;
-	
-	public UserModel(String username) {
-		this.username = username;
-		this.isAuthenticated = true;
-		this.accountType = AccountType.LOCAL;
-	}
-
-	private UserModel() {
-		this.username = "$anonymous";
-		this.isAuthenticated = false;
-		this.accountType = AccountType.LOCAL;
-	}
-	
-	public boolean isLocalAccount() {
-		return accountType.isLocal();
-	}
-
-	/**
-	 * This method does not take into consideration Ownership where the
-	 * administrator has not explicitly granted access to the owner.
-	 * 
-	 * @param repositoryName
-	 * @return
-	 */
-	@Deprecated
-	public boolean canAccessRepository(String repositoryName) {
-		return canAdmin() || repositories.contains(repositoryName.toLowerCase())
-				|| hasTeamAccess(repositoryName);
-	}
-
-	@Deprecated
-	@Unused
-	public boolean canAccessRepository(RepositoryModel repository) {
-		boolean isOwner = repository.isOwner(username);
-		boolean allowAuthenticated = isAuthenticated && AuthorizationControl.AUTHENTICATED.equals(repository.authorizationControl);
-		return canAdmin() || isOwner || repositories.contains(repository.name.toLowerCase())
-				|| hasTeamAccess(repository.name) || allowAuthenticated;
-	}
-
-	@Deprecated
-	@Unused
-	public boolean hasTeamAccess(String repositoryName) {
-		for (TeamModel team : teams) {
-			if (team.hasRepositoryPermission(repositoryName)) {
-				return true;
-			}
-		}
-		return false;
-	}
-	
-	@Deprecated
-	@Unused
-	public boolean hasRepository(String name) {
-		return hasRepositoryPermission(name);
-	}
-
-	@Deprecated
-	@Unused
-	public void addRepository(String name) {
-		addRepositoryPermission(name);
-	}
-
-	@Deprecated
-	@Unused
-	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() {
-		List<RegistrantAccessPermission> list = new ArrayList<RegistrantAccessPermission>();
-		if (canAdmin()) {
-			// user has REWIND access to all repositories
-			return list;
-		}
-		for (Map.Entry<String, AccessPermission> entry : permissions.entrySet()) {
-			String registrant = entry.getKey();
-			AccessPermission ap = entry.getValue();
-			String source = null;
-			boolean mutable = true;
-			PermissionType pType = PermissionType.EXPLICIT;
-			if (isMyPersonalRepository(registrant)) {
-				pType = PermissionType.OWNER;
-				ap = AccessPermission.REWIND;
-				mutable = false;
-			} else if (StringUtils.findInvalidCharacter(registrant) != null) {
-				// a regex will have at least 1 invalid character
-				pType = PermissionType.REGEX;
-				source = registrant;
-			}
-			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) {
-			for (RegistrantAccessPermission teamPermission : team.getRepositoryPermissions()) {
-				// we can not change an inherited team permission, though we can override
-				teamPermission.registrantType = RegistrantType.REPOSITORY;
-				teamPermission.permissionType = PermissionType.TEAM;
-				teamPermission.source = team.name;
-				teamPermission.mutable = false;
-				set.add(teamPermission);
-			}
-		}
-		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
-	 */
-	public boolean hasRepositoryPermission(String name) {
-		String repository = AccessPermission.repositoryFromRole(name).toLowerCase();
-		if (permissions.containsKey(repository)) {
-			// exact repository permission specified
-			return true;
-		} else {
-			// search for regex permission match
-			for (String key : permissions.keySet()) {
-				if (name.matches(key)) {
-					AccessPermission p = permissions.get(key);
-					if (p != null) {
-						return true;
-					}
-				}
-			}
-		}
-		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
-	 */
-	public boolean hasExplicitRepositoryPermission(String name) {
-		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
-	 */
-	public boolean hasTeamRepositoryPermission(String name) {
-		if (teams != null) {
-			for (TeamModel team : teams) {
-				if (team.hasRepositoryPermission(name)) {
-					return true;
-				}
-			}
-		}
-		return false;
-	}
-	
-	/**
-	 * Adds a repository permission to the team.
-	 * <p>
-	 * Role may be formatted as:
-	 * <ul>
-	 * <li> myrepo.git <i>(this is implicitly RW+)</i>
-	 * <li> RW+:myrepo.git
-	 * </ul>
-	 * @param role
-	 */
-	public void addRepositoryPermission(String role) {
-		AccessPermission permission = AccessPermission.permissionFromRole(role);
-		String repository = AccessPermission.repositoryFromRole(role).toLowerCase();
-		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) {
-		permissions.put(repository.toLowerCase(), permission);
-	}
-
-	public RegistrantAccessPermission getRepositoryPermission(RepositoryModel repository) {
-		RegistrantAccessPermission ap = new RegistrantAccessPermission();
-		ap.registrant = username;
-		ap.registrantType = RegistrantType.USER;
-		ap.permission = AccessPermission.NONE;
-		ap.mutable = false;
-
-		if (AccessRestrictionType.NONE.equals(repository.accessRestriction)) {
-			// anonymous rewind
-			ap.permissionType = PermissionType.ADMINISTRATOR;
-			ap.permission = AccessPermission.REWIND;
-			return ap;
-		}
-
-		// administrator
-		if (canAdmin()) {
-			ap.permissionType = PermissionType.ADMINISTRATOR;
-			ap.permission = AccessPermission.REWIND;
-			if (!canAdmin) {
-				// administator permission from team membership
-				for (TeamModel team : teams) {
-					if (team.canAdmin) {
-						ap.source = team.name;
-						break;
-					}
-				}
-			}
-			return ap;
-		}
-		
-		// repository owner - either specified owner or personal repository
-		if (repository.isOwner(username) || repository.isUsersPersonalRepository(username)) {
-			ap.permissionType = PermissionType.OWNER;
-			ap.permission = AccessPermission.REWIND;
-			return ap;
-		}
-		
-		if (AuthorizationControl.AUTHENTICATED.equals(repository.authorizationControl) && isAuthenticated) {
-			// AUTHENTICATED is a shortcut for authorizing all logged-in users RW+ access
-			ap.permission = AccessPermission.REWIND;
-			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())) {
-			// exact repository permission specified, use it
-			AccessPermission p = permissions.get(repository.name.toLowerCase());
-			if (p != null) {
-				ap.permissionType = PermissionType.EXPLICIT;
-				ap.permission = p;
-				ap.mutable = true;
-				return ap;
-			}
-		} else {
-			// search for case-insensitive regex permission match
-			for (String key : permissions.keySet()) {
-				if (StringUtils.matchesIgnoreCase(repository.name, key)) {
-					AccessPermission p = permissions.get(key);
-					if (p != null) {
-						// take first match
-						ap.permissionType = PermissionType.REGEX;
-						ap.permission = p;
-						ap.source = key;
-						return ap;
-					}
-				}
-			}
-		}
-		
-		// try to find a team match
-		for (TeamModel team : teams) {
-			RegistrantAccessPermission p = team.getRepositoryPermission(repository);
-			if (p.permission.exceeds(ap.permission)) {
-				// use highest team permission
-				ap.permission = p.permission;
-				ap.source = team.name;
-				ap.permissionType = PermissionType.TEAM;
-			}
-		}		
-		
-		return ap;
-	}
-	
-	protected boolean canAccess(RepositoryModel repository, AccessRestrictionType ifRestriction, AccessPermission requirePermission) {
-		if (repository.accessRestriction.atLeast(ifRestriction)) {
-			RegistrantAccessPermission ap = getRepositoryPermission(repository);
-			return ap.permission.atLeast(requirePermission);
-		}
-		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.
-		return canView(repository);
-	}
-
-	public boolean canClone(RepositoryModel repository) {
-		return canAccess(repository, AccessRestrictionType.CLONE, AccessPermission.CLONE);
-	}
-
-	public boolean canPush(RepositoryModel repository) {
-		if (repository.isFrozen) {
-			return false;
-		}
-		return canAccess(repository, AccessRestrictionType.PUSH, AccessPermission.PUSH);
-	}
-
-	public boolean canCreateRef(RepositoryModel repository) {
-		if (repository.isFrozen) {
-			return false;
-		}
-		return canAccess(repository, AccessRestrictionType.PUSH, AccessPermission.CREATE);
-	}
-
-	public boolean canDeleteRef(RepositoryModel repository) {
-		if (repository.isFrozen) {
-			return false;
-		}
-		return canAccess(repository, AccessRestrictionType.PUSH, AccessPermission.DELETE);
-	}
-
-	public boolean canRewindRef(RepositoryModel repository) {
-		if (repository.isFrozen) {
-			return false;
-		}
-		return canAccess(repository, AccessRestrictionType.PUSH, AccessPermission.REWIND);
-	}
-
-	public boolean canFork(RepositoryModel repository) {
-		if (repository.isUsersPersonalRepository(username)) {
-			// can not fork your own repository
-			return false;
-		}
-		if (canAdmin() || repository.isOwner(username)) {
-			return true;
-		}
-		if (!repository.allowForks) {
-			return false;
-		}
-		if (!isAuthenticated || !canFork()) {
-			return false;
-		}
-		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() {
-		if (canFork) {
-			return true;
-		}
-		if (!ArrayUtils.isEmpty(teams)) {
-			for (TeamModel team : teams) {
-				if (team.canFork) {
-					return true;
-				}
-			}
-		}
-		return false;
-	}
-
-	/**
-	 * 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() {
-		if (canAdmin) {
-			return true;
-		}
-		if (!ArrayUtils.isEmpty(teams)) {
-			for (TeamModel team : teams) {
-				if (team.canAdmin) {
-					return true;
-				}
-			}
-		}
-		return false;
-	}
-
-	/**
-	 * 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() {
-		if (canCreate) {
-			return true;
-		}
-		if (!ArrayUtils.isEmpty(teams)) {
-			for (TeamModel team : teams) {
-				if (team.canCreate) {
-					return true;
-				}
-			}
-		}
-		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
-	 */
-	public boolean canCreate(String repository) {
-		if (canAdmin()) {
-			// admins can create any repository
-			return true;
-		}
-		if (canCreate) {
-			String projectPath = StringUtils.getFirstPathElement(repository);
-			if (!StringUtils.isEmpty(projectPath) && projectPath.equalsIgnoreCase("~" + username)) {
-				// personal repository
-				return true;
-			}
-		}
-		return false;
-	}
-
-	public boolean isTeamMember(String teamname) {
-		for (TeamModel team : teams) {
-			if (team.name.equalsIgnoreCase(teamname)) {
-				return true;
-			}
-		}
-		return false;
-	}
-
-	public TeamModel getTeam(String teamname) {
-		if (teams == null) {
-			return null;
-		}
-		for (TeamModel team : teams) {
-			if (team.name.equalsIgnoreCase(teamname)) {
-				return team;
-			}
-		}
-		return null;
-	}
-
-	@Override
-	public String getName() {
-		return username;
-	}
-	
-	public String getDisplayName() {
-		if (StringUtils.isEmpty(displayName)) {
-			return username;
-		}
-		return displayName;
-	}
-	
-	public String getPersonalPath() {
-		return "~" + username;
-	}
-	
-	@Override
-	public int hashCode() {
-		return username.hashCode();
-	}
-	
-	@Override
-	public boolean equals(Object o) {
-		if (o instanceof UserModel) {
-			return username.equals(((UserModel) o).username);
-		}
-		return false;
-	}
-
-	@Override
-	public String toString() {
-		return username;
-	}
-
-	@Override
-	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
-	 */
-	public boolean is(String name, String email) {
-		// at a minimum a usename or display name must be supplied
-		if (StringUtils.isEmpty(name)) {
-			return false;
-		}
-		boolean nameVerified = name.equalsIgnoreCase(username) || name.equalsIgnoreCase(getDisplayName());
-		boolean emailVerified = false;
-		if (StringUtils.isEmpty(emailAddress)) {
-			// user account has not specified an email address
-			// rely on username/displayname verification
-			emailVerified = true;
-		} else {
-			// user account has specified an email address
-			// require email address verification
-			if (!StringUtils.isEmpty(email)) {
-				emailVerified = email.equalsIgnoreCase(emailAddress);
-			}
-		}
-		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("~" + username);
-	}
-}
diff --git a/src/com/gitblit/utils/ActivityUtils.java b/src/com/gitblit/utils/ActivityUtils.java
deleted file mode 100644
index 732fdeb..0000000
--- a/src/com/gitblit/utils/ActivityUtils.java
+++ /dev/null
@@ -1,205 +0,0 @@
-/*
- * Copyright 2011 gitblit.com.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.gitblit.utils;
-
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.lang.reflect.Type;
-import java.text.DateFormat;
-import java.text.MessageFormat;
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Calendar;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.TimeZone;
-
-import org.eclipse.jgit.lib.Constants;
-import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.revwalk.RevCommit;
-
-import com.gitblit.GitBlit;
-import com.gitblit.models.Activity;
-import com.gitblit.models.GravatarProfile;
-import com.gitblit.models.RefModel;
-import com.gitblit.models.RepositoryCommit;
-import com.gitblit.models.RepositoryModel;
-import com.google.gson.reflect.TypeToken;
-
-/**
- * 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
-	 *            the number of days back from Now to collect
-	 * @param objectId
-	 *            the branch to retrieve. If this value is null or empty all
-	 *            branches are queried.
-	 * @param timezone
-	 *            the timezone for aggregating commits
-	 * @return
-	 */
-	public static List<Activity> getRecentActivity(List<RepositoryModel> models, int daysBack,
-			String objectId, TimeZone timezone) {
-
-		// Activity panel shows last daysBack of activity across all
-		// repositories.
-		Date thresholdDate = new Date(System.currentTimeMillis() - daysBack * TimeUtils.ONEDAY);
-
-		// Build a map of DailyActivity from the available repositories for the
-		// specified threshold date.
-		DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
-		df.setTimeZone(timezone);
-		Calendar cal = Calendar.getInstance();
-		cal.setTimeZone(timezone);
-
-		Map<String, Activity> activity = new HashMap<String, Activity>();
-		for (RepositoryModel model : models) {
-			if (model.maxActivityCommits == -1) {
-				// skip this repository
-				continue;
-			}
-			if (model.hasCommits && model.lastChange.after(thresholdDate)) {
-				if (model.isCollectingGarbage) {
-					continue;
-				}
-				Repository repository = GitBlit.self()
-						.getRepository(model.name);
-				List<String> branches = new ArrayList<String>();
-				if (StringUtils.isEmpty(objectId)) {
-					for (RefModel local : JGitUtils.getLocalBranches(
-							repository, true, -1)) {
-						branches.add(local.getName());
-					}
-				} else {
-					branches.add(objectId);
-				}
-				Map<ObjectId, List<RefModel>> allRefs = JGitUtils
-						.getAllRefs(repository, model.showRemoteBranches);
-
-				for (String branch : branches) {
-					String shortName = branch;
-					if (shortName.startsWith(Constants.R_HEADS)) {
-						shortName = shortName.substring(Constants.R_HEADS.length());
-					}
-					List<RevCommit> commits = JGitUtils.getRevLog(repository,
-							branch, thresholdDate);
-					if (model.maxActivityCommits > 0 && commits.size() > model.maxActivityCommits) {
-						// trim commits to maximum count
-						commits = commits.subList(0,  model.maxActivityCommits);
-					}
-					for (RevCommit commit : commits) {						
-						Date date = JGitUtils.getCommitDate(commit);
-						String dateStr = df.format(date);
-						if (!activity.containsKey(dateStr)) {
-							// Normalize the date to midnight
-							cal.setTime(date);
-							cal.set(Calendar.HOUR_OF_DAY, 0);
-							cal.set(Calendar.MINUTE, 0);
-							cal.set(Calendar.SECOND, 0);
-							cal.set(Calendar.MILLISECOND, 0);
-							activity.put(dateStr, new Activity(cal.getTime()));
-						}
-						RepositoryCommit commitModel = activity.get(dateStr)
-								.addCommit(model.name, shortName, commit);
-						if (commitModel != null) {
-							commitModel.setRefs(allRefs.get(commit.getId()));
-						}
-					}
-				}
-				
-				// close the repository
-				repository.close();
-			}
-		}
-
-		List<Activity> recentActivity = new ArrayList<Activity>(activity.values());
-		return recentActivity;
-	}
-
-	/**
-	 * Returns the Gravatar profile, if available, for the specified email
-	 * address.
-	 * 
-	 * @param emailaddress
-	 * @return a Gravatar Profile
-	 * @throws IOException
-	 */
-	public static GravatarProfile getGravatarProfileFromAddress(String emailaddress)
-			throws IOException {
-		return getGravatarProfile(StringUtils.getMD5(emailaddress.toLowerCase()));
-	}
-
-	/**
-	 * Creates a Gravatar thumbnail url from the specified email address.
-	 * 
-	 * @param email
-	 *            address to query Gravatar
-	 * @param width
-	 *            size of thumbnail. if width <= 0, the default of 50 is used.
-	 * @return
-	 */
-	public static String getGravatarThumbnailUrl(String email, int width) {
-		if (width <= 0) {
-			width = 50;
-		}
-		String emailHash = StringUtils.getMD5(email);
-		String url = MessageFormat.format(
-				"https://www.gravatar.com/avatar/{0}?s={1,number,0}&d=identicon", emailHash, width);
-		return url;
-	}
-
-	/**
-	 * Returns the Gravatar profile, if available, for the specified hashcode.
-	 * address.
-	 * 
-	 * @param hash
-	 *            the hash of the email address
-	 * @return a Gravatar Profile
-	 * @throws IOException
-	 */
-	public static GravatarProfile getGravatarProfile(String hash) throws IOException {
-		String url = MessageFormat.format("https://www.gravatar.com/{0}.json", hash);
-		// Gravatar has a complex json structure
-		Type profileType = new TypeToken<Map<String, List<GravatarProfile>>>() {
-		}.getType();
-		Map<String, List<GravatarProfile>> profiles = null;
-		try {
-			profiles = JsonUtils.retrieveJson(url, profileType);
-		} catch (FileNotFoundException e) {
-		}
-		if (profiles == null || profiles.size() == 0) {
-			return null;
-		}
-		// due to the complex json structure we need to pull out the profile
-		// from a list 2 levels deep
-		GravatarProfile profile = profiles.values().iterator().next().get(0);
-		return profile;
-	}
-}
diff --git a/src/com/gitblit/utils/ConnectionUtils.java b/src/com/gitblit/utils/ConnectionUtils.java
deleted file mode 100644
index f0b4111..0000000
--- a/src/com/gitblit/utils/ConnectionUtils.java
+++ /dev/null
@@ -1,214 +0,0 @@
-/*
- * Copyright 2011 gitblit.com.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.gitblit.utils;
-
-import java.io.IOException;
-import java.net.InetAddress;
-import java.net.Socket;
-import java.net.URL;
-import java.net.URLConnection;
-import java.net.UnknownHostException;
-import java.security.GeneralSecurityException;
-import java.security.SecureRandom;
-import java.security.cert.CertificateException;
-import java.security.cert.X509Certificate;
-
-import javax.net.SocketFactory;
-import javax.net.ssl.HostnameVerifier;
-import javax.net.ssl.HttpsURLConnection;
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.SSLSession;
-import javax.net.ssl.SSLSocketFactory;
-import javax.net.ssl.TrustManager;
-import javax.net.ssl.X509TrustManager;
-
-
-/**
- * Utility class for establishing HTTP/HTTPS connections.
- * 
- * @author James Moger
- * 
- */
-public class ConnectionUtils {
-
-	static final String CHARSET;
-
-	private static final SSLContext SSL_CONTEXT;
-
-	private static final DummyHostnameVerifier HOSTNAME_VERIFIER;
-
-	static {
-		SSLContext context = null;
-		try {
-			context = SSLContext.getInstance("SSL");
-			context.init(null, new TrustManager[] { new DummyTrustManager() }, new SecureRandom());
-		} catch (Throwable t) {
-			t.printStackTrace();
-		}
-		SSL_CONTEXT = context;
-		HOSTNAME_VERIFIER = new DummyHostnameVerifier();
-		CHARSET = "UTF-8";
-	}
-
-	public static void setAuthorization(URLConnection conn, String username, char[] password) {
-		if (!StringUtils.isEmpty(username) && (password != null && password.length > 0)) {
-			conn.setRequestProperty(
-					"Authorization",
-					"Basic "
-							+ Base64.encodeBytes((username + ":" + new String(password)).getBytes()));
-		}
-	}
-
-	public static URLConnection openReadConnection(String url, String username, char[] password)
-			throws IOException {
-		URLConnection conn = openConnection(url, username, password);
-		conn.setRequestProperty("Accept-Charset", ConnectionUtils.CHARSET);
-		return conn;
-	}
-
-	public static URLConnection openConnection(String url, String username, char[] password)
-			throws IOException {
-		URL urlObject = new URL(url);
-		URLConnection conn = urlObject.openConnection();
-		setAuthorization(conn, username, password);
-		conn.setUseCaches(false);
-		conn.setDoOutput(true);
-		if (conn instanceof HttpsURLConnection) {
-			HttpsURLConnection secureConn = (HttpsURLConnection) conn;
-			secureConn.setSSLSocketFactory(SSL_CONTEXT.getSocketFactory());
-			secureConn.setHostnameVerifier(HOSTNAME_VERIFIER);
-		}
-		return conn;
-	}
-		
-	// Copyright (C) 2009 The Android Open Source Project
-	//
-	// 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.
-	public static class BlindSSLSocketFactory extends SSLSocketFactory {
-		private static final BlindSSLSocketFactory INSTANCE;
-
-		static {
-			try {
-				final SSLContext context = SSLContext.getInstance("SSL");
-				final TrustManager[] trustManagers = { new DummyTrustManager() };
-				final SecureRandom rng = new SecureRandom();
-				context.init(null, trustManagers, rng);
-				INSTANCE = new BlindSSLSocketFactory(context.getSocketFactory());
-			} catch (GeneralSecurityException e) {
-				throw new RuntimeException("Cannot create BlindSslSocketFactory", e);
-			}
-		}
-
-		public static SocketFactory getDefault() {
-			return INSTANCE;
-		}
-
-		private final SSLSocketFactory sslFactory;
-
-		private BlindSSLSocketFactory(final SSLSocketFactory sslFactory) {
-			this.sslFactory = sslFactory;
-		}
-
-		@Override
-		public Socket createSocket(Socket s, String host, int port, boolean autoClose)
-				throws IOException {
-			return sslFactory.createSocket(s, host, port, autoClose);
-		}
-
-		@Override
-		public String[] getDefaultCipherSuites() {
-			return sslFactory.getDefaultCipherSuites();
-		}
-
-		@Override
-		public String[] getSupportedCipherSuites() {
-			return sslFactory.getSupportedCipherSuites();
-		}
-
-		@Override
-		public Socket createSocket() throws IOException {
-			return sslFactory.createSocket();
-		}
-
-		@Override
-		public Socket createSocket(String host, int port) throws IOException,
-		UnknownHostException {
-			return sslFactory.createSocket(host, port);
-		}
-
-		@Override
-		public Socket createSocket(InetAddress host, int port) throws IOException {
-			return sslFactory.createSocket(host, port);
-		}
-
-		@Override
-		public Socket createSocket(String host, int port, InetAddress localHost,
-				int localPort) throws IOException, UnknownHostException {
-			return sslFactory.createSocket(host, port, localHost, localPort);
-		}
-
-		@Override
-		public Socket createSocket(InetAddress address, int port,
-				InetAddress localAddress, int localPort) throws IOException {
-			return sslFactory.createSocket(address, port, localAddress, localPort);
-		}
-	}
-
-	/**
-	 * DummyTrustManager trusts all certificates.
-	 * 
-	 * @author James Moger
-	 */
-	private static class DummyTrustManager implements X509TrustManager {
-
-		@Override
-		public void checkClientTrusted(X509Certificate[] certs, String authType)
-				throws CertificateException {
-		}
-
-		@Override
-		public void checkServerTrusted(X509Certificate[] certs, String authType)
-				throws CertificateException {
-		}
-
-		@Override
-		public X509Certificate[] getAcceptedIssuers() {
-			return null;
-		}
-	}
-
-	/**
-	 * Trusts all hostnames from a certificate, including self-signed certs.
-	 * 
-	 * @author James Moger
-	 */
-	private static class DummyHostnameVerifier implements HostnameVerifier {
-		@Override
-		public boolean verify(String hostname, SSLSession session) {
-			return true;
-		}
-	}
-}
diff --git a/src/com/gitblit/utils/DiffUtils.java b/src/com/gitblit/utils/DiffUtils.java
deleted file mode 100644
index 04b5b0b..0000000
--- a/src/com/gitblit/utils/DiffUtils.java
+++ /dev/null
@@ -1,281 +0,0 @@
-/*
- * Copyright 2011 gitblit.com.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.gitblit.utils;
-
-import java.io.ByteArrayOutputStream;
-import java.util.ArrayList;
-import java.util.List;
-
-import org.eclipse.jgit.api.BlameCommand;
-import org.eclipse.jgit.blame.BlameResult;
-import org.eclipse.jgit.diff.DiffEntry;
-import org.eclipse.jgit.diff.DiffFormatter;
-import org.eclipse.jgit.diff.RawText;
-import org.eclipse.jgit.diff.RawTextComparator;
-import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.revwalk.RevCommit;
-import org.eclipse.jgit.revwalk.RevTree;
-import org.eclipse.jgit.revwalk.RevWalk;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.gitblit.models.AnnotatedLine;
-
-/**
- * DiffUtils is a class of utility methods related to diff, patch, and blame.
- * 
- * The diff methods support pluggable diff output types like Gitblit, Gitweb,
- * and Plain.
- * 
- * @author James Moger
- * 
- */
-public class DiffUtils {
-
-	private static final Logger LOGGER = LoggerFactory.getLogger(DiffUtils.class);
-
-	/**
-	 * Enumeration for the diff output types.
-	 */
-	public static enum DiffOutputType {
-		PLAIN, GITWEB, GITBLIT;
-
-		public static DiffOutputType forName(String name) {
-			for (DiffOutputType type : values()) {
-				if (type.name().equalsIgnoreCase(name)) {
-					return type;
-				}
-			}
-			return null;
-		}
-	}
-
-	/**
-	 * Returns the complete diff of the specified commit compared to its primary
-	 * parent.
-	 * 
-	 * @param repository
-	 * @param commit
-	 * @param outputType
-	 * @return the diff as a string
-	 */
-	public static String getCommitDiff(Repository repository, RevCommit commit,
-			DiffOutputType outputType) {
-		return getDiff(repository, null, commit, null, outputType);
-	}
-
-	/**
-	 * Returns the diff for the specified file or folder from the specified
-	 * commit compared to its primary parent.
-	 * 
-	 * @param repository
-	 * @param commit
-	 * @param path
-	 * @param outputType
-	 * @return the diff as a string
-	 */
-	public static String getDiff(Repository repository, RevCommit commit, String path,
-			DiffOutputType outputType) {
-		return getDiff(repository, null, commit, path, outputType);
-	}
-
-	/**
-	 * Returns the complete diff between the two specified commits.
-	 * 
-	 * @param repository
-	 * @param baseCommit
-	 * @param commit
-	 * @param outputType
-	 * @return the diff as a string
-	 */
-	public static String getDiff(Repository repository, RevCommit baseCommit, RevCommit commit,
-			DiffOutputType outputType) {
-		return getDiff(repository, baseCommit, commit, null, outputType);
-	}
-
-	/**
-	 * Returns the diff between two commits for the specified file.
-	 * 
-	 * @param repository
-	 * @param baseCommit
-	 *            if base commit is null the diff is to the primary parent of
-	 *            the commit.
-	 * @param commit
-	 * @param path
-	 *            if the path is specified, the diff is restricted to that file
-	 *            or folder. if unspecified, the diff is for the entire commit.
-	 * @param outputType
-	 * @return the diff as a string
-	 */
-	public static String getDiff(Repository repository, RevCommit baseCommit, RevCommit commit,
-			String path, DiffOutputType outputType) {
-		String diff = null;
-		try {
-			final ByteArrayOutputStream os = new ByteArrayOutputStream();
-			RawTextComparator cmp = RawTextComparator.DEFAULT;
-			DiffFormatter df;
-			switch (outputType) {
-			case GITWEB:
-				df = new GitWebDiffFormatter(os);
-				break;
-			case GITBLIT:
-				df = new GitBlitDiffFormatter(os);
-				break;
-			case PLAIN:
-			default:
-				df = new DiffFormatter(os);
-				break;
-			}
-			df.setRepository(repository);
-			df.setDiffComparator(cmp);
-			df.setDetectRenames(true);
-
-			RevTree commitTree = commit.getTree();
-			RevTree baseTree;
-			if (baseCommit == null) {
-				if (commit.getParentCount() > 0) {
-					final RevWalk rw = new RevWalk(repository);
-					RevCommit parent = rw.parseCommit(commit.getParent(0).getId());
-					rw.dispose();
-					baseTree = parent.getTree();
-				} else {
-					// FIXME initial commit. no parent?!
-					baseTree = commitTree;
-				}
-			} else {
-				baseTree = baseCommit.getTree();
-			}
-
-			List<DiffEntry> diffEntries = df.scan(baseTree, commitTree);
-			if (path != null && path.length() > 0) {
-				for (DiffEntry diffEntry : diffEntries) {
-					if (diffEntry.getNewPath().equalsIgnoreCase(path)) {
-						df.format(diffEntry);
-						break;
-					}
-				}
-			} else {
-				df.format(diffEntries);
-			}
-			if (df instanceof GitWebDiffFormatter) {
-				// workaround for complex private methods in DiffFormatter
-				diff = ((GitWebDiffFormatter) df).getHtml();
-			} else {
-				diff = os.toString();
-			}
-			df.flush();
-		} catch (Throwable t) {
-			LOGGER.error("failed to generate commit diff!", t);
-		}
-		return diff;
-	}
-
-	/**
-	 * Returns the diff between the two commits for the specified file or folder
-	 * formatted as a patch.
-	 * 
-	 * @param repository
-	 * @param baseCommit
-	 *            if base commit is unspecified, the patch is generated against
-	 *            the primary parent of the specified commit.
-	 * @param commit
-	 * @param path
-	 *            if path is specified, the patch is generated only for the
-	 *            specified file or folder. if unspecified, the patch is
-	 *            generated for the entire diff between the two commits.
-	 * @return patch as a string
-	 */
-	public static String getCommitPatch(Repository repository, RevCommit baseCommit,
-			RevCommit commit, String path) {
-		String diff = null;
-		try {
-			final ByteArrayOutputStream os = new ByteArrayOutputStream();
-			RawTextComparator cmp = RawTextComparator.DEFAULT;
-			PatchFormatter df = new PatchFormatter(os);
-			df.setRepository(repository);
-			df.setDiffComparator(cmp);
-			df.setDetectRenames(true);
-
-			RevTree commitTree = commit.getTree();
-			RevTree baseTree;
-			if (baseCommit == null) {
-				if (commit.getParentCount() > 0) {
-					final RevWalk rw = new RevWalk(repository);
-					RevCommit parent = rw.parseCommit(commit.getParent(0).getId());
-					baseTree = parent.getTree();
-				} else {
-					// FIXME initial commit. no parent?!
-					baseTree = commitTree;
-				}
-			} else {
-				baseTree = baseCommit.getTree();
-			}
-
-			List<DiffEntry> diffEntries = df.scan(baseTree, commitTree);
-			if (path != null && path.length() > 0) {
-				for (DiffEntry diffEntry : diffEntries) {
-					if (diffEntry.getNewPath().equalsIgnoreCase(path)) {
-						df.format(diffEntry);
-						break;
-					}
-				}
-			} else {
-				df.format(diffEntries);
-			}
-			diff = df.getPatch(commit);
-			df.flush();
-		} catch (Throwable t) {
-			LOGGER.error("failed to generate commit diff!", t);
-		}
-		return diff;
-	}
-
-	/**
-	 * Returns the list of lines in the specified source file annotated with the
-	 * source commit metadata.
-	 * 
-	 * @param repository
-	 * @param blobPath
-	 * @param objectId
-	 * @return list of annotated lines
-	 */
-	public static List<AnnotatedLine> blame(Repository repository, String blobPath, String objectId) {
-		List<AnnotatedLine> lines = new ArrayList<AnnotatedLine>();
-		try {
-			ObjectId object;
-			if (StringUtils.isEmpty(objectId)) {
-				object = JGitUtils.getDefaultBranch(repository);
-			} else {
-				object = repository.resolve(objectId);
-			}
-			BlameCommand blameCommand = new BlameCommand(repository);
-			blameCommand.setFilePath(blobPath);
-			blameCommand.setStartCommit(object);
-			BlameResult blameResult = blameCommand.call();
-			RawText rawText = blameResult.getResultContents();
-			int length = rawText.size();
-			for (int i = 0; i < length; i++) {
-				RevCommit commit = blameResult.getSourceCommit(i);
-				AnnotatedLine line = new AnnotatedLine(commit, i + 1, rawText.getString(i));
-				lines.add(line);
-			}
-		} catch (Throwable t) {
-			LOGGER.error("failed to generate blame!", t);
-		}
-		return lines;
-	}
-}
diff --git a/src/com/gitblit/utils/GitBlitDiffFormatter.java b/src/com/gitblit/utils/GitBlitDiffFormatter.java
deleted file mode 100644
index 2966aa8..0000000
--- a/src/com/gitblit/utils/GitBlitDiffFormatter.java
+++ /dev/null
@@ -1,164 +0,0 @@
-/*
- * Copyright 2011 gitblit.com.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.gitblit.utils;
-
-import static org.eclipse.jgit.lib.Constants.encode;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-
-import org.eclipse.jgit.diff.RawText;
-import org.eclipse.jgit.util.RawParseUtils;
-
-/**
- * Generates an html snippet of a diff in Gitblit's style.
- * 
- * @author James Moger
- * 
- */
-public class GitBlitDiffFormatter extends GitWebDiffFormatter {
-
-	private final OutputStream os;
-
-	private int left, right;
-
-	public GitBlitDiffFormatter(OutputStream os) {
-		super(os);
-		this.os = os;
-	}
-
-	/**
-	 * Output a hunk header
-	 * 
-	 * @param aStartLine
-	 *            within first source
-	 * @param aEndLine
-	 *            within first source
-	 * @param bStartLine
-	 *            within second source
-	 * @param bEndLine
-	 *            within second source
-	 * @throws IOException
-	 */
-	@Override
-	protected void writeHunkHeader(int aStartLine, int aEndLine, int bStartLine, int bEndLine)
-			throws IOException {
-		os.write("<tr><th>..</th><th>..</th><td class='hunk_header'>".getBytes());
-		os.write('@');
-		os.write('@');
-		writeRange('-', aStartLine + 1, aEndLine - aStartLine);
-		writeRange('+', bStartLine + 1, bEndLine - bStartLine);
-		os.write(' ');
-		os.write('@');
-		os.write('@');
-		os.write("</td></tr>\n".getBytes());
-		left = aStartLine + 1;
-		right = bStartLine + 1;
-	}
-
-	@Override
-	protected void writeLine(final char prefix, final RawText text, final int cur)
-			throws IOException {
-		os.write("<tr>".getBytes());
-		switch (prefix) {
-		case '+':
-			os.write(("<th></th><th>" + (right++) + "</th>").getBytes());
-			os.write("<td><div class=\"diff add2\">".getBytes());
-			break;
-		case '-':
-			os.write(("<th>" + (left++) + "</th><th></th>").getBytes());
-			os.write("<td><div class=\"diff remove2\">".getBytes());
-			break;
-		default:
-			os.write(("<th>" + (left++) + "</th><th>" + (right++) + "</th>").getBytes());
-			os.write("<td>".getBytes());
-			break;
-		}
-		os.write(prefix);
-		String line = text.getString(cur);
-		line = StringUtils.escapeForHtml(line, false);
-		os.write(encode(line));
-		switch (prefix) {
-		case '+':
-		case '-':
-			os.write("</div>".getBytes());
-			break;
-		default:
-			os.write("</td>".getBytes());
-		}
-		os.write("</tr>\n".getBytes());
-	}
-
-	/**
-	 * Workaround function for complex private methods in DiffFormatter. This
-	 * sets the html for the diff headers.
-	 * 
-	 * @return
-	 */
-	@Override
-	public String getHtml() {
-		ByteArrayOutputStream bos = (ByteArrayOutputStream) os;
-		String html = RawParseUtils.decode(bos.toByteArray());
-		String[] lines = html.split("\n");
-		StringBuilder sb = new StringBuilder();
-		boolean inFile = false;
-		String oldnull = "a/dev/null";
-		for (String line : lines) {
-			if (line.startsWith("index")) {
-				// skip index lines
-			} else if (line.startsWith("new file")) {
-				// skip new file lines
-			} else if (line.startsWith("\\ No newline")) {
-				// skip no new line
-			} else if (line.startsWith("---") || line.startsWith("+++")) {
-				// skip --- +++ lines
-			} else if (line.startsWith("diff")) {
-				line = StringUtils.convertOctal(line);
-				if (line.indexOf(oldnull) > -1) {
-					// a is null, use b
-					line = line.substring(("diff --git " + oldnull).length()).trim();
-					// trim b/
-					line = line.substring(2).trim();
-				} else {
-					// use a
-					line = line.substring("diff --git ".length()).trim();
-					line = line.substring(line.startsWith("\"a/") ? 3 : 2);					
-					line = line.substring(0, line.indexOf(" b/") > -1 ? line.indexOf(" b/") : line.indexOf("\"b/")).trim();
-				}
-				
-				if (line.charAt(0) == '"') {
-					line = line.substring(1);
-				}
-				if (line.charAt(line.length() - 1) == '"') {
-					line = line.substring(0, line.length() - 1);
-				}
-				if (inFile) {
-					sb.append("</tbody></table></div>\n");
-					inFile = false;
-				}
-				sb.append("<div class='header'>").append(line).append("</div>");
-				sb.append("<div class=\"diff\">");
-				sb.append("<table><tbody>");
-				inFile = true;
-			} else {
-				sb.append(line);
-			}
-		}
-		sb.append("</table></div>");
-		return sb.toString();
-	}
-}
diff --git a/src/com/gitblit/utils/JGitUtils.java b/src/com/gitblit/utils/JGitUtils.java
deleted file mode 100644
index 1f2ae94..0000000
--- a/src/com/gitblit/utils/JGitUtils.java
+++ /dev/null
@@ -1,1775 +0,0 @@
-/*
- * Copyright 2011 gitblit.com.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.gitblit.utils;
-
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.text.MessageFormat;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.regex.Pattern;
-
-import org.eclipse.jgit.api.CloneCommand;
-import org.eclipse.jgit.api.FetchCommand;
-import org.eclipse.jgit.api.Git;
-import org.eclipse.jgit.api.errors.GitAPIException;
-import org.eclipse.jgit.diff.DiffEntry;
-import org.eclipse.jgit.diff.DiffEntry.ChangeType;
-import org.eclipse.jgit.diff.DiffFormatter;
-import org.eclipse.jgit.diff.RawTextComparator;
-import org.eclipse.jgit.errors.ConfigInvalidException;
-import org.eclipse.jgit.errors.IncorrectObjectTypeException;
-import org.eclipse.jgit.errors.MissingObjectException;
-import org.eclipse.jgit.errors.StopWalkException;
-import org.eclipse.jgit.lib.BlobBasedConfig;
-import org.eclipse.jgit.lib.CommitBuilder;
-import org.eclipse.jgit.lib.Constants;
-import org.eclipse.jgit.lib.FileMode;
-import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.ObjectInserter;
-import org.eclipse.jgit.lib.ObjectLoader;
-import org.eclipse.jgit.lib.PersonIdent;
-import org.eclipse.jgit.lib.Ref;
-import org.eclipse.jgit.lib.RefUpdate;
-import org.eclipse.jgit.lib.RefUpdate.Result;
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.lib.RepositoryCache.FileKey;
-import org.eclipse.jgit.lib.TreeFormatter;
-import org.eclipse.jgit.revwalk.RevBlob;
-import org.eclipse.jgit.revwalk.RevCommit;
-import org.eclipse.jgit.revwalk.RevObject;
-import org.eclipse.jgit.revwalk.RevSort;
-import org.eclipse.jgit.revwalk.RevTree;
-import org.eclipse.jgit.revwalk.RevWalk;
-import org.eclipse.jgit.revwalk.filter.CommitTimeRevFilter;
-import org.eclipse.jgit.revwalk.filter.RevFilter;
-import org.eclipse.jgit.storage.file.FileRepository;
-import org.eclipse.jgit.transport.CredentialsProvider;
-import org.eclipse.jgit.transport.FetchResult;
-import org.eclipse.jgit.transport.RefSpec;
-import org.eclipse.jgit.treewalk.TreeWalk;
-import org.eclipse.jgit.treewalk.filter.AndTreeFilter;
-import org.eclipse.jgit.treewalk.filter.OrTreeFilter;
-import org.eclipse.jgit.treewalk.filter.PathFilter;
-import org.eclipse.jgit.treewalk.filter.PathFilterGroup;
-import org.eclipse.jgit.treewalk.filter.PathSuffixFilter;
-import org.eclipse.jgit.treewalk.filter.TreeFilter;
-import org.eclipse.jgit.util.FS;
-import org.eclipse.jgit.util.io.DisabledOutputStream;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.gitblit.models.GitNote;
-import com.gitblit.models.PathModel;
-import com.gitblit.models.PathModel.PathChangeModel;
-import com.gitblit.models.RefModel;
-import com.gitblit.models.SubmoduleModel;
-
-/**
- * Collection of static methods for retrieving information from a repository.
- * 
- * @author James Moger
- * 
- */
-public class JGitUtils {
-
-	static final Logger LOGGER = LoggerFactory.getLogger(JGitUtils.class);
-
-	/**
-	 * Log an error message and exception.
-	 * 
-	 * @param t
-	 * @param repository
-	 *            if repository is not null it MUST be the {0} parameter in the
-	 *            pattern.
-	 * @param pattern
-	 * @param objects
-	 */
-	private static void error(Throwable t, Repository repository, String pattern, Object... objects) {
-		List<Object> parameters = new ArrayList<Object>();
-		if (objects != null && objects.length > 0) {
-			for (Object o : objects) {
-				parameters.add(o);
-			}
-		}
-		if (repository != null) {
-			parameters.add(0, repository.getDirectory().getAbsolutePath());
-		}
-		LOGGER.error(MessageFormat.format(pattern, parameters.toArray()), t);
-	}
-
-	/**
-	 * 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"
-	 */
-	public static String getDisplayName(PersonIdent person) {
-		if (StringUtils.isEmpty(person.getEmailAddress())) {
-			return person.getName();
-		}
-		final StringBuilder r = new StringBuilder();
-		r.append(person.getName());
-		r.append(" <");
-		r.append(person.getEmailAddress());
-		r.append('>');
-		return r.toString().trim();
-	}
-
-	/**
-	 * Encapsulates the result of cloning or pulling from a repository.
-	 */
-	public static class CloneResult {
-		public String name;
-		public FetchResult fetchResult;
-		public boolean createdRepository;
-	}
-
-	/**
-	 * 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
-	 * @return CloneResult
-	 * @throws Exception
-	 */
-	public static CloneResult cloneRepository(File repositoriesFolder, String name, String fromUrl)
-			throws Exception {
-		return cloneRepository(repositoriesFolder, name, fromUrl, true, null);
-	}
-
-	/**
-	 * 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
-	 * @param bare
-	 * @param credentialsProvider
-	 * @return CloneResult
-	 * @throws Exception
-	 */
-	public static CloneResult cloneRepository(File repositoriesFolder, String name, String fromUrl,
-			boolean bare, CredentialsProvider credentialsProvider) throws Exception {
-		CloneResult result = new CloneResult();
-		if (bare) {
-			// bare repository, ensure .git suffix
-			if (!name.toLowerCase().endsWith(Constants.DOT_GIT_EXT)) {
-				name += Constants.DOT_GIT_EXT;
-			}
-		} else {
-			// normal repository, strip .git suffix
-			if (name.toLowerCase().endsWith(Constants.DOT_GIT_EXT)) {
-				name = name.substring(0, name.indexOf(Constants.DOT_GIT_EXT));
-			}
-		}
-		result.name = name;
-
-		File folder = new File(repositoriesFolder, name);
-		if (folder.exists()) {
-			File gitDir = FileKey.resolve(new File(repositoriesFolder, name), FS.DETECTED);
-			FileRepository repository = new FileRepository(gitDir);
-			result.fetchResult = fetchRepository(credentialsProvider, repository);
-			repository.close();
-		} else {
-			CloneCommand clone = new CloneCommand();
-			clone.setBare(bare);
-			clone.setCloneAllBranches(true);
-			clone.setURI(fromUrl);
-			clone.setDirectory(folder);
-			if (credentialsProvider != null) {
-				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;
-			result.fetchResult = fetchRepository(credentialsProvider, repository);
-			repository.close();
-		}
-		return result;
-	}
-
-	/**
-	 * Fetch updates from the remote repository. If refSpecs is unspecifed,
-	 * remote heads, tags, and notes are retrieved.
-	 * 
-	 * @param credentialsProvider
-	 * @param repository
-	 * @param refSpecs
-	 * @return FetchResult
-	 * @throws Exception
-	 */
-	public static FetchResult fetchRepository(CredentialsProvider credentialsProvider,
-			Repository repository, RefSpec... refSpecs) throws Exception {
-		Git git = new Git(repository);
-		FetchCommand fetch = git.fetch();
-		List<RefSpec> specs = new ArrayList<RefSpec>();
-		if (refSpecs == null || refSpecs.length == 0) {
-			specs.add(new RefSpec("+refs/heads/*:refs/remotes/origin/*"));
-			specs.add(new RefSpec("+refs/tags/*:refs/tags/*"));
-			specs.add(new RefSpec("+refs/notes/*:refs/notes/*"));
-		} else {
-			specs.addAll(Arrays.asList(refSpecs));
-		}
-		if (credentialsProvider != null) {
-			fetch.setCredentialsProvider(credentialsProvider);
-		}
-		fetch.setRefSpecs(specs);
-		FetchResult fetchRes = fetch.call();
-		return fetchRes;
-	}
-
-	/**
-	 * Creates a bare repository.
-	 * 
-	 * @param repositoriesFolder
-	 * @param name
-	 * @return Repository
-	 */
-	public static Repository createRepository(File repositoriesFolder, String name) {
-		try {
-			Git git = Git.init().setDirectory(new File(repositoriesFolder, name)).setBare(true).call();
-			return git.getRepository();
-		} catch (GitAPIException e) {
-			throw new RuntimeException(e);
-		}
-	}
-
-	/**
-	 * Returns a list of repository names in the specified folder.
-	 * 
-	 * @param repositoriesFolder
-	 * @param onlyBare
-	 *            if true, only bare repositories repositories are listed. If
-	 *            false all repositories are included.
-	 * @param searchSubfolders
-	 *            recurse into subfolders to find grouped repositories
-	 * @param depth
-	 *            optional recursion depth, -1 = infinite recursion
-	 * @param exclusions
-	 *            list of regex exclusions for matching to folder names
-	 * @return list of repository names
-	 */
-	public static List<String> getRepositoryList(File repositoriesFolder, boolean onlyBare,
-			boolean searchSubfolders, int depth, List<String> exclusions) {
-		List<String> list = new ArrayList<String>();
-		if (repositoriesFolder == null || !repositoriesFolder.exists()) {
-			return list;
-		}
-		List<Pattern> patterns = new ArrayList<Pattern>();
-		if (!ArrayUtils.isEmpty(exclusions)) {
-			for (String regex : exclusions) {
-				patterns.add(Pattern.compile(regex));
-			}
-		}
-		list.addAll(getRepositoryList(repositoriesFolder.getAbsolutePath(), repositoriesFolder,
-				onlyBare, searchSubfolders, depth, patterns));
-		StringUtils.sortRepositorynames(list);
-		return list;
-	}
-
-	/**
-	 * Recursive function to find git repositories.
-	 * 
-	 * @param basePath
-	 *            basePath is stripped from the repository name as repositories
-	 *            are relative to this path
-	 * @param searchFolder
-	 * @param onlyBare
-	 *            if true only bare repositories will be listed. if false all
-	 *            repositories are included.
-	 * @param searchSubfolders
-	 *            recurse into subfolders to find grouped repositories
-	 * @param depth
-	 *            recursion depth, -1 = infinite recursion
-	 * @param patterns
-	 *            list of regex patterns for matching to folder names
-	 * @return
-	 */
-	private static List<String> getRepositoryList(String basePath, File searchFolder,
-			boolean onlyBare, boolean searchSubfolders, int depth, List<Pattern> patterns) {
-		File baseFile = new File(basePath);
-		List<String> list = new ArrayList<String>();
-		if (depth == 0) {
-			return list;
-		}
-		
-		int nextDepth = (depth == -1) ? -1 : depth - 1;
-		for (File file : searchFolder.listFiles()) {
-			if (file.isDirectory()) {
-				boolean exclude = false;
-				for (Pattern pattern : patterns) {
-					String path = FileUtils.getRelativePath(baseFile, file).replace('\\',  '/');
-					if (pattern.matcher(path).matches()) {
-						LOGGER.debug(MessageFormat.format("excluding {0} because of rule {1}", path, pattern.pattern()));
-						exclude = true;
-						break;
-					}
-				}
-				if (exclude) {
-					// skip to next file
-					continue;
-				}
-
-				File gitDir = FileKey.resolve(new File(searchFolder, file.getName()), FS.DETECTED);
-				if (gitDir != null) {
-					if (onlyBare && gitDir.getName().equals(".git")) {
-						continue;
-					}
-					if (gitDir.equals(file) || gitDir.getParentFile().equals(file)) {
-						// determine repository name relative to base path
-						String repository = FileUtils.getRelativePath(baseFile, file);
-						list.add(repository);
-					} else if (searchSubfolders && file.canRead()) {
-						// look for repositories in subfolders
-						list.addAll(getRepositoryList(basePath, file, onlyBare, searchSubfolders,
-								nextDepth, patterns));
-					}
-				} else if (searchSubfolders && file.canRead()) {
-					// look for repositories in subfolders
-					list.addAll(getRepositoryList(basePath, file, onlyBare, searchSubfolders,
-							nextDepth, patterns));
-				}
-			}
-		}
-		return list;
-	}
-
-	/**
-	 * 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.
-	 * @return RevCommit
-	 */
-	public static RevCommit getFirstCommit(Repository repository, String branch) {
-		if (!hasCommits(repository)) {
-			return null;
-		}
-		RevCommit commit = null;
-		try {
-			// resolve branch
-			ObjectId branchObject;
-			if (StringUtils.isEmpty(branch)) {
-				branchObject = getDefaultBranch(repository);
-			} else {
-				branchObject = repository.resolve(branch);
-			}
-
-			RevWalk walk = new RevWalk(repository);
-			walk.sort(RevSort.REVERSE);
-			RevCommit head = walk.parseCommit(branchObject);
-			walk.markStart(head);
-			commit = walk.next();
-			walk.dispose();
-		} catch (Throwable t) {
-			error(t, repository, "{0} failed to determine first commit");
-		}
-		return commit;
-	}
-
-	/**
-	 * 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.
-	 * @return Date of the first commit on a branch
-	 */
-	public static Date getFirstChange(Repository repository, String branch) {
-		RevCommit commit = getFirstCommit(repository, branch);
-		if (commit == null) {
-			if (repository == null || !repository.getDirectory().exists()) {
-				return new Date(0);
-			}
-			// fresh repository
-			return new Date(repository.getDirectory().lastModified());
-		}
-		return getCommitDate(commit);
-	}
-
-	/**
-	 * 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
-	 */
-	public static boolean hasCommits(Repository repository) {
-		if (repository != null && repository.getDirectory().exists()) {
-			return (new File(repository.getDirectory(), "objects").list().length > 2)
-					|| (new File(repository.getDirectory(), "objects/pack").list().length > 0);
-		}
-		return false;
-	}
-
-	/**
-	 * Returns the date 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
-	 */
-	public static Date getLastChange(Repository repository) {
-		if (!hasCommits(repository)) {
-			// null repository
-			if (repository == null) {
-				return new Date(0);
-			}
-			// fresh repository
-			return new Date(repository.getDirectory().lastModified());
-		}
-
-		List<RefModel> branchModels = getLocalBranches(repository, true, -1);
-		if (branchModels.size() > 0) {
-			// find most recent branch update
-			Date lastChange = new Date(0);
-			for (RefModel branchModel : branchModels) {
-				if (branchModel.getDate().after(lastChange)) {
-					lastChange = branchModel.getDate();
-				}
-			}
-			return lastChange;
-		}
-		
-		// default to the repository folder modification date
-		return new Date(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
-	 */
-	public static Date getCommitDate(RevCommit commit) {
-		if (commit == null) {
-			return new Date(0);
-		}
-		return new Date(commit.getCommitTime() * 1000L);
-	}
-
-	/**
-	 * Retrieves a Java Date from a Git commit.
-	 * 
-	 * @param commit
-	 * @return date of the commit or Date(0) if the commit is null
-	 */
-	public static Date getAuthorDate(RevCommit commit) {
-		if (commit == null) {
-			return new Date(0);
-		}
-		return commit.getAuthorIdent().getWhen();
-	}
-
-	/**
-	 * 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.
-	 * @return RevCommit
-	 */
-	public static RevCommit getCommit(Repository repository, String objectId) {
-		if (!hasCommits(repository)) {
-			return null;
-		}
-		RevCommit commit = null;
-		try {
-			// resolve object id
-			ObjectId branchObject;
-			if (StringUtils.isEmpty(objectId)) {
-				branchObject = getDefaultBranch(repository);
-			} else {
-				branchObject = repository.resolve(objectId);
-			}
-			RevWalk walk = new RevWalk(repository);
-			RevCommit rev = walk.parseCommit(branchObject);
-			commit = rev;
-			walk.dispose();
-		} catch (Throwable t) {
-			error(t, repository, "{0} failed to get commit {1}", objectId);
-		}
-		return commit;
-	}
-
-	/**
-	 * Retrieves the raw byte content of a file in the specified tree.
-	 * 
-	 * @param repository
-	 * @param tree
-	 *            if null, the RevTree from HEAD is assumed.
-	 * @param path
-	 * @return content as a byte []
-	 */
-	public static byte[] getByteContent(Repository repository, RevTree tree, final String path, boolean throwError) {
-		RevWalk rw = new RevWalk(repository);
-		TreeWalk tw = new TreeWalk(repository);
-		tw.setFilter(PathFilterGroup.createFromStrings(Collections.singleton(path)));
-		byte[] content = null;
-		try {
-			if (tree == null) {
-				ObjectId object = getDefaultBranch(repository);
-				RevCommit commit = rw.parseCommit(object);
-				tree = commit.getTree();
-			}
-			tw.reset(tree);
-			while (tw.next()) {
-				if (tw.isSubtree() && !path.equals(tw.getPathString())) {
-					tw.enterSubtree();
-					continue;
-				}
-				ObjectId entid = tw.getObjectId(0);
-				FileMode entmode = tw.getFileMode(0);
-				if (entmode != FileMode.GITLINK) {
-					RevObject ro = rw.lookupAny(entid, entmode.getObjectType());
-					rw.parseBody(ro);
-					ByteArrayOutputStream os = new ByteArrayOutputStream();
-					ObjectLoader ldr = repository.open(ro.getId(), Constants.OBJ_BLOB);
-					byte[] tmp = new byte[4096];
-					InputStream in = ldr.openStream();
-					int n;
-					while ((n = in.read(tmp)) > 0) {
-						os.write(tmp, 0, n);
-					}
-					in.close();
-					content = os.toByteArray();
-				}
-			}
-		} catch (Throwable t) {
-			if (throwError) {
-				error(t, repository, "{0} can't find {1} in tree {2}", path, tree.name());
-			}
-		} finally {
-			rw.dispose();
-			tw.release();
-		}
-		return content;
-	}
-
-	/**
-	 * 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.
-	 * @param blobPath
-	 * @param charsets optional
-	 * @return UTF-8 string content
-	 */
-	public static String getStringContent(Repository repository, RevTree tree, String blobPath, String... charsets) {
-		byte[] content = getByteContent(repository, tree, blobPath, true);
-		if (content == null) {
-			return null;
-		}
-		return StringUtils.decodeString(content, charsets);
-	}
-
-	/**
-	 * Gets the raw byte content of the specified blob object.
-	 * 
-	 * @param repository
-	 * @param objectId
-	 * @return byte [] blob content
-	 */
-	public static byte[] getByteContent(Repository repository, String objectId) {
-		RevWalk rw = new RevWalk(repository);
-		byte[] content = null;
-		try {
-			RevBlob blob = rw.lookupBlob(ObjectId.fromString(objectId));
-			rw.parseBody(blob);
-			ByteArrayOutputStream os = new ByteArrayOutputStream();
-			ObjectLoader ldr = repository.open(blob.getId(), Constants.OBJ_BLOB);
-			byte[] tmp = new byte[4096];
-			InputStream in = ldr.openStream();
-			int n;
-			while ((n = in.read(tmp)) > 0) {
-				os.write(tmp, 0, n);
-			}
-			in.close();
-			content = os.toByteArray();
-		} catch (Throwable t) {
-			error(t, repository, "{0} can't find blob {1}", objectId);
-		} finally {
-			rw.dispose();
-		}
-		return content;
-	}
-
-	/**
-	 * Gets the UTF-8 string content of the blob specified by objectId.
-	 * 
-	 * @param repository
-	 * @param objectId
-	 * @param charsets optional
-	 * @return UTF-8 string content
-	 */
-	public static String getStringContent(Repository repository, String objectId, String... charsets) {
-		byte[] content = getByteContent(repository, objectId);
-		if (content == null) {
-			return null;
-		}
-		return StringUtils.decodeString(content, charsets);
-	}
-
-	/**
-	 * 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.
-	 * @param commit
-	 *            if null, HEAD is assumed.
-	 * @return list of files in specified path
-	 */
-	public static List<PathModel> getFilesInPath(Repository repository, String path,
-			RevCommit commit) {
-		List<PathModel> list = new ArrayList<PathModel>();
-		if (!hasCommits(repository)) {
-			return list;
-		}
-		if (commit == null) {
-			commit = getCommit(repository, null);
-		}
-		final TreeWalk tw = new TreeWalk(repository);
-		try {
-			tw.addTree(commit.getTree());
-			if (!StringUtils.isEmpty(path)) {
-				PathFilter f = PathFilter.create(path);
-				tw.setFilter(f);
-				tw.setRecursive(false);
-				boolean foundFolder = false;
-				while (tw.next()) {
-					if (!foundFolder && tw.isSubtree()) {
-						tw.enterSubtree();
-					}
-					if (tw.getPathString().equals(path)) {
-						foundFolder = true;
-						continue;
-					}
-					if (foundFolder) {
-						list.add(getPathModel(tw, path, commit));
-					}
-				}
-			} else {
-				tw.setRecursive(false);
-				while (tw.next()) {
-					list.add(getPathModel(tw, null, commit));
-				}
-			}
-		} catch (IOException e) {
-			error(e, repository, "{0} failed to get files for commit {1}", commit.getName());
-		} finally {
-			tw.release();
-		}
-		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.
-	 * @return list of files changed in a commit
-	 */
-	public static List<PathChangeModel> getFilesInCommit(Repository repository, RevCommit commit) {
-		List<PathChangeModel> list = new ArrayList<PathChangeModel>();
-		if (!hasCommits(repository)) {
-			return list;
-		}
-		RevWalk rw = new RevWalk(repository);
-		try {
-			if (commit == null) {
-				ObjectId object = getDefaultBranch(repository);
-				commit = rw.parseCommit(object);
-			}
-
-			if (commit.getParentCount() == 0) {
-				TreeWalk tw = new TreeWalk(repository);
-				tw.reset();
-				tw.setRecursive(true);
-				tw.addTree(commit.getTree());
-				while (tw.next()) {
-					list.add(new PathChangeModel(tw.getPathString(), tw.getPathString(), 0, tw
-							.getRawMode(0), tw.getObjectId(0).getName(), commit.getId().getName(),
-							ChangeType.ADD));
-				}
-				tw.release();
-			} else {
-				RevCommit parent = rw.parseCommit(commit.getParent(0).getId());
-				DiffFormatter df = new DiffFormatter(DisabledOutputStream.INSTANCE);
-				df.setRepository(repository);
-				df.setDiffComparator(RawTextComparator.DEFAULT);
-				df.setDetectRenames(true);
-				List<DiffEntry> diffs = df.scan(parent.getTree(), commit.getTree());
-				for (DiffEntry diff : diffs) {
-					String objectId = diff.getNewId().name();
-					if (diff.getChangeType().equals(ChangeType.DELETE)) {
-						list.add(new PathChangeModel(diff.getOldPath(), diff.getOldPath(), 0, diff
-								.getNewMode().getBits(), objectId, commit.getId().getName(), diff
-								.getChangeType()));
-					} else if (diff.getChangeType().equals(ChangeType.RENAME)) {
-						list.add(new PathChangeModel(diff.getOldPath(), diff.getNewPath(), 0, diff
-								.getNewMode().getBits(), objectId, commit.getId().getName(), diff
-								.getChangeType()));
-					} else {
-						list.add(new PathChangeModel(diff.getNewPath(), diff.getNewPath(), 0, diff
-								.getNewMode().getBits(), objectId, commit.getId().getName(), diff
-								.getChangeType()));
-					}
-				}
-			}
-		} catch (Throwable t) {
-			error(t, repository, "{0} failed to determine files in commit!");
-		} finally {
-			rw.dispose();
-		}
-		return list;
-	}
-
-	/**
-	 * 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
-	 */
-	public static List<PathModel> getDocuments(Repository repository, List<String> extensions) {
-		return getDocuments(repository, extensions, null);
-	}
-
-	/**
-	 * 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
-	 * @return list of files in repository with a matching extension
-	 */
-	public static List<PathModel> getDocuments(Repository repository, List<String> extensions,
-			String objectId) {
-		List<PathModel> list = new ArrayList<PathModel>();
-		if (!hasCommits(repository)) {
-			return list;
-		}
-		RevCommit commit = getCommit(repository, objectId);
-		final TreeWalk tw = new TreeWalk(repository);
-		try {
-			tw.addTree(commit.getTree());
-			if (extensions != null && extensions.size() > 0) {
-				List<TreeFilter> suffixFilters = new ArrayList<TreeFilter>();
-				for (String extension : extensions) {
-					if (extension.charAt(0) == '.') {
-						suffixFilters.add(PathSuffixFilter.create("\\" + extension));
-					} else {
-						// escape the . since this is a regexp filter
-						suffixFilters.add(PathSuffixFilter.create("\\." + extension));
-					}
-				}
-				TreeFilter filter;
-				if (suffixFilters.size() == 1) {
-					filter = suffixFilters.get(0);
-				} else {
-					filter = OrTreeFilter.create(suffixFilters);
-				}
-				tw.setFilter(filter);
-				tw.setRecursive(true);
-			}
-			while (tw.next()) {
-				list.add(getPathModel(tw, null, commit));
-			}
-		} catch (IOException e) {
-			error(e, repository, "{0} failed to get documents for commit {1}", commit.getName());
-		} finally {
-			tw.release();
-		}
-		Collections.sort(list);
-		return list;
-	}
-
-	/**
-	 * Returns a path model of the current file in the treewalk.
-	 * 
-	 * @param tw
-	 * @param basePath
-	 * @param commit
-	 * @return a path model of the current file in the treewalk
-	 */
-	private static PathModel getPathModel(TreeWalk tw, String basePath, RevCommit commit) {
-		String name;
-		long size = 0;
-		if (StringUtils.isEmpty(basePath)) {
-			name = tw.getPathString();
-		} else {
-			name = tw.getPathString().substring(basePath.length() + 1);
-		}
-		ObjectId objectId = tw.getObjectId(0);
-		try {
-			if (!tw.isSubtree() && (tw.getFileMode(0) != FileMode.GITLINK)) {
-				size = tw.getObjectReader().getObjectSize(objectId, Constants.OBJ_BLOB);
-			}
-		} catch (Throwable t) {
-			error(t, null, "failed to retrieve blob size for " + tw.getPathString());
-		}
-		return new PathModel(name, tw.getPathString(), size, tw.getFileMode(0).getBits(),
-				objectId.getName(), commit.getName());
-	}
-
-	/**
-	 * Returns a permissions representation of the mode bits.
-	 * 
-	 * @param mode
-	 * @return string representation of the mode bits
-	 */
-	public static String getPermissionsFromMode(int mode) {
-		if (FileMode.TREE.equals(mode)) {
-			return "drwxr-xr-x";
-		} else if (FileMode.REGULAR_FILE.equals(mode)) {
-			return "-rw-r--r--";
-		} else if (FileMode.EXECUTABLE_FILE.equals(mode)) {
-			return "-rwxr-xr-x";
-		} else if (FileMode.SYMLINK.equals(mode)) {
-			return "symlink";
-		} else if (FileMode.GITLINK.equals(mode)) {
-			return "submodule";
-		}
-		return "missing";
-	}
-
-	/**
-	 * Returns a list of commits since the minimum date starting from the
-	 * specified object id.
-	 * 
-	 * @param repository
-	 * @param objectId
-	 *            if unspecified, HEAD is assumed.
-	 * @param minimumDate
-	 * @return list of commits
-	 */
-	public static List<RevCommit> getRevLog(Repository repository, String objectId, Date minimumDate) {
-		List<RevCommit> list = new ArrayList<RevCommit>();
-		if (!hasCommits(repository)) {
-			return list;
-		}
-		try {
-			// resolve branch
-			ObjectId branchObject;
-			if (StringUtils.isEmpty(objectId)) {
-				branchObject = getDefaultBranch(repository);
-			} else {
-				branchObject = repository.resolve(objectId);
-			}
-
-			RevWalk rw = new RevWalk(repository);
-			rw.markStart(rw.parseCommit(branchObject));
-			rw.setRevFilter(CommitTimeRevFilter.after(minimumDate));
-			Iterable<RevCommit> revlog = rw;
-			for (RevCommit rev : revlog) {
-				list.add(rev);
-			}
-			rw.dispose();
-		} catch (Throwable t) {
-			error(t, repository, "{0} failed to get {1} revlog for minimum date {2}", objectId,
-					minimumDate);
-		}
-		return list;
-	}
-
-	/**
-	 * Returns a list of commits starting from HEAD and working backwards.
-	 * 
-	 * @param repository
-	 * @param maxCount
-	 *            if < 0, all commits for the repository are returned.
-	 * @return list of commits
-	 */
-	public static List<RevCommit> getRevLog(Repository repository, int maxCount) {
-		return getRevLog(repository, null, 0, maxCount);
-	}
-
-	/**
-	 * Returns a list of commits starting from the specified objectId using an
-	 * 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.
-	 * @param offset
-	 * @param maxCount
-	 *            if < 0, all commits are returned.
-	 * @return a paged list of commits
-	 */
-	public static List<RevCommit> getRevLog(Repository repository, String objectId, int offset,
-			int maxCount) {
-		return getRevLog(repository, objectId, null, offset, maxCount);
-	}
-
-	/**
-	 * Returns a list of commits for the repository or a path within the
-	 * 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.
-	 * @param path
-	 *            if unspecified, commits for repository are returned. If
-	 *            specified, commits for the path are returned.
-	 * @param offset
-	 * @param maxCount
-	 *            if < 0, all commits are returned.
-	 * @return a paged list of commits
-	 */
-	public static List<RevCommit> getRevLog(Repository repository, String objectId, String path,
-			int offset, int maxCount) {
-		List<RevCommit> list = new ArrayList<RevCommit>();
-		if (maxCount == 0) {
-			return list;
-		}
-		if (!hasCommits(repository)) {
-			return list;
-		}
-		try {
-			// resolve branch
-			ObjectId branchObject;
-			if (StringUtils.isEmpty(objectId)) {
-				branchObject = getDefaultBranch(repository);
-			} else {
-				branchObject = repository.resolve(objectId);
-			}
-			if (branchObject == null) {
-				return list;
-			}
-
-			RevWalk rw = new RevWalk(repository);
-			rw.markStart(rw.parseCommit(branchObject));
-			if (!StringUtils.isEmpty(path)) {
-				TreeFilter filter = AndTreeFilter.create(
-						PathFilterGroup.createFromStrings(Collections.singleton(path)),
-						TreeFilter.ANY_DIFF);
-				rw.setTreeFilter(filter);
-			}
-			Iterable<RevCommit> revlog = rw;
-			if (offset > 0) {
-				int count = 0;
-				for (RevCommit rev : revlog) {
-					count++;
-					if (count > offset) {
-						list.add(rev);
-						if (maxCount > 0 && list.size() == maxCount) {
-							break;
-						}
-					}
-				}
-			} else {
-				for (RevCommit rev : revlog) {
-					list.add(rev);
-					if (maxCount > 0 && list.size() == maxCount) {
-						break;
-					}
-				}
-			}
-			rw.dispose();
-		} catch (Throwable t) {
-			error(t, repository, "{0} failed to get {1} revlog for path {2}", objectId, path);
-		}
-		return list;
-	}
-
-	/**
-	 * 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)
-	 * @param endRangeId
-	 *            the end commit (included in results)
-	 * @return a list of commits
-	 */
-	public static List<RevCommit> getRevLog(Repository repository, String startRangeId,
-			String endRangeId) {
-		List<RevCommit> list = new ArrayList<RevCommit>();
-		if (!hasCommits(repository)) {
-			return list;
-		}
-		try {
-			ObjectId endRange = repository.resolve(endRangeId);
-			ObjectId startRange = repository.resolve(startRangeId);
-
-			RevWalk rw = new RevWalk(repository);
-			rw.markStart(rw.parseCommit(endRange));
-			if (startRange.equals(ObjectId.zeroId())) {
-				// maybe this is a tag or an orphan branch
-				list.add(rw.parseCommit(endRange));
-				rw.dispose();
-				return list;
-			} else {
-				rw.markUninteresting(rw.parseCommit(startRange));
-			}
-
-			Iterable<RevCommit> revlog = rw;
-			for (RevCommit rev : revlog) {
-				list.add(rev);
-			}
-			rw.dispose();
-		} catch (Throwable t) {
-			error(t, repository, "{0} failed to get revlog for {1}..{2}", startRangeId, endRangeId);
-		}
-		return list;
-	}
-
-	/**
-	 * Search the commit history for a case-insensitive match to the value.
-	 * 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.
-	 * @param value
-	 * @param type
-	 *            AUTHOR, COMMITTER, COMMIT
-	 * @param offset
-	 * @param maxCount
-	 *            if < 0, all matches are returned
-	 * @return matching list of commits
-	 */
-	public static List<RevCommit> searchRevlogs(Repository repository, String objectId,
-			String value, final com.gitblit.Constants.SearchType type, int offset, int maxCount) {
-		final String lcValue = value.toLowerCase();
-		List<RevCommit> list = new ArrayList<RevCommit>();
-		if (maxCount == 0) {
-			return list;
-		}
-		if (!hasCommits(repository)) {
-			return list;
-		}
-		try {
-			// resolve branch
-			ObjectId branchObject;
-			if (StringUtils.isEmpty(objectId)) {
-				branchObject = getDefaultBranch(repository);
-			} else {
-				branchObject = repository.resolve(objectId);
-			}
-
-			RevWalk rw = new RevWalk(repository);
-			rw.setRevFilter(new RevFilter() {
-
-				@Override
-				public RevFilter clone() {
-					// FindBugs complains about this method name.
-					// This is part of JGit design and unrelated to Cloneable.
-					return this;
-				}
-
-				@Override
-				public boolean include(RevWalk walker, RevCommit commit) throws StopWalkException,
-						MissingObjectException, IncorrectObjectTypeException, IOException {
-					boolean include = false;
-					switch (type) {
-					case AUTHOR:
-						include = (commit.getAuthorIdent().getName().toLowerCase().indexOf(lcValue) > -1)
-								|| (commit.getAuthorIdent().getEmailAddress().toLowerCase()
-										.indexOf(lcValue) > -1);
-						break;
-					case COMMITTER:
-						include = (commit.getCommitterIdent().getName().toLowerCase()
-								.indexOf(lcValue) > -1)
-								|| (commit.getCommitterIdent().getEmailAddress().toLowerCase()
-										.indexOf(lcValue) > -1);
-						break;
-					case COMMIT:
-						include = commit.getFullMessage().toLowerCase().indexOf(lcValue) > -1;
-						break;
-					}
-					return include;
-				}
-
-			});
-			rw.markStart(rw.parseCommit(branchObject));
-			Iterable<RevCommit> revlog = rw;
-			if (offset > 0) {
-				int count = 0;
-				for (RevCommit rev : revlog) {
-					count++;
-					if (count > offset) {
-						list.add(rev);
-						if (maxCount > 0 && list.size() == maxCount) {
-							break;
-						}
-					}
-				}
-			} else {
-				for (RevCommit rev : revlog) {
-					list.add(rev);
-					if (maxCount > 0 && list.size() == maxCount) {
-						break;
-					}
-				}
-			}
-			rw.dispose();
-		} catch (Throwable t) {
-			error(t, repository, "{0} failed to {1} search revlogs for {2}", type.name(), value);
-		}
-		return list;
-	}
-
-	/**
-	 * 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
-	 */
-	public static ObjectId getDefaultBranch(Repository repository) throws Exception {
-		ObjectId object = repository.resolve(Constants.HEAD);
-		if (object == null) {
-			// no HEAD
-			// perhaps non-standard repository, try local branches
-			List<RefModel> branchModels = getLocalBranches(repository, true, -1);
-			if (branchModels.size() > 0) {
-				// use most recently updated branch
-				RefModel branch = null;
-				Date lastDate = new Date(0);
-				for (RefModel branchModel : branchModels) {
-					if (branchModel.getDate().after(lastDate)) {
-						branch = branchModel;
-						lastDate = branch.getDate();
-					}
-				}
-				object = branch.getReferencedObjectId();
-			}
-		}
-		return object;
-	}
-
-	/**
-	 * Returns the target of the symbolic HEAD reference for a repository.
-	 * Normally returns a branch reference name, but when HEAD is detached,
-	 * the commit is matched against the known tags. The most recent matching
-	 * tag ref name will be returned if it references the HEAD commit. If
-	 * no match is found, the SHA1 is returned.
-	 *
-	 * @param repository
-	 * @return the ref name or the SHA1 for a detached HEAD
-	 */
-	public static String getHEADRef(Repository repository) {
-		String target = null;
-		try {
-			target = repository.getFullBranch();
-			if (!target.startsWith(Constants.R_HEADS)) {
-				// refers to an actual commit, probably a tag
-				// find latest tag that matches the commit, if any
-				List<RefModel> tagModels = getTags(repository, true, -1);
-				if (tagModels.size() > 0) {
-					RefModel tag = null;
-					Date lastDate = new Date(0);
-					for (RefModel tagModel : tagModels) {
-						if (tagModel.getReferencedObjectId().getName().equals(target) &&
-								tagModel.getDate().after(lastDate)) {
-							tag = tagModel;
-							lastDate = tag.getDate();
-						}
-					}
-					target = tag.getName();
-				}
-			}
-		} catch (Throwable t) {
-			error(t, repository, "{0} failed to get symbolic HEAD target");
-		}
-		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.
-	 *
-	 * @param repository
-	 * @param targetRef
-	 * @return true if successful
-	 */
-	public static boolean setHEADtoRef(Repository repository, String targetRef) {
-		try {
-			 // detach HEAD if target ref is not a branch
-			boolean detach = !targetRef.startsWith(Constants.R_HEADS);
-			RefUpdate.Result result;
-			RefUpdate head = repository.updateRef(Constants.HEAD, detach);
-			if (detach) { // Tag
-				RevCommit commit = getCommit(repository, targetRef);
-				head.setNewObjectId(commit.getId());
-				result = head.forceUpdate();
-			} else {
-				result = head.link(targetRef);
-			}
-			switch (result) {
-			case NEW:
-			case FORCED:
-			case NO_CHANGE:
-			case FAST_FORWARD:
-				return true;				
-			default:
-				LOGGER.error(MessageFormat.format("{0} HEAD update to {1} returned result {2}",
-						repository.getDirectory().getAbsolutePath(), targetRef, result));
-			}
-		} catch (Throwable t) {
-			error(t, repository, "{0} failed to set HEAD to {1}", targetRef);
-		}
-		return false;
-	}
-	
-	/**
-	 * Sets the local branch ref to point to the specified commit id.
-	 *
-	 * @param repository
-	 * @param branch
-	 * @param commitId
-	 * @return true if successful
-	 */
-	public static boolean setBranchRef(Repository repository, String branch, String commitId) {
-		String branchName = branch;
-		if (!branchName.startsWith(Constants.R_HEADS)) {
-			branchName = Constants.R_HEADS + branch;
-		}
-
-		try {
-			RefUpdate refUpdate = repository.updateRef(branchName, false);
-			refUpdate.setNewObjectId(ObjectId.fromString(commitId));
-			RefUpdate.Result result = refUpdate.forceUpdate();
-
-			switch (result) {
-			case NEW:
-			case FORCED:
-			case NO_CHANGE:
-			case FAST_FORWARD:
-				return true;				
-			default:
-				LOGGER.error(MessageFormat.format("{0} {1} update to {2} returned result {3}",
-						repository.getDirectory().getAbsolutePath(), branchName, commitId, result));
-			}
-		} catch (Throwable t) {
-			error(t, repository, "{0} failed to set {1} to {2}", branchName, commitId);
-		}
-		return false;
-	}
-	
-	/**
-	 * Deletes the specified branch ref.
-	 *  
-	 * @param repository
-	 * @param branch
-	 * @return true if successful
-	 */
-	public static boolean deleteBranchRef(Repository repository, String branch) {
-		String branchName = branch;
-		if (!branchName.startsWith(Constants.R_HEADS)) {
-			branchName = Constants.R_HEADS + branch;
-		}
-
-		try {
-			RefUpdate refUpdate = repository.updateRef(branchName, false);
-			refUpdate.setForceUpdate(true);
-			RefUpdate.Result result = refUpdate.delete();
-			switch (result) {
-			case NEW:
-			case FORCED:
-			case NO_CHANGE:
-			case FAST_FORWARD:
-				return true;				
-			default:
-				LOGGER.error(MessageFormat.format("{0} failed to delete to {1} returned result {2}",
-						repository.getDirectory().getAbsolutePath(), branchName, result));
-			}
-		} catch (Throwable t) {
-			error(t, repository, "{0} failed to delete {1}", branchName);
-		}
-		return false;
-	}
-	
-	/**
-	 * Get the full branch and tag ref names for any potential HEAD targets.
-	 *
-	 * @param repository
-	 * @return a list of ref names
-	 */
-	public static List<String> getAvailableHeadTargets(Repository repository) {
-		List<String> targets = new ArrayList<String>();
-		for (RefModel branchModel : JGitUtils.getLocalBranches(repository, true, -1)) {
-			targets.add(branchModel.getName());
-		}
-
-		for (RefModel tagModel : JGitUtils.getTags(repository, true, -1)) {
-			targets.add(tagModel.getName());
-		}
-		return targets;
-	}
-
-	/**
-	 * 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
-	 */
-	public static Map<ObjectId, List<RefModel>> getAllRefs(Repository repository, boolean includeRemoteRefs) {
-		List<RefModel> list = getRefs(repository, org.eclipse.jgit.lib.RefDatabase.ALL, true, -1);
-		Map<ObjectId, List<RefModel>> refs = new HashMap<ObjectId, List<RefModel>>();
-		for (RefModel ref : list) {
-			if (!includeRemoteRefs && ref.getName().startsWith(Constants.R_REMOTES)) {
-				continue;
-			}
-			ObjectId objectid = ref.getReferencedObjectId();
-			if (!refs.containsKey(objectid)) {
-				refs.put(objectid, new ArrayList<RefModel>());
-			}
-			refs.get(objectid).add(ref);
-		}
-		return refs;
-	}
-
-	/**
-	 * 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,
-	 *            yadayadayada is returned.
-	 * @param maxCount
-	 *            if < 0, all tags are returned
-	 * @return list of tags
-	 */
-	public static List<RefModel> getTags(Repository repository, boolean fullName, int maxCount) {
-		return getRefs(repository, Constants.R_TAGS, fullName, maxCount);
-	}
-
-	/**
-	 * 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,
-	 *            yadayadayada is returned.
-	 * @param maxCount
-	 *            if < 0, all local branches are returned
-	 * @return list of local branches
-	 */
-	public static List<RefModel> getLocalBranches(Repository repository, boolean fullName,
-			int maxCount) {
-		return getRefs(repository, Constants.R_HEADS, fullName, maxCount);
-	}
-
-	/**
-	 * 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,
-	 *            yadayadayada is returned.
-	 * @param maxCount
-	 *            if < 0, all remote branches are returned
-	 * @return list of remote branches
-	 */
-	public static List<RefModel> getRemoteBranches(Repository repository, boolean fullName,
-			int maxCount) {
-		return getRefs(repository, Constants.R_REMOTES, fullName, maxCount);
-	}
-
-	/**
-	 * 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,
-	 *            yadayadayada is returned.
-	 * @param maxCount
-	 *            if < 0, all note branches are returned
-	 * @return list of note branches
-	 */
-	public static List<RefModel> getNoteBranches(Repository repository, boolean fullName,
-			int maxCount) {
-		return getRefs(repository, Constants.R_NOTES, fullName, maxCount);
-	}
-	
-	/**
-	 * 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,
-	 *            yadayadayada is returned.
-	 * @return list of refs
-	 */
-	public static List<RefModel> getRefs(Repository repository, String baseRef) {
-		return getRefs(repository, baseRef, true, -1);
-	}
-
-	/**
-	 * 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
-	 * @param fullName
-	 *            if true, /refs/something/yadayadayada is returned. If false,
-	 *            yadayadayada is returned.
-	 * @param maxCount
-	 *            if < 0, all references are returned
-	 * @return list of references
-	 */
-	private static List<RefModel> getRefs(Repository repository, String refs, boolean fullName,
-			int maxCount) {
-		List<RefModel> list = new ArrayList<RefModel>();
-		if (maxCount == 0) {
-			return list;
-		}
-		if (!hasCommits(repository)) {
-			return list;
-		}
-		try {
-			Map<String, Ref> map = repository.getRefDatabase().getRefs(refs);
-			RevWalk rw = new RevWalk(repository);
-			for (Entry<String, Ref> entry : map.entrySet()) {
-				Ref ref = entry.getValue();
-				RevObject object = rw.parseAny(ref.getObjectId());
-				String name = entry.getKey();
-				if (fullName && !StringUtils.isEmpty(refs)) {
-					name = refs + name;
-				}
-				list.add(new RefModel(name, ref, object));
-			}
-			rw.dispose();
-			Collections.sort(list);
-			Collections.reverse(list);
-			if (maxCount > 0 && list.size() > maxCount) {
-				list = new ArrayList<RefModel>(list.subList(0, maxCount));
-			}
-		} catch (IOException e) {
-			error(e, repository, "{0} failed to retrieve {1}", refs);
-		}
-		return list;
-	}
-
-	/**
-	 * 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
-	 */
-	public static RefModel getPagesBranch(Repository repository) {
-		return getBranch(repository, "gh-pages");
-	}
-
-	/**
-	 * 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
-	 */
-	public static RefModel getBranch(Repository repository, String name) {
-		RefModel branch = null;
-		try {
-			// search for the branch in local heads
-			for (RefModel ref : JGitUtils.getLocalBranches(repository, false, -1)) {
-				if (ref.reference.getName().endsWith(name)) {
-					branch = ref;
-					break;
-				}
-			}
-
-			// search for the branch in remote heads
-			if (branch == null) {
-				for (RefModel ref : JGitUtils.getRemoteBranches(repository, false, -1)) {
-					if (ref.reference.getName().endsWith(name)) {
-						branch = ref;
-						break;
-					}
-				}
-			}
-		} catch (Throwable t) {
-			LOGGER.error(MessageFormat.format("Failed to find {0} branch!", name), t);
-		}
-		return branch;
-	}
-		
-	/**
-	 * Returns the list of submodules for this repository.
-	 * 
-	 * @param repository
-	 * @param commit
-	 * @return list of submodules
-	 */
-	public static List<SubmoduleModel> getSubmodules(Repository repository, String commitId) {
-		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
-	 */
-	public static List<SubmoduleModel> getSubmodules(Repository repository, RevTree tree) {
-		List<SubmoduleModel> list = new ArrayList<SubmoduleModel>();
-		byte [] blob = getByteContent(repository, tree, ".gitmodules", false);
-		if (blob == null) {
-			return list;
-		}
-		try {
-			BlobBasedConfig config = new BlobBasedConfig(repository.getConfig(), blob);
-			for (String module : config.getSubsections("submodule")) {
-				String path = config.getString("submodule", module, "path");
-				String url = config.getString("submodule", module, "url");
-				list.add(new SubmoduleModel(module, path, url));
-			}
-		} catch (ConfigInvalidException e) {
-			LOGGER.error("Failed to load .gitmodules file for " + repository.getDirectory(), e);
-		}
-		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
-	 * @return a submodule definition or null if there is no submodule
-	 */
-	public static SubmoduleModel getSubmoduleModel(Repository repository, String commitId, String path) {
-		for (SubmoduleModel model : getSubmodules(repository, commitId)) {
-			if (model.path.equals(path)) {
-				return model;
-			}
-		}
-		return null;
-	}
-	
-	public static String getSubmoduleCommitId(Repository repository, String path, RevCommit commit) {
-		String commitId = null;
-		RevWalk rw = new RevWalk(repository);
-		TreeWalk tw = new TreeWalk(repository);
-		tw.setFilter(PathFilterGroup.createFromStrings(Collections.singleton(path)));
-		try {
-			tw.reset(commit.getTree());
-			while (tw.next()) {
-				if (tw.isSubtree() && !path.equals(tw.getPathString())) {
-					tw.enterSubtree();
-					continue;
-				}
-				if (FileMode.GITLINK == tw.getFileMode(0)) {
-					commitId = tw.getObjectId(0).getName();
-					break;
-				}
-			}
-		} catch (Throwable t) {
-			error(t, repository, "{0} can't find {1} in commit {2}", path, commit.name());
-		} finally {
-			rw.dispose();
-			tw.release();
-		}
-		return commitId;
-	}
-
-	/**
-	 * 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
-	 */
-	public static List<GitNote> getNotesOnCommit(Repository repository, RevCommit commit) {
-		List<GitNote> list = new ArrayList<GitNote>();
-		if (!hasCommits(repository)) {
-			return list;
-		}
-		List<RefModel> noteBranches = getNoteBranches(repository, true, -1);
-		for (RefModel notesRef : noteBranches) {
-			RevTree notesTree = JGitUtils.getCommit(repository, notesRef.getName()).getTree();
-			// flat notes list
-			String notePath = commit.getName();
-			String text = getStringContent(repository, notesTree, notePath);
-			if (!StringUtils.isEmpty(text)) {
-				List<RevCommit> history = getRevLog(repository, notesRef.getName(), notePath, 0, -1);
-				RefModel noteRef = new RefModel(notesRef.displayName, null, history.get(history
-						.size() - 1));
-				GitNote gitNote = new GitNote(noteRef, text);
-				list.add(gitNote);
-				continue;
-			}
-			
-			// folder structure
-			StringBuilder sb = new StringBuilder(commit.getName());
-			sb.insert(2, '/');
-			notePath = sb.toString();
-			text = getStringContent(repository, notesTree, notePath);
-			if (!StringUtils.isEmpty(text)) {
-				List<RevCommit> history = getRevLog(repository, notesRef.getName(), notePath, 0, -1);
-				RefModel noteRef = new RefModel(notesRef.displayName, null, history.get(history
-						.size() - 1));
-				GitNote gitNote = new GitNote(noteRef, text);
-				list.add(gitNote);
-			}
-		}
-		return list;
-	}
-
-	/**
-	 * Create an orphaned branch in a repository.
-	 * 
-	 * @param repository
-	 * @param branchName
-	 * @param author
-	 *            if unspecified, Gitblit will be the author of this new branch
-	 * @return true if successful
-	 */
-	public static boolean createOrphanBranch(Repository repository, String branchName,
-			PersonIdent author) {
-		boolean success = false;
-		String message = "Created branch " + branchName;
-		if (author == null) {
-			author = new PersonIdent("Gitblit", "gitblit@localhost");
-		}
-		try {
-			ObjectInserter odi = repository.newObjectInserter();
-			try {
-				// Create a blob object to insert into a tree
-				ObjectId blobId = odi.insert(Constants.OBJ_BLOB,
-						message.getBytes(Constants.CHARACTER_ENCODING));
-
-				// Create a tree object to reference from a commit
-				TreeFormatter tree = new TreeFormatter();
-				tree.append(".branch", FileMode.REGULAR_FILE, blobId);
-				ObjectId treeId = odi.insert(tree);
-
-				// Create a commit object
-				CommitBuilder commit = new CommitBuilder();
-				commit.setAuthor(author);
-				commit.setCommitter(author);
-				commit.setEncoding(Constants.CHARACTER_ENCODING);
-				commit.setMessage(message);
-				commit.setTreeId(treeId);
-
-				// Insert the commit into the repository
-				ObjectId commitId = odi.insert(commit);
-				odi.flush();
-
-				RevWalk revWalk = new RevWalk(repository);
-				try {
-					RevCommit revCommit = revWalk.parseCommit(commitId);
-					if (!branchName.startsWith("refs/")) {
-						branchName = "refs/heads/" + branchName;
-					}
-					RefUpdate ru = repository.updateRef(branchName);
-					ru.setNewObjectId(commitId);
-					ru.setRefLogMessage("commit: " + revCommit.getShortMessage(), false);
-					Result rc = ru.forceUpdate();
-					switch (rc) {
-					case NEW:
-					case FORCED:
-					case FAST_FORWARD:
-						success = true;
-						break;
-					default:
-						success = false;
-					}
-				} finally {
-					revWalk.release();
-				}
-			} finally {
-				odi.release();
-			}
-		} catch (Throwable t) {
-			error(t, repository, "Failed to create orphan branch {1} in repository {0}", branchName);
-		}
-		return success;
-	}
-	
-	/**
-	 * Reads the sparkleshare id, if present, from the repository.
-	 * 
-	 * @param repository
-	 * @return an id or null
-	 */
-	public static String getSparkleshareId(Repository repository) {
-		byte[] content = getByteContent(repository, null, ".sparkleshare", false);
-		if (content == null) {
-			return null;
-		}
-		return StringUtils.decodeString(content);
-	}
-}
diff --git a/src/com/gitblit/utils/JsonUtils.java b/src/com/gitblit/utils/JsonUtils.java
deleted file mode 100644
index 24f4ecb..0000000
--- a/src/com/gitblit/utils/JsonUtils.java
+++ /dev/null
@@ -1,346 +0,0 @@
-/*
- * Copyright 2011 gitblit.com.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.gitblit.utils;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.OutputStream;
-import java.lang.reflect.Type;
-import java.net.HttpURLConnection;
-import java.net.URLConnection;
-import java.text.DateFormat;
-import java.text.ParseException;
-import java.text.SimpleDateFormat;
-import java.util.Collection;
-import java.util.Date;
-import java.util.Locale;
-import java.util.Map;
-import java.util.TimeZone;
-
-import com.gitblit.Constants.AccessPermission;
-import com.gitblit.GitBlitException.ForbiddenException;
-import com.gitblit.GitBlitException.NotAllowedException;
-import com.gitblit.GitBlitException.UnauthorizedException;
-import com.gitblit.GitBlitException.UnknownRequestException;
-import com.gitblit.models.RepositoryModel;
-import com.gitblit.models.UserModel;
-import com.google.gson.ExclusionStrategy;
-import com.google.gson.FieldAttributes;
-import com.google.gson.Gson;
-import com.google.gson.GsonBuilder;
-import com.google.gson.JsonDeserializationContext;
-import com.google.gson.JsonDeserializer;
-import com.google.gson.JsonElement;
-import com.google.gson.JsonPrimitive;
-import com.google.gson.JsonSerializationContext;
-import com.google.gson.JsonSerializer;
-import com.google.gson.JsonSyntaxException;
-import com.google.gson.reflect.TypeToken;
-
-/**
- * Utility methods for json calls to a Gitblit server.
- * 
- * @author James Moger
- * 
- */
-public class JsonUtils {
-
-	public static final Type REPOSITORIES_TYPE = new TypeToken<Map<String, RepositoryModel>>() {
-	}.getType();
-
-	public static final Type USERS_TYPE = new TypeToken<Collection<UserModel>>() {
-	}.getType();
-
-	/**
-	 * Creates JSON from the specified object.
-	 * 
-	 * @param o
-	 * @return json
-	 */
-	public static String toJsonString(Object o) {
-		String json = gson().toJson(o);
-		return json;
-	}
-
-	/**
-	 * Convert a json string to an object of the specified type.
-	 * 
-	 * @param json
-	 * @param clazz
-	 * @return an object
-	 */
-	public static <X> X fromJsonString(String json, Class<X> clazz) {
-		return gson().fromJson(json, clazz);
-	}
-
-	/**
-	 * Convert a json string to an object of the specified type.
-	 * 
-	 * @param json
-	 * @param clazz
-	 * @return an object
-	 */
-	public static <X> X fromJsonString(String json, Type type) {
-		return gson().fromJson(json, type);
-	}
-
-	/**
-	 * Reads a gson object from the specified url.
-	 * 
-	 * @param url
-	 * @param type
-	 * @return the deserialized object
-	 * @throws {@link IOException}
-	 */
-	public static <X> X retrieveJson(String url, Type type) throws IOException,
-			UnauthorizedException {
-		return retrieveJson(url, type, null, null);
-	}
-
-	/**
-	 * Reads a gson object from the specified url.
-	 * 
-	 * @param url
-	 * @param type
-	 * @return the deserialized object
-	 * @throws {@link IOException}
-	 */
-	public static <X> X retrieveJson(String url, Class<? extends X> clazz) throws IOException,
-			UnauthorizedException {
-		return retrieveJson(url, clazz, null, null);
-	}
-
-	/**
-	 * Reads a gson object from the specified url.
-	 * 
-	 * @param url
-	 * @param type
-	 * @param username
-	 * @param password
-	 * @return the deserialized object
-	 * @throws {@link IOException}
-	 */
-	public static <X> X retrieveJson(String url, Type type, String username, char[] password)
-			throws IOException {
-		String json = retrieveJsonString(url, username, password);
-		if (StringUtils.isEmpty(json)) {
-			return null;
-		}
-		return gson().fromJson(json, type);
-	}
-
-	/**
-	 * Reads a gson object from the specified url.
-	 * 
-	 * @param url
-	 * @param clazz
-	 * @param username
-	 * @param password
-	 * @return the deserialized object
-	 * @throws {@link IOException}
-	 */
-	public static <X> X retrieveJson(String url, Class<X> clazz, String username, char[] password)
-			throws IOException {
-		String json = retrieveJsonString(url, username, password);
-		if (StringUtils.isEmpty(json)) {
-			return null;
-		}
-		return gson().fromJson(json, clazz);
-	}
-
-	/**
-	 * Retrieves a JSON message.
-	 * 
-	 * @param url
-	 * @return the JSON message as a string
-	 * @throws {@link IOException}
-	 */
-	public static String retrieveJsonString(String url, String username, char[] password)
-			throws IOException {
-		try {
-			URLConnection conn = ConnectionUtils.openReadConnection(url, username, password);
-			InputStream is = conn.getInputStream();
-			BufferedReader reader = new BufferedReader(new InputStreamReader(is,
-					ConnectionUtils.CHARSET));
-			StringBuilder json = new StringBuilder();
-			char[] buffer = new char[4096];
-			int len = 0;
-			while ((len = reader.read(buffer)) > -1) {
-				json.append(buffer, 0, len);
-			}
-			is.close();
-			return json.toString();
-		} catch (IOException e) {
-			if (e.getMessage().indexOf("401") > -1) {
-				// unauthorized
-				throw new UnauthorizedException(url);
-			} else if (e.getMessage().indexOf("403") > -1) {
-				// requested url is forbidden by the requesting user
-				throw new ForbiddenException(url);
-			} else if (e.getMessage().indexOf("405") > -1) {
-				// requested url is not allowed by the server
-				throw new NotAllowedException(url);
-			} else if (e.getMessage().indexOf("501") > -1) {
-				// requested url is not recognized by the server
-				throw new UnknownRequestException(url);
-			}
-			throw e;
-		}
-	}
-
-	/**
-	 * Sends a JSON message.
-	 * 
-	 * @param url
-	 *            the url to write to
-	 * @param json
-	 *            the json message to send
-	 * @return the http request result code
-	 * @throws {@link IOException}
-	 */
-	public static int sendJsonString(String url, String json) throws IOException {
-		return sendJsonString(url, json, null, null);
-	}
-
-	/**
-	 * Sends a JSON message.
-	 * 
-	 * @param url
-	 *            the url to write to
-	 * @param json
-	 *            the json message to send
-	 * @param username
-	 * @param password
-	 * @return the http request result code
-	 * @throws {@link IOException}
-	 */
-	public static int sendJsonString(String url, String json, String username, char[] password)
-			throws IOException {
-		try {
-			byte[] jsonBytes = json.getBytes(ConnectionUtils.CHARSET);
-			URLConnection conn = ConnectionUtils.openConnection(url, username, password);
-			conn.setRequestProperty("Content-Type", "text/plain;charset=" + ConnectionUtils.CHARSET);
-			conn.setRequestProperty("Content-Length", "" + jsonBytes.length);
-
-			// write json body
-			OutputStream os = conn.getOutputStream();
-			os.write(jsonBytes);
-			os.close();
-
-			int status = ((HttpURLConnection) conn).getResponseCode();
-			return status;
-		} catch (IOException e) {
-			if (e.getMessage().indexOf("401") > -1) {
-				// unauthorized
-				throw new UnauthorizedException(url);
-			} else if (e.getMessage().indexOf("403") > -1) {
-				// requested url is forbidden by the requesting user
-				throw new ForbiddenException(url);
-			} else if (e.getMessage().indexOf("405") > -1) {
-				// requested url is not allowed by the server
-				throw new NotAllowedException(url);
-			} else if (e.getMessage().indexOf("501") > -1) {
-				// requested url is not recognized by the server
-				throw new UnknownRequestException(url);
-			}
-			throw e;
-		}
-	}
-
-	// build custom gson instance with GMT date serializer/deserializer
-	// http://code.google.com/p/google-gson/issues/detail?id=281
-	public static Gson gson(ExclusionStrategy... strategies) {
-		GsonBuilder builder = new GsonBuilder();
-		builder.registerTypeAdapter(Date.class, new GmtDateTypeAdapter());
-		builder.registerTypeAdapter(AccessPermission.class, new AccessPermissionTypeAdapter());
-		builder.setPrettyPrinting();
-		if (!ArrayUtils.isEmpty(strategies)) {
-			builder.setExclusionStrategies(strategies);
-		}
-		return builder.create();
-	}
-
-	private static class GmtDateTypeAdapter implements JsonSerializer<Date>, JsonDeserializer<Date> {
-		private final DateFormat dateFormat;
-
-		private GmtDateTypeAdapter() {
-			dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US);
-			dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
-		}
-
-		@Override
-		public synchronized JsonElement serialize(Date date, Type type,
-				JsonSerializationContext jsonSerializationContext) {
-			synchronized (dateFormat) {
-				String dateFormatAsString = dateFormat.format(date);
-				return new JsonPrimitive(dateFormatAsString);
-			}
-		}
-
-		@Override
-		public synchronized Date deserialize(JsonElement jsonElement, Type type,
-				JsonDeserializationContext jsonDeserializationContext) {
-			try {
-				synchronized (dateFormat) {
-					Date date = dateFormat.parse(jsonElement.getAsString());					
-					return new Date((date.getTime() / 1000) * 1000);
-				}
-			} catch (ParseException e) {
-				throw new JsonSyntaxException(jsonElement.getAsString(), e);
-			}
-		}
-	}
-	
-	private static class AccessPermissionTypeAdapter implements JsonSerializer<AccessPermission>, JsonDeserializer<AccessPermission> {
-
-		private AccessPermissionTypeAdapter() {
-		}
-
-		@Override
-		public synchronized JsonElement serialize(AccessPermission permission, Type type,
-				JsonSerializationContext jsonSerializationContext) {
-			return new JsonPrimitive(permission.code);
-		}
-
-		@Override
-		public synchronized AccessPermission deserialize(JsonElement jsonElement, Type type,
-				JsonDeserializationContext jsonDeserializationContext) {
-			return AccessPermission.fromCode(jsonElement.getAsString());					
-		}
-	}
-
-	public static class ExcludeField implements ExclusionStrategy {
-
-		private Class<?> c;
-		private String fieldName;
-
-		public ExcludeField(String fqfn) throws SecurityException, NoSuchFieldException,
-				ClassNotFoundException {
-			this.c = Class.forName(fqfn.substring(0, fqfn.lastIndexOf(".")));
-			this.fieldName = fqfn.substring(fqfn.lastIndexOf(".") + 1);
-		}
-
-		public boolean shouldSkipClass(Class<?> arg0) {
-			return false;
-		}
-
-		public boolean shouldSkipField(FieldAttributes f) {
-			return (f.getDeclaringClass() == c && f.getName().equals(fieldName));
-		}
-	}
-}
diff --git a/src/com/gitblit/utils/ObjectCache.java b/src/com/gitblit/utils/ObjectCache.java
deleted file mode 100644
index 38f2e59..0000000
--- a/src/com/gitblit/utils/ObjectCache.java
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Copyright 2011 gitblit.com.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.gitblit.utils;
-
-import java.io.Serializable;
-import java.util.Date;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-
-/**
- * Reusable coarse date-based object cache. The date precision is in
- * 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 {
-
-	private static final long serialVersionUID = 1L;
-
-	private final Map<String, CachedObject<X>> cache = new ConcurrentHashMap<String, CachedObject<X>>();
-
-	private class CachedObject<Y> {
-
-		public final String name;
-
-		private volatile Date date;
-
-		private volatile Y object;
-
-		CachedObject(String name) {
-			this.name = name;
-			date = new Date(0);
-		}
-
-		@Override
-		public String toString() {
-			return getClass().getSimpleName() + ": " + name;
-		}
-	}
-
-	public boolean hasCurrent(String name, Date date) {
-		return cache.containsKey(name) && cache.get(name).date.compareTo(date) == 0;
-	}
-
-	public Date getDate(String name) {
-		return cache.get(name).date;
-	}
-
-	public X getObject(String name) {
-		if (cache.containsKey(name)) {
-			return cache.get(name).object;
-		}
-		return null;
-	}
-
-	public void updateObject(String name, X object) {
-		this.updateObject(name, new Date(), object);
-	}
-
-	public void updateObject(String name, Date date, X object) {
-		CachedObject<X> obj;
-		if (cache.containsKey(name)) {
-			obj = cache.get(name);
-		} else {
-			obj = new CachedObject<X>(name);
-			cache.put(name, obj);
-		}
-		obj.date = date;
-		obj.object = object;
-	}
-
-	public Object remove(String name) {
-		if (cache.containsKey(name)) {
-			return cache.remove(name).object;
-		}
-		return null;
-	}
-	
-	public int size() {
-		return cache.size();
-	}
-}
diff --git a/src/com/gitblit/utils/PushLogUtils.java b/src/com/gitblit/utils/PushLogUtils.java
deleted file mode 100644
index 665533b..0000000
--- a/src/com/gitblit/utils/PushLogUtils.java
+++ /dev/null
@@ -1,344 +0,0 @@
-/*
- * Copyright 2013 gitblit.com.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.gitblit.utils;
-
-import java.io.IOException;
-import java.text.MessageFormat;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Date;
-import java.util.List;
-import java.util.Set;
-import java.util.TreeSet;
-
-import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException;
-import org.eclipse.jgit.api.errors.JGitInternalException;
-import org.eclipse.jgit.dircache.DirCache;
-import org.eclipse.jgit.dircache.DirCacheBuilder;
-import org.eclipse.jgit.dircache.DirCacheEntry;
-import org.eclipse.jgit.internal.JGitText;
-import org.eclipse.jgit.lib.CommitBuilder;
-import org.eclipse.jgit.lib.Constants;
-import org.eclipse.jgit.lib.FileMode;
-import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.ObjectInserter;
-import org.eclipse.jgit.lib.PersonIdent;
-import org.eclipse.jgit.lib.RefUpdate;
-import org.eclipse.jgit.lib.RefUpdate.Result;
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.revwalk.RevCommit;
-import org.eclipse.jgit.revwalk.RevWalk;
-import org.eclipse.jgit.transport.ReceiveCommand;
-import org.eclipse.jgit.treewalk.CanonicalTreeParser;
-import org.eclipse.jgit.treewalk.TreeWalk;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.gitblit.models.PathModel.PathChangeModel;
-import com.gitblit.models.PushLogEntry;
-import com.gitblit.models.RefModel;
-import com.gitblit.models.UserModel;
-
-/**
- * Utility class for maintaining a pushlog within a git repository on an
- * orphan branch.
- * 
- * @author James Moger
- *
- */
-public class PushLogUtils {
-	
-	public static final String GB_PUSHES = "refs/gitblit/pushes";
-
-	static final Logger LOGGER = LoggerFactory.getLogger(PushLogUtils.class);
-
-	/**
-	 * Log an error message and exception.
-	 * 
-	 * @param t
-	 * @param repository
-	 *            if repository is not null it MUST be the {0} parameter in the
-	 *            pattern.
-	 * @param pattern
-	 * @param objects
-	 */
-	private static void error(Throwable t, Repository repository, String pattern, Object... objects) {
-		List<Object> parameters = new ArrayList<Object>();
-		if (objects != null && objects.length > 0) {
-			for (Object o : objects) {
-				parameters.add(o);
-			}
-		}
-		if (repository != null) {
-			parameters.add(0, repository.getDirectory().getAbsolutePath());
-		}
-		LOGGER.error(MessageFormat.format(pattern, parameters.toArray()), t);
-	}
-
-	/**
-	 * Returns a RefModel for the gb-pushes branch in the repository. If the
-	 * branch can not be found, null is returned.
-	 * 
-	 * @param repository
-	 * @return a refmodel for the gb-pushes branch or null
-	 */
-	public static RefModel getPushLogBranch(Repository repository) {
-		List<RefModel> refs = JGitUtils.getRefs(repository, com.gitblit.Constants.R_GITBLIT);
-		for (RefModel ref : refs) {
-			if (ref.reference.getName().equals(GB_PUSHES)) {
-				return ref;
-			}
-		}
-		return null;
-	}
-	
-	/**
-	 * Updates a push log.
-	 * 
-	 * @param user
-	 * @param repository
-	 * @param commands
-	 * @return true, if the update was successful
-	 */
-	public static boolean updatePushLog(UserModel user, Repository repository,
-			Collection<ReceiveCommand> commands) {
-		RefModel pushlogBranch = getPushLogBranch(repository);
-		if (pushlogBranch == null) {
-			JGitUtils.createOrphanBranch(repository, GB_PUSHES, null);
-		}
-		
-		boolean success = false;
-		String message = "push";
-		
-		try {
-			ObjectId headId = repository.resolve(GB_PUSHES + "^{commit}");
-			ObjectInserter odi = repository.newObjectInserter();
-			try {
-				// Create the in-memory index of the push log entry
-				DirCache index = createIndex(repository, headId, commands);
-				ObjectId indexTreeId = index.writeTree(odi);
-
-				PersonIdent ident = new PersonIdent(user.getDisplayName(), 
-						user.emailAddress == null ? user.username:user.emailAddress);
-
-				// Create a commit object
-				CommitBuilder commit = new CommitBuilder();
-				commit.setAuthor(ident);
-				commit.setCommitter(ident);
-				commit.setEncoding(Constants.CHARACTER_ENCODING);
-				commit.setMessage(message);
-				commit.setParentId(headId);
-				commit.setTreeId(indexTreeId);
-
-				// Insert the commit into the repository
-				ObjectId commitId = odi.insert(commit);
-				odi.flush();
-
-				RevWalk revWalk = new RevWalk(repository);
-				try {
-					RevCommit revCommit = revWalk.parseCommit(commitId);
-					RefUpdate ru = repository.updateRef(GB_PUSHES);
-					ru.setNewObjectId(commitId);
-					ru.setExpectedOldObjectId(headId);
-					ru.setRefLogMessage("commit: " + revCommit.getShortMessage(), false);
-					Result rc = ru.forceUpdate();
-					switch (rc) {
-					case NEW:
-					case FORCED:
-					case FAST_FORWARD:
-						success = true;
-						break;
-					case REJECTED:
-					case LOCK_FAILURE:
-						throw new ConcurrentRefUpdateException(JGitText.get().couldNotLockHEAD,
-								ru.getRef(), rc);
-					default:
-						throw new JGitInternalException(MessageFormat.format(
-								JGitText.get().updatingRefFailed, GB_PUSHES, commitId.toString(),
-								rc));
-					}
-				} finally {
-					revWalk.release();
-				}
-			} finally {
-				odi.release();
-			}
-		} catch (Throwable t) {
-			error(t, repository, "Failed to commit pushlog entry to {0}");
-		}
-		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, 
-			Collection<ReceiveCommand> commands) throws IOException {
-
-		DirCache inCoreIndex = DirCache.newInCore();
-		DirCacheBuilder dcBuilder = inCoreIndex.builder();
-		ObjectInserter inserter = repo.newObjectInserter();
-
-		long now = System.currentTimeMillis();
-		Set<String> ignorePaths = new TreeSet<String>();
-		try {
-			// add receive commands to the temporary index
-			for (ReceiveCommand command : commands) {
-				// use the ref names as the path names
-				String path = command.getRefName();
-				ignorePaths.add(path);
-
-				StringBuilder change = new StringBuilder();
-				change.append(command.getType().name()).append(' ');
-				switch (command.getType()) {
-				case CREATE:
-					change.append(ObjectId.zeroId().getName());
-					change.append(' ');
-					change.append(command.getNewId().getName());
-					break;
-				case UPDATE:
-				case UPDATE_NONFASTFORWARD:
-					change.append(command.getOldId().getName());
-					change.append(' ');
-					change.append(command.getNewId().getName());
-					break;
-				case DELETE:
-					change = null;
-					break;
-				}
-				if (change == null) {
-					// ref deleted
-					continue;
-				}
-				String content = change.toString();
-				
-				// create an index entry for this attachment
-				final DirCacheEntry dcEntry = new DirCacheEntry(path);
-				dcEntry.setLength(content.length());
-				dcEntry.setLastModified(now);
-				dcEntry.setFileMode(FileMode.REGULAR_FILE);
-
-				// insert object
-				dcEntry.setObjectId(inserter.insert(Constants.OBJ_BLOB, content.getBytes("UTF-8")));
-
-				// add to temporary in-core index
-				dcBuilder.add(dcEntry);
-			}
-
-			// Traverse HEAD to add all other paths
-			TreeWalk treeWalk = new TreeWalk(repo);
-			int hIdx = -1;
-			if (headId != null)
-				hIdx = treeWalk.addTree(new RevWalk(repo).parseTree(headId));
-			treeWalk.setRecursive(true);
-
-			while (treeWalk.next()) {
-				String path = treeWalk.getPathString();
-				CanonicalTreeParser hTree = null;
-				if (hIdx != -1)
-					hTree = treeWalk.getTree(hIdx, CanonicalTreeParser.class);
-				if (!ignorePaths.contains(path)) {
-					// add entries from HEAD for all other paths
-					if (hTree != null) {
-						// create a new DirCacheEntry with data retrieved from
-						// HEAD
-						final DirCacheEntry dcEntry = new DirCacheEntry(path);
-						dcEntry.setObjectId(hTree.getEntryObjectId());
-						dcEntry.setFileMode(hTree.getEntryFileMode());
-
-						// add to temporary in-core index
-						dcBuilder.add(dcEntry);
-					}
-				}
-			}
-
-			// release the treewalk
-			treeWalk.release();
-
-			// finish temporary in-core index used for this commit
-			dcBuilder.finish();
-		} finally {
-			inserter.release();
-		}
-		return inCoreIndex;
-	}
-
-	public static List<PushLogEntry> getPushLog(String repositoryName, Repository repository) {
-		return getPushLog(repositoryName, repository, null, -1);
-	}
-
-	public static List<PushLogEntry> getPushLog(String repositoryName, Repository repository, int maxCount) {
-		return getPushLog(repositoryName, repository, null, maxCount);
-	}
-
-	public static List<PushLogEntry> getPushLog(String repositoryName, Repository repository, Date minimumDate) {
-		return getPushLog(repositoryName, repository, minimumDate, -1);
-	}
-	
-	public static List<PushLogEntry> getPushLog(String repositoryName, Repository repository, Date minimumDate, int maxCount) {
-		List<PushLogEntry> list = new ArrayList<PushLogEntry>();
-		RefModel ref = getPushLogBranch(repository);
-		if (ref == null) {
-			return list;
-		}
-		List<RevCommit> pushes;
-		if (minimumDate == null) {
-			pushes = JGitUtils.getRevLog(repository, GB_PUSHES, 0, maxCount);
-		} else {
-			pushes = JGitUtils.getRevLog(repository, GB_PUSHES, minimumDate);
-		}
-		for (RevCommit push : pushes) {
-			if (push.getAuthorIdent().getName().equalsIgnoreCase("gitblit")) {
-				// skip gitblit/internal commits
-				continue;
-			}
-			Date date = push.getAuthorIdent().getWhen();
-			UserModel user = new UserModel(push.getAuthorIdent().getEmailAddress());
-			user.displayName = push.getAuthorIdent().getName();
-			PushLogEntry log = new PushLogEntry(repositoryName, date, user);
-			list.add(log);
-			List<PathChangeModel> changedRefs = JGitUtils.getFilesInCommit(repository, push);
-			for (PathChangeModel change : changedRefs) {
-				switch (change.changeType) {
-				case DELETE:
-					log.updateRef(change.path, ReceiveCommand.Type.DELETE);
-					break;
-				case ADD:
-					log.updateRef(change.path, ReceiveCommand.Type.CREATE);
-				default:
-					String content = JGitUtils.getStringContent(repository, push.getTree(), change.path);
-					String [] fields = content.split(" ");
-					log.updateRef(change.path, ReceiveCommand.Type.valueOf(fields[0]));
-					String oldId = fields[1];
-					String newId = fields[2];
-					List<RevCommit> pushedCommits = JGitUtils.getRevLog(repository, oldId, newId);
-					for (RevCommit pushedCommit : pushedCommits) {
-						log.addCommit(change.path, pushedCommit);
-					}
-				}
-			}
-		}
-		Collections.sort(list);
-		return list;
-	}
-}
diff --git a/src/com/gitblit/utils/RpcUtils.java b/src/com/gitblit/utils/RpcUtils.java
deleted file mode 100644
index ed23dab..0000000
--- a/src/com/gitblit/utils/RpcUtils.java
+++ /dev/null
@@ -1,637 +0,0 @@
-/*
- * Copyright 2011 gitblit.com.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.gitblit.utils;
-
-import java.io.IOException;
-import java.lang.reflect.Type;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-
-import com.gitblit.Constants;
-import com.gitblit.Constants.RpcRequest;
-import com.gitblit.GitBlitException.UnknownRequestException;
-import com.gitblit.models.RegistrantAccessPermission;
-import com.gitblit.models.FederationModel;
-import com.gitblit.models.FederationProposal;
-import com.gitblit.models.FederationSet;
-import com.gitblit.models.FeedModel;
-import com.gitblit.models.RepositoryModel;
-import com.gitblit.models.ServerSettings;
-import com.gitblit.models.ServerStatus;
-import com.gitblit.models.TeamModel;
-import com.gitblit.models.UserModel;
-import com.google.gson.reflect.TypeToken;
-
-/**
- * Utility methods for rpc calls.
- * 
- * @author James Moger
- * 
- */
-public class RpcUtils {
-
-	public static final Type NAMES_TYPE = new TypeToken<Collection<String>>() {
-	}.getType();
-
-	public static final Type SETTINGS_TYPE = new TypeToken<Map<String, String>>() {
-	}.getType();
-
-	private static final Type REPOSITORIES_TYPE = new TypeToken<Map<String, RepositoryModel>>() {
-	}.getType();
-
-	private static final Type USERS_TYPE = new TypeToken<Collection<UserModel>>() {
-	}.getType();
-
-	private static final Type TEAMS_TYPE = new TypeToken<Collection<TeamModel>>() {
-	}.getType();
-
-	private static final Type REGISTRATIONS_TYPE = new TypeToken<Collection<FederationModel>>() {
-	}.getType();
-
-	private static final Type PROPOSALS_TYPE = new TypeToken<Collection<FederationProposal>>() {
-	}.getType();
-
-	private static final Type SETS_TYPE = new TypeToken<Collection<FederationSet>>() {
-	}.getType();
-
-	private static final Type BRANCHES_TYPE = new TypeToken<Map<String, Collection<String>>>() {
-	}.getType();
-
-	public static final Type REGISTRANT_PERMISSIONS_TYPE = new TypeToken<Collection<RegistrantAccessPermission>>() {
-	}.getType();
-
-	/**
-	 * 
-	 * @param remoteURL
-	 *            the url of the remote gitblit instance
-	 * @param req
-	 *            the rpc request type
-	 * @return
-	 */
-	public static String asLink(String remoteURL, RpcRequest req) {
-		return asLink(remoteURL, req, null);
-	}
-
-	/**
-	 * 
-	 * @param remoteURL
-	 *            the url of the remote gitblit instance
-	 * @param req
-	 *            the rpc request type
-	 * @param name
-	 *            the name of the actionable object
-	 * @return
-	 */
-	public static String asLink(String remoteURL, RpcRequest req, String name) {
-		if (remoteURL.length() > 0 && remoteURL.charAt(remoteURL.length() - 1) == '/') {
-			remoteURL = remoteURL.substring(0, remoteURL.length() - 1);
-		}
-		if (req == null) {
-			req = RpcRequest.LIST_REPOSITORIES;
-		}
-		return remoteURL + Constants.RPC_PATH + "?req=" + req.name().toLowerCase()
-				+ (name == null ? "" : ("&name=" + StringUtils.encodeURL(name)));
-	}
-
-	/**
-	 * Returns the version of the RPC protocol on the server.
-	 * 
-	 * @param serverUrl
-	 * @param account
-	 * @param password
-	 * @return the protocol version
-	 * @throws IOException
-	 */
-	public static int getProtocolVersion(String serverUrl, String account, char[] password)
-			throws IOException {
-		String url = asLink(serverUrl, RpcRequest.GET_PROTOCOL);
-		int protocol = 1;
-		try {
-			protocol = JsonUtils.retrieveJson(url, Integer.class, account, password);
-		} catch (UnknownRequestException e) {
-			// v0.7.0 (protocol 1) did not have this request type 
-		}
-		return protocol;
-	}
-
-	/**
-	 * Retrieves a map of the repositories at the remote gitblit instance keyed
-	 * by the repository clone url.
-	 * 
-	 * @param serverUrl
-	 * @param account
-	 * @param password
-	 * @return a map of cloneable repositories
-	 * @throws IOException
-	 */
-	public static Map<String, RepositoryModel> getRepositories(String serverUrl, String account,
-			char[] password) throws IOException {
-		String url = asLink(serverUrl, RpcRequest.LIST_REPOSITORIES);
-		Map<String, RepositoryModel> models = JsonUtils.retrieveJson(url, REPOSITORIES_TYPE,
-				account, password);
-		return models;
-	}
-
-	/**
-	 * Tries to pull the gitblit user accounts from the remote gitblit instance.
-	 * 
-	 * @param serverUrl
-	 * @param account
-	 * @param password
-	 * @return a collection of UserModel objects
-	 * @throws IOException
-	 */
-	public static List<UserModel> getUsers(String serverUrl, String account, char[] password)
-			throws IOException {
-		String url = asLink(serverUrl, RpcRequest.LIST_USERS);
-		Collection<UserModel> models = JsonUtils.retrieveJson(url, USERS_TYPE, account, password);
-		List<UserModel> list = new ArrayList<UserModel>(models);
-		return list;
-	}
-
-	/**
-	 * Tries to pull the gitblit team definitions from the remote gitblit
-	 * instance.
-	 * 
-	 * @param serverUrl
-	 * @param account
-	 * @param password
-	 * @return a collection of UserModel objects
-	 * @throws IOException
-	 */
-	public static List<TeamModel> getTeams(String serverUrl, String account, char[] password)
-			throws IOException {
-		String url = asLink(serverUrl, RpcRequest.LIST_TEAMS);
-		Collection<TeamModel> models = JsonUtils.retrieveJson(url, TEAMS_TYPE, account, password);
-		List<TeamModel> list = new ArrayList<TeamModel>(models);
-		return list;
-	}
-
-	/**
-	 * Create a repository on the Gitblit server.
-	 * 
-	 * @param repository
-	 * @param serverUrl
-	 * @param account
-	 * @param password
-	 * @return true if the action succeeded
-	 * @throws IOException
-	 */
-	public static boolean createRepository(RepositoryModel repository, String serverUrl,
-			String account, char[] password) throws IOException {
-		// ensure repository name ends with .git
-		if (!repository.name.endsWith(".git")) {
-			repository.name += ".git";
-		}
-		return doAction(RpcRequest.CREATE_REPOSITORY, null, repository, serverUrl, account,
-				password);
-
-	}
-
-	/**
-	 * Send a revised version of the repository model to the Gitblit server.
-	 * 
-	 * @param repository
-	 * @param serverUrl
-	 * @param account
-	 * @param password
-	 * @return true if the action succeeded
-	 * @throws IOException
-	 */
-	public static boolean updateRepository(String repositoryName, RepositoryModel repository,
-			String serverUrl, String account, char[] password) throws IOException {
-		return doAction(RpcRequest.EDIT_REPOSITORY, repositoryName, repository, serverUrl, account,
-				password);
-	}
-
-	/**
-	 * Delete a repository from the Gitblit server.
-	 * 
-	 * @param repository
-	 * @param serverUrl
-	 * @param account
-	 * @param password
-	 * @return true if the action succeeded
-	 * @throws IOException
-	 */
-	public static boolean deleteRepository(RepositoryModel repository, String serverUrl,
-			String account, char[] password) throws IOException {
-		return doAction(RpcRequest.DELETE_REPOSITORY, null, repository, serverUrl, account,
-				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, 
-			char[] password) throws IOException {
-		return doAction(RpcRequest.CLEAR_REPOSITORY_CACHE, null, null, serverUrl, account,
-				password);
-	}
-
-	/**
-	 * Create a user on the Gitblit server.
-	 * 
-	 * @param user
-	 * @param serverUrl
-	 * @param account
-	 * @param password
-	 * @return true if the action succeeded
-	 * @throws IOException
-	 */
-	public static boolean createUser(UserModel user, String serverUrl, String account,
-			char[] password) throws IOException {
-		return doAction(RpcRequest.CREATE_USER, null, user, serverUrl, account, password);
-
-	}
-
-	/**
-	 * Send a revised version of the user model to the Gitblit server.
-	 * 
-	 * @param user
-	 * @param serverUrl
-	 * @param account
-	 * @param password
-	 * @return true if the action succeeded
-	 * @throws IOException
-	 */
-	public static boolean updateUser(String username, UserModel user, String serverUrl,
-			String account, char[] password) throws IOException {
-		return doAction(RpcRequest.EDIT_USER, username, user, serverUrl, account, password);
-
-	}
-
-	/**
-	 * Deletes a user from the Gitblit server.
-	 * 
-	 * @param user
-	 * @param serverUrl
-	 * @param account
-	 * @param password
-	 * @return true if the action succeeded
-	 * @throws IOException
-	 */
-	public static boolean deleteUser(UserModel user, String serverUrl, String account,
-			char[] password) throws IOException {
-		return doAction(RpcRequest.DELETE_USER, null, user, serverUrl, account, password);
-	}
-
-	/**
-	 * Create a team on the Gitblit server.
-	 * 
-	 * @param team
-	 * @param serverUrl
-	 * @param account
-	 * @param password
-	 * @return true if the action succeeded
-	 * @throws IOException
-	 */
-	public static boolean createTeam(TeamModel team, String serverUrl, String account,
-			char[] password) throws IOException {
-		return doAction(RpcRequest.CREATE_TEAM, null, team, serverUrl, account, password);
-
-	}
-
-	/**
-	 * Send a revised version of the team model to the Gitblit server.
-	 * 
-	 * @param team
-	 * @param serverUrl
-	 * @param account
-	 * @param password
-	 * @return true if the action succeeded
-	 * @throws IOException
-	 */
-	public static boolean updateTeam(String teamname, TeamModel team, String serverUrl,
-			String account, char[] password) throws IOException {
-		return doAction(RpcRequest.EDIT_TEAM, teamname, team, serverUrl, account, password);
-
-	}
-
-	/**
-	 * Deletes a team from the Gitblit server.
-	 * 
-	 * @param team
-	 * @param serverUrl
-	 * @param account
-	 * @param password
-	 * @return true if the action succeeded
-	 * @throws IOException
-	 */
-	public static boolean deleteTeam(TeamModel team, String serverUrl, String account,
-			char[] password) throws IOException {
-		return doAction(RpcRequest.DELETE_TEAM, null, team, serverUrl, account, password);
-	}
-
-	/**
-	 * Retrieves the list of users that can access the specified repository.
-	 * 
-	 * @param repository
-	 * @param serverUrl
-	 * @param account
-	 * @param password
-	 * @return list of members
-	 * @throws IOException
-	 */
-	public static List<String> getRepositoryMembers(RepositoryModel repository, String serverUrl,
-			String account, char[] password) throws IOException {
-		String url = asLink(serverUrl, RpcRequest.LIST_REPOSITORY_MEMBERS, repository.name);
-		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
-	 * @param password
-	 * @return list of User-AccessPermission tuples
-	 * @throws IOException
-	 */
-	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);
-		return new ArrayList<RegistrantAccessPermission>(list);
-	}
-
-	/**
-	 * Sets the repository user access permissions
-	 * 
-	 * @param repository
-	 * @param permissions
-	 * @param serverUrl
-	 * @param account
-	 * @param password
-	 * @return true if the action succeeded
-	 * @throws IOException
-	 */
-	public static boolean setRepositoryMemberPermissions(RepositoryModel repository,
-			List<RegistrantAccessPermission> permissions, String serverUrl, String account, char[] password)
-			throws IOException {
-		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
-	 * @param password
-	 * @return list of teams
-	 * @throws IOException
-	 */
-	public static List<String> getRepositoryTeams(RepositoryModel repository, String serverUrl,
-			String account, char[] password) throws IOException {
-		String url = asLink(serverUrl, RpcRequest.LIST_REPOSITORY_TEAMS, repository.name);
-		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
-	 * @param password
-	 * @return list of Team-AccessPermission tuples
-	 * @throws IOException
-	 */
-	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);
-		return new ArrayList<RegistrantAccessPermission>(list);
-	}
-
-	/**
-	 * Sets the repository team access permissions
-	 * 
-	 * @param repository
-	 * @param permissions
-	 * @param serverUrl
-	 * @param account
-	 * @param password
-	 * @return true if the action succeeded
-	 * @throws IOException
-	 */
-	public static boolean setRepositoryTeamPermissions(RepositoryModel repository,
-			List<RegistrantAccessPermission> permissions, String serverUrl, String account, char[] password)
-			throws IOException {
-		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
-	 * @return a collection of FederationRegistration objects
-	 * @throws IOException
-	 */
-	public static List<FederationModel> getFederationRegistrations(String serverUrl,
-			String account, char[] password) throws IOException {
-		String url = asLink(serverUrl, RpcRequest.LIST_FEDERATION_REGISTRATIONS);
-		Collection<FederationModel> registrations = JsonUtils.retrieveJson(url, REGISTRATIONS_TYPE,
-				account, password);
-		List<FederationModel> list = new ArrayList<FederationModel>(registrations);
-		return list;
-	}
-
-	/**
-	 * 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
-	 * @return a collection of FederationRegistration objects
-	 * @throws IOException
-	 */
-	public static List<FederationModel> getFederationResultRegistrations(String serverUrl,
-			String account, char[] password) throws IOException {
-		String url = asLink(serverUrl, RpcRequest.LIST_FEDERATION_RESULTS);
-		Collection<FederationModel> registrations = JsonUtils.retrieveJson(url, REGISTRATIONS_TYPE,
-				account, password);
-		List<FederationModel> list = new ArrayList<FederationModel>(registrations);
-		return list;
-	}
-
-	/**
-	 * Retrieves the list of federation proposals.
-	 * 
-	 * @param serverUrl
-	 * @param account
-	 * @param password
-	 * @return a collection of FederationProposal objects
-	 * @throws IOException
-	 */
-	public static List<FederationProposal> getFederationProposals(String serverUrl, String account,
-			char[] password) throws IOException {
-		String url = asLink(serverUrl, RpcRequest.LIST_FEDERATION_PROPOSALS);
-		Collection<FederationProposal> proposals = JsonUtils.retrieveJson(url, PROPOSALS_TYPE,
-				account, password);
-		List<FederationProposal> list = new ArrayList<FederationProposal>(proposals);
-		return list;
-	}
-
-	/**
-	 * Retrieves the list of federation repository sets.
-	 * 
-	 * @param serverUrl
-	 * @param account
-	 * @param password
-	 * @return a collection of FederationSet objects
-	 * @throws IOException
-	 */
-	public static List<FederationSet> getFederationSets(String serverUrl, String account,
-			char[] password) throws IOException {
-		String url = asLink(serverUrl, RpcRequest.LIST_FEDERATION_SETS);
-		Collection<FederationSet> sets = JsonUtils.retrieveJson(url, SETS_TYPE, account, password);
-		List<FederationSet> list = new ArrayList<FederationSet>(sets);
-		return list;
-	}
-
-	/**
-	 * Retrieves the settings of the Gitblit server.
-	 * 
-	 * @param serverUrl
-	 * @param account
-	 * @param password
-	 * @return an Settings object
-	 * @throws IOException
-	 */
-	public static ServerSettings getSettings(String serverUrl, String account, char[] password)
-			throws IOException {
-		String url = asLink(serverUrl, RpcRequest.LIST_SETTINGS);
-		ServerSettings settings = JsonUtils.retrieveJson(url, ServerSettings.class, account,
-				password);
-		return settings;
-	}
-
-	/**
-	 * Update the settings on the Gitblit server.
-	 * 
-	 * @param settings
-	 *            the settings to update
-	 * @param serverUrl
-	 * @param account
-	 * @param password
-	 * @return true if the action succeeded
-	 * @throws IOException
-	 */
-	public static boolean updateSettings(Map<String, String> settings, String serverUrl,
-			String account, char[] password) throws IOException {
-		return doAction(RpcRequest.EDIT_SETTINGS, null, settings, serverUrl, account, password);
-
-	}
-
-	/**
-	 * Retrieves the server status object.
-	 * 
-	 * @param serverUrl
-	 * @param account
-	 * @param password
-	 * @return an ServerStatus object
-	 * @throws IOException
-	 */
-	public static ServerStatus getStatus(String serverUrl, String account, char[] password)
-			throws IOException {
-		String url = asLink(serverUrl, RpcRequest.LIST_STATUS);
-		ServerStatus status = JsonUtils.retrieveJson(url, ServerStatus.class, account, password);
-		return status;
-	}
-
-	/**
-	 * Retrieves a map of local branches in the Gitblit server keyed by
-	 * repository.
-	 * 
-	 * @param serverUrl
-	 * @param account
-	 * @param password
-	 * @return
-	 * @throws IOException
-	 */
-	public static Map<String, Collection<String>> getBranches(String serverUrl, String account,
-			char[] password) throws IOException {
-		String url = asLink(serverUrl, RpcRequest.LIST_BRANCHES);
-		Map<String, Collection<String>> branches = JsonUtils.retrieveJson(url, BRANCHES_TYPE,
-				account, password);
-		return branches;
-	}
-
-	/**
-	 * Retrieves a list of available branch feeds in the Gitblit server.
-	 * 
-	 * @param serverUrl
-	 * @param account
-	 * @param password
-	 * @return
-	 * @throws IOException
-	 */
-	public static List<FeedModel> getBranchFeeds(String serverUrl, String account, char[] password)
-			throws IOException {
-		List<FeedModel> feeds = new ArrayList<FeedModel>();
-		Map<String, Collection<String>> allBranches = getBranches(serverUrl, account, password);
-		for (Map.Entry<String, Collection<String>> entry : allBranches.entrySet()) {
-			for (String branch : entry.getValue()) {
-				FeedModel feed = new FeedModel();
-				feed.repository = entry.getKey();
-				feed.branch = branch;
-				feeds.add(feed);
-			}
-		}
-		return feeds;
-	}
-
-	/**
-	 * Do the specified administrative action on the Gitblit server.
-	 * 
-	 * @param request
-	 * @param name
-	 *            the name of the object (may be null)
-	 * @param object
-	 * @param serverUrl
-	 * @param account
-	 * @param password
-	 * @return true if the action succeeded
-	 * @throws IOException
-	 */
-	protected static boolean doAction(RpcRequest request, String name, Object object,
-			String serverUrl, String account, char[] password) throws IOException {
-		String url = asLink(serverUrl, request, name);
-		String json = JsonUtils.toJsonString(object);
-		int resultCode = JsonUtils.sendJsonString(url, json, account, password);
-		return resultCode == 200;
-	}
-}
diff --git a/src/com/gitblit/utils/StringUtils.java b/src/com/gitblit/utils/StringUtils.java
deleted file mode 100644
index 86823db..0000000
--- a/src/com/gitblit/utils/StringUtils.java
+++ /dev/null
@@ -1,736 +0,0 @@
-/*
- * Copyright 2011 gitblit.com.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.gitblit.utils;
-
-import java.io.ByteArrayOutputStream;
-import java.io.UnsupportedEncodingException;
-import java.nio.ByteBuffer;
-import java.nio.CharBuffer;
-import java.nio.charset.CharacterCodingException;
-import java.nio.charset.Charset;
-import java.nio.charset.CharsetDecoder;
-import java.nio.charset.IllegalCharsetNameException;
-import java.nio.charset.UnsupportedCharsetException;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Set;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-import java.util.regex.PatternSyntaxException;
-
-/**
- * Utility class of string functions.
- * 
- * @author James Moger
- * 
- */
-public class StringUtils {
-
-	public static final String MD5_TYPE = "MD5:";
-
-	public static final String COMBINED_MD5_TYPE = "CMD5:";
-
-	/**
-	 * Returns true if the string is null or empty.
-	 * 
-	 * @param value
-	 * @return true if string is null or empty
-	 */
-	public static boolean isEmpty(String value) {
-		return value == null || value.trim().length() == 0;
-	}
-
-	/**
-	 * Replaces carriage returns and line feeds with html line breaks.
-	 * 
-	 * @param string
-	 * @return plain text with html line breaks
-	 */
-	public static String breakLinesForHtml(String string) {
-		return string.replace("\r\n", "<br/>").replace("\r", "<br/>").replace("\n", "<br/>");
-	}
-
-	/**
-	 * Prepare text for html presentation. Replace sensitive characters with
-	 * html entities.
-	 * 
-	 * @param inStr
-	 * @param changeSpace
-	 * @return plain text escaped for html
-	 */
-	public static String escapeForHtml(String inStr, boolean changeSpace) {
-		StringBuilder retStr = new StringBuilder();
-		int i = 0;
-		while (i < inStr.length()) {
-			if (inStr.charAt(i) == '&') {
-				retStr.append("&amp;");
-			} else if (inStr.charAt(i) == '<') {
-				retStr.append("&lt;");
-			} else if (inStr.charAt(i) == '>') {
-				retStr.append("&gt;");
-			} else if (inStr.charAt(i) == '\"') {
-				retStr.append("&quot;");
-			} else if (changeSpace && inStr.charAt(i) == ' ') {
-				retStr.append("&nbsp;");
-			} else if (changeSpace && inStr.charAt(i) == '\t') {
-				retStr.append(" &nbsp; &nbsp;");
-			} else {
-				retStr.append(inStr.charAt(i));
-			}
-			i++;
-		}
-		return retStr.toString();
-	}
-
-	/**
-	 * Decode html entities back into plain text characters.
-	 * 
-	 * @param inStr
-	 * @return returns plain text from html
-	 */
-	public static String decodeFromHtml(String inStr) {
-		return inStr.replace("&amp;", "&").replace("&lt;", "<").replace("&gt;", ">")
-				.replace("&quot;", "\"").replace("&nbsp;", " ");
-	}
-
-	/**
-	 * Encodes a url parameter by escaping troublesome characters.
-	 * 
-	 * @param inStr
-	 * @return properly escaped url
-	 */
-	public static String encodeURL(String inStr) {
-		StringBuilder retStr = new StringBuilder();
-		int i = 0;
-		while (i < inStr.length()) {
-			if (inStr.charAt(i) == '/') {
-				retStr.append("%2F");
-			} else if (inStr.charAt(i) == ' ') {
-				retStr.append("%20");
-			} else {
-				retStr.append(inStr.charAt(i));
-			}
-			i++;
-		}
-		return retStr.toString();
-	}
-
-	/**
-	 * Flatten the list of strings into a single string with a space separator.
-	 * 
-	 * @param values
-	 * @return flattened list
-	 */
-	public static String flattenStrings(Collection<String> values) {
-		return flattenStrings(values, " ");
-	}
-
-	/**
-	 * Flatten the list of strings into a single string with the specified
-	 * separator.
-	 * 
-	 * @param values
-	 * @param separator
-	 * @return flattened list
-	 */
-	public static String flattenStrings(Collection<String> values, String separator) {
-		StringBuilder sb = new StringBuilder();
-		for (String value : values) {
-			sb.append(value).append(separator);
-		}
-		if (sb.length() > 0) {
-			// truncate trailing separator
-			sb.setLength(sb.length() - separator.length());
-		}
-		return sb.toString().trim();
-	}
-
-	/**
-	 * 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
-	 */
-	public static String trimString(String value, int max) {
-		if (value.length() <= max) {
-			return value;
-		}
-		return value.substring(0, max - 3) + "...";
-	}
-
-	/**
-	 * Left pad a string with the specified character, if the string length is
-	 * less than the specified length.
-	 * 
-	 * @param input
-	 * @param length
-	 * @param pad
-	 * @return left-padded string
-	 */
-	public static String leftPad(String input, int length, char pad) {
-		if (input.length() < length) {
-			StringBuilder sb = new StringBuilder();
-			for (int i = 0, len = length - input.length(); i < len; i++) {
-				sb.append(pad);
-			}
-			sb.append(input);
-			return sb.toString();
-		}
-		return input;
-	}
-
-	/**
-	 * Right pad a string with the specified character, if the string length is
-	 * less then the specified length.
-	 * 
-	 * @param input
-	 * @param length
-	 * @param pad
-	 * @return right-padded string
-	 */
-	public static String rightPad(String input, int length, char pad) {
-		if (input.length() < length) {
-			StringBuilder sb = new StringBuilder();
-			sb.append(input);
-			for (int i = 0, len = length - input.length(); i < len; i++) {
-				sb.append(pad);
-			}
-			return sb.toString();
-		}
-		return input;
-	}
-
-	/**
-	 * Calculates the SHA1 of the string.
-	 * 
-	 * @param text
-	 * @return sha1 of the string
-	 */
-	public static String getSHA1(String text) {
-		try {
-			byte[] bytes = text.getBytes("iso-8859-1");
-			return getSHA1(bytes);
-		} catch (UnsupportedEncodingException u) {
-			throw new RuntimeException(u);
-		}
-	}
-
-	/**
-	 * Calculates the SHA1 of the byte array.
-	 * 
-	 * @param bytes
-	 * @return sha1 of the byte array
-	 */
-	public static String getSHA1(byte[] bytes) {
-		try {
-			MessageDigest md = MessageDigest.getInstance("SHA-1");
-			md.update(bytes, 0, bytes.length);
-			byte[] digest = md.digest();
-			return toHex(digest);
-		} catch (NoSuchAlgorithmException t) {
-			throw new RuntimeException(t);
-		}
-	}
-
-	/**
-	 * Calculates the MD5 of the string.
-	 * 
-	 * @param string
-	 * @return md5 of the string
-	 */
-	public static String getMD5(String string) {
-		try {
-			return getMD5(string.getBytes("iso-8859-1"));
-		} catch (UnsupportedEncodingException u) {
-			throw new RuntimeException(u);
-		}
-	}
-	
-	/**
-	 * Calculates the MD5 of the string.
-	 * 
-	 * @param string
-	 * @return md5 of the string
-	 */
-	public static String getMD5(byte [] bytes) {
-		try {
-			MessageDigest md = MessageDigest.getInstance("MD5");
-			md.reset();
-			md.update(bytes);
-			byte[] digest = md.digest();
-			return toHex(digest);
-		} catch (NoSuchAlgorithmException t) {
-			throw new RuntimeException(t);
-		}
-	}
-
-	/**
-	 * 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) {
-				sb.append('0');
-			}
-			sb.append(Long.toString((int) bytes[i] & 0xff, 16));
-		}
-		return sb.toString();
-	}
-
-	/**
-	 * 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
-	 */
-	public static String getRootPath(String path) {
-		if (path.indexOf('/') > -1) {
-			return path.substring(0, path.lastIndexOf('/'));
-		}
-		return "";
-	}
-
-	/**
-	 * Returns the path remainder after subtracting the basePath from the
-	 * fullPath.
-	 * 
-	 * @param basePath
-	 * @param fullPath
-	 * @return the relative path
-	 */
-	public static String getRelativePath(String basePath, String fullPath) {
-		String bp = basePath.replace('\\', '/').toLowerCase();
-		String fp = fullPath.replace('\\', '/').toLowerCase();
-		if (fp.startsWith(bp)) {
-			String relativePath = fullPath.substring(basePath.length()).replace('\\', '/');
-			if (relativePath.charAt(0) == '/') {
-				relativePath = relativePath.substring(1);
-			}
-			return relativePath;
-		}
-		return fullPath;
-	}
-
-	/**
-	 * Splits the space-separated string into a list of strings.
-	 * 
-	 * @param value
-	 * @return list of strings
-	 */
-	public static List<String> getStringsFromValue(String value) {
-		return getStringsFromValue(value, " ");
-	}
-
-	/**
-	 * Splits the string into a list of string by the specified separator.
-	 * 
-	 * @param value
-	 * @param separator
-	 * @return list of strings
-	 */
-	public static List<String> getStringsFromValue(String value, String separator) {
-        List<String> strings = new ArrayList<String>();
-        try {
-            String[] chunks = value.split(separator + "(?=([^\"]*\"[^\"]*\")*[^\"]*$)");            
-            for (String chunk : chunks) {
-                chunk = chunk.trim();
-                if (chunk.length() > 0) {
-                    if (chunk.charAt(0) == '"' && chunk.charAt(chunk.length() - 1) == '"') {
-                        // strip double quotes
-                        chunk = chunk.substring(1, chunk.length() - 1).trim();
-                    }
-                    strings.add(chunk);
-                }
-            }
-        } catch (PatternSyntaxException e) {
-            throw new RuntimeException(e);
-        }
-        return strings;
-    }
-
-	/**
-	 * 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
-	 */
-	public static Character findInvalidCharacter(String name) {
-		char[] validChars = { '/', '.', '_', '-', '~' };
-		for (char c : name.toCharArray()) {
-			if (!Character.isLetterOrDigit(c)) {
-				boolean ok = false;
-				for (char vc : validChars) {
-					ok |= c == vc;
-				}
-				if (!ok) {
-					return c;
-				}
-			}
-		}
-		return null;
-	}
-
-	/**
-	 * 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
-	 */
-	public static boolean fuzzyMatch(String value, String pattern) {
-		if (value.equalsIgnoreCase(pattern)) {
-			return true;
-		}
-		if (pattern.contains("*")) {
-			boolean prefixMatches = false;
-			boolean suffixMatches = false;
-
-			int wildcard = pattern.indexOf('*');
-			String prefix = pattern.substring(0, wildcard).toLowerCase();
-			prefixMatches = value.toLowerCase().startsWith(prefix);
-
-			if (pattern.length() > (wildcard + 1)) {
-				String suffix = pattern.substring(wildcard + 1).toLowerCase();
-				suffixMatches = value.toLowerCase().endsWith(suffix);
-				return prefixMatches && suffixMatches;
-			}
-			return prefixMatches || suffixMatches;
-		}
-		return false;
-	}
-
-	/**
-	 * Compare two repository names for proper group sorting.
-	 * 
-	 * @param r1
-	 * @param r2
-	 * @return
-	 */
-	public static int compareRepositoryNames(String r1, String r2) {
-		// sort root repositories first, alphabetically
-		// then sort grouped repositories, alphabetically
-		r1 = r1.toLowerCase();
-		r2 = r2.toLowerCase();
-		int s1 = r1.indexOf('/');
-		int s2 = r2.indexOf('/');
-		if (s1 == -1 && s2 == -1) {
-			// neither grouped
-			return r1.compareTo(r2);
-		} else if (s1 > -1 && s2 > -1) {
-			// both grouped
-			return r1.compareTo(r2);
-		} else if (s1 == -1) {
-			return -1;
-		} else if (s2 == -1) {
-			return 1;
-		}
-		return 0;
-	}
-
-	/**
-	 * Sort grouped repository names.
-	 * 
-	 * @param list
-	 */
-	public static void sortRepositorynames(List<String> list) {
-		Collections.sort(list, new Comparator<String>() {
-			@Override
-			public int compare(String o1, String o2) {
-				return compareRepositoryNames(o1, o2);
-			}
-		});
-	}
-
-	public static String getColor(String value) {
-		int cs = 0;
-		for (char c : getMD5(value.toLowerCase()).toCharArray()) {
-			cs += c;
-		}
-		int n = (cs % 360);		
-		float hue = ((float) n) / 360;
-		return hsvToRgb(hue, 0.90f, 0.65f);
-	}
-
-	public static String hsvToRgb(float hue, float saturation, float value) {
-		int h = (int) (hue * 6);
-		float f = hue * 6 - h;
-		float p = value * (1 - saturation);
-		float q = value * (1 - f * saturation);
-		float t = value * (1 - (1 - f) * saturation);
-
-		switch (h) {
-		case 0:
-			return rgbToString(value, t, p);
-		case 1:
-			return rgbToString(q, value, p);
-		case 2:
-			return rgbToString(p, value, t);
-		case 3:
-			return rgbToString(p, q, value);
-		case 4:
-			return rgbToString(t, p, value);
-		case 5:
-			return rgbToString(value, p, q);
-		default:
-			throw new RuntimeException(
-					"Something went wrong when converting from HSV to RGB. Input was " + hue + ", "
-							+ saturation + ", " + value);
-		}
-	}
-
-	public static String rgbToString(float r, float g, float b) {
-		String rs = Integer.toHexString((int) (r * 256));
-		String gs = Integer.toHexString((int) (g * 256));
-		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
-	 */
-	public static String stripDotGit(String value) {
-		if (value.toLowerCase().endsWith(".git")) {
-			return value.substring(0, value.length() - 4);
-		}
-		return value;
-	}
-	
-	/**
-	 * Count the number of lines in a string.
-	 * 
-	 * @param value
-	 * @return the line count
-	 */
-	public static int countLines(String value) {
-		if (isEmpty(value)) {
-			return 0;
-		}
-		return value.split("\n").length;
-	}
-	
-	/**
-	 * Returns the file extension of a path.
-	 * 
-	 * @param path
-	 * @return a blank string or a file extension
-	 */
-	public static String getFileExtension(String path) {
-		int lastDot = path.lastIndexOf('.');
-		if (lastDot > -1) {
-			return path.substring(lastDot + 1);
-		}
-		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
-	 * @return a String with the replacements
-	 */
-	public static String replace(String inString, String oldPattern, String newPattern) {
-		StringBuilder sb = new StringBuilder();
-		int pos = 0; // our position in the old string
-		int index = inString.indexOf(oldPattern);
-		// the index of an occurrence we've found, or -1
-		int patLen = oldPattern.length();
-		while (index >= 0) {
-			sb.append(inString.substring(pos, index));
-			sb.append(newPattern);
-			pos = index + patLen;
-			index = inString.indexOf(oldPattern, pos);
-		}
-		sb.append(inString.substring(pos));
-		// 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
-	 */
-	public static String decodeString(byte [] content, String... charsets) {
-		Set<String> sets = new LinkedHashSet<String>();
-		if (!ArrayUtils.isEmpty(charsets)) {
-			sets.addAll(Arrays.asList(charsets));
-		}
-		String value = null;
-		sets.addAll(Arrays.asList("UTF-8", "ISO-8859-1", Charset.defaultCharset().name()));
-		for (String charset : sets) {
-			try {
-				Charset cs = Charset.forName(charset);
-				CharsetDecoder decoder = cs.newDecoder();
-				CharBuffer buffer = decoder.decode(ByteBuffer.wrap(content));
-				value = buffer.toString();
-				break;
-			} catch (CharacterCodingException e) {
-				// ignore and advance to the next charset
-			} catch (IllegalCharsetNameException e) {
-				// ignore illegal charset names
-			} catch (UnsupportedCharsetException e) {
-				// ignore unsupported charsets
-			}
-		}
-		if (value.startsWith("\uFEFF")) {
-			// strip UTF-8 BOM
-            return value.substring(1);
-        }
-		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
-	 */
-	public static String extractRepositoryPath(String url, String... urlpatterns) {
-		for (String urlPattern : urlpatterns) {
-			Pattern p = Pattern.compile(urlPattern);
-			Matcher m = p.matcher(url);
-			while (m.find()) {
-				String repositoryPath = m.group(1);
-				return repositoryPath;
-			}
-		}
-		// last resort
-		if (url.lastIndexOf('/') > -1) {
-			return url.substring(url.lastIndexOf('/') + 1);
-		}
-		return url;
-	}
-	
-	/**
-	 * Converts a string with \nnn sequences into a UTF-8 encoded string.
-	 * @param input
-	 * @return
-	 */
-	public static String convertOctal(String input) {
-		try {
-			ByteArrayOutputStream bytes = new ByteArrayOutputStream();
-			Pattern p = Pattern.compile("(\\\\\\d{3})");
-			Matcher m = p.matcher(input);
-			int i = 0;
-			while (m.find()) {
-				bytes.write(input.substring(i, m.start()).getBytes("UTF-8"));
-				// replace octal encoded value
-				// strip leading \ character
-				String oct = m.group().substring(1);
-				bytes.write(Integer.parseInt(oct, 8));
-				i = m.end();			
-			}
-			if (bytes.size() == 0) {
-				// no octal matches
-				return input;
-			} else {
-				if (i < input.length()) {
-					// add remainder of string
-					bytes.write(input.substring(i).getBytes("UTF-8"));
-				}
-			}
-			return bytes.toString("UTF-8");
-		} catch (Exception e) {
-			e.printStackTrace();
-		}
-		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. 
-	 * 
-	 * @param path
-	 * @return the first element in the path
-	 */
-	public static String getFirstPathElement(String path) {
-		if (path.indexOf('/') > -1) {
-			return path.substring(0, path.indexOf('/')).trim();
-		}
-		return "";
-	}
-	
-	/**
-	 * Returns the last path element of a path string
-	 * 
-	 * @param path
-	 * @return the last element in the path
-	 */
-	public static String getLastPathElement(String path) {
-		if (path.indexOf('/') > -1) {
-			return path.substring(path.lastIndexOf('/') + 1);
-		}
-		return path;
-	}
-	
-	/**
-	 * Variation of String.matches() which disregards case issues.
-	 * 
-	 * @param regex
-	 * @param input
-	 * @return true if the pattern matches
-	 */
-	public static boolean matchesIgnoreCase(String input, String regex) {
-		Pattern p = Pattern.compile(regex, Pattern.CASE_INSENSITIVE);
-		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
-	 */
-	public static String removeNewlines(String input) {
-		if (input == null) {
-			return "";
-		}
-		return input.replace('\n',' ').replace('\r',  ' ').trim();
-	}
-}
\ No newline at end of file
diff --git a/src/com/gitblit/utils/TimeUtils.java b/src/com/gitblit/utils/TimeUtils.java
deleted file mode 100644
index ec8871c..0000000
--- a/src/com/gitblit/utils/TimeUtils.java
+++ /dev/null
@@ -1,341 +0,0 @@
-/*
- * Copyright 2011 gitblit.com.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.gitblit.utils;
-
-import java.text.MessageFormat;
-import java.text.SimpleDateFormat;
-import java.util.Calendar;
-import java.util.Date;
-import java.util.ResourceBundle;
-
-/**
- * Utility class of time functions.
- * 
- * @author James Moger
- * 
- */
-public class TimeUtils {
-	public static final long MIN = 1000 * 60L;
-
-	public static final long HALFHOUR = MIN * 30L;
-
-	public static final long ONEHOUR = HALFHOUR * 2;
-
-	public static final long ONEDAY = ONEHOUR * 24L;
-
-	public static final long ONEYEAR = ONEDAY * 365L;
-	
-	private final ResourceBundle translation;
-	
-	public TimeUtils() {
-		this(null);
-	}
-	
-	public TimeUtils(ResourceBundle translation) {
-		this.translation = translation;
-	}
-
-	/**
-	 * Returns true if date is today.
-	 * 
-	 * @param date
-	 * @return true if date is today
-	 */
-	public static boolean isToday(Date date) {
-		return (System.currentTimeMillis() - date.getTime()) < ONEDAY;
-	}
-
-	/**
-	 * Returns true if date is yesterday.
-	 * 
-	 * @param date
-	 * @return true if date is yesterday
-	 */
-	public static boolean isYesterday(Date date) {
-		Calendar cal = Calendar.getInstance();
-		cal.setTime(new Date());
-		cal.add(Calendar.DATE, -1);
-		SimpleDateFormat df = new SimpleDateFormat("yyyyMMdd");
-		return df.format(cal.getTime()).equals(df.format(date));
-	}
-
-	/**
-	 * 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
-	 */
-	public String duration(int days) {
-		if (days <= 60) {
-			return (days > 1 ? translate(days, "gb.duration.days", "{0} days") : translate("gb.duration.oneDay", "1 day"));
-		} else if (days < 365) {
-			int rem = days % 30;
-			return translate(((days / 30) + (rem >= 15 ? 1 : 0)), "gb.duration.months", "{0} months");
-		} else {
-			int years = days / 365;
-			int rem = days % 365;
-			String yearsString = (years > 1 ? translate(years, "gb.duration.years", "{0} years") : translate("gb.duration.oneYear", "1 year"));
-			if (rem < 30) {
-				if (rem == 0) {
-					return yearsString;
-				} else {
-					return yearsString + (rem >= 15 ? (", " + translate("gb.duration.oneMonth", "1 month")): "");
-				}
-			} else {
-				int months = rem / 30;
-				int remDays = rem % 30;
-				if (remDays >= 15) {
-					months++;
-				}
-				String monthsString = yearsString + ", "
-						+ (months > 1 ? translate(months, "gb.duration.months", "{0} months") : translate("gb.duration.oneMonth", "1 month"));
-				return monthsString;
-			}
-		}
-	}
-
-	/**
-	 * Returns the number of minutes ago between the start time and the end
-	 * time.
-	 * 
-	 * @param date
-	 * @param endTime
-	 * @param roundup
-	 * @return difference in minutes
-	 */
-	public static int minutesAgo(Date date, long endTime, boolean roundup) {
-		long diff = endTime - date.getTime();
-		int mins = (int) (diff / MIN);
-		if (roundup && (diff % MIN) >= 30) {
-			mins++;
-		}
-		return mins;
-	}
-
-	/**
-	 * Return the difference in minutes between now and the date.
-	 * 
-	 * @param date
-	 * @param roundup
-	 * @return minutes ago
-	 */
-	public static int minutesAgo(Date date, boolean roundup) {
-		return minutesAgo(date, System.currentTimeMillis(), roundup);
-	}
-
-	/**
-	 * Return the difference in hours between now and the date.
-	 * 
-	 * @param date
-	 * @param roundup
-	 * @return hours ago
-	 */
-	public static int hoursAgo(Date date, boolean roundup) {
-		long diff = System.currentTimeMillis() - date.getTime();
-		int hours = (int) (diff / ONEHOUR);
-		if (roundup && (diff % ONEHOUR) >= HALFHOUR) {
-			hours++;
-		}
-		return hours;
-	}
-
-	/**
-	 * Return the difference in days between now and the date.
-	 * 
-	 * @param date
-	 * @return days ago
-	 */
-	public static int daysAgo(Date date) {
-		long today = ONEDAY * (System.currentTimeMillis()/ONEDAY);
-		long day = ONEDAY * (date.getTime()/ONEDAY);
-		long diff = today - day;
-		int days = (int) (diff / ONEDAY);
-		return days;
-	}
-
-	public String today() {
-		return translate("gb.time.today", "today");
-	}
-
-	public String yesterday() {
-		return translate("gb.time.yesterday", "yesterday");
-	}
-
-	/**
-	 * Returns the string representation of the duration between now and the
-	 * date.
-	 * 
-	 * @param date
-	 * @return duration as a string
-	 */
-	public String timeAgo(Date date) {
-		return timeAgo(date, false);
-	}
-
-	/**
-	 * Returns the CSS class for the date based on its age from Now.
-	 * 
-	 * @param date
-	 * @return the css class
-	 */
-	public String timeAgoCss(Date date) {
-		return timeAgo(date, true);
-	}
-
-	/**
-	 * 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
-	 */
-	private String timeAgo(Date date, boolean css) {
-		if (isToday(date) || isYesterday(date)) {
-			int mins = minutesAgo(date, true);
-			if (mins >= 120) {
-				if (css) {
-					return "age1";
-				}
-				int hours = hoursAgo(date, true);
-				if (hours > 23) {
-					return yesterday();
-				} else {
-					return translate(hours, "gb.time.hoursAgo", "{0} hours ago");
-				}
-			}
-			if (css) {
-				return "age0";
-			}
-			if (mins > 2) {
-				return translate(mins, "gb.time.minsAgo", "{0} mins ago");
-			}
-			return translate("gb.time.justNow", "just now");
-		} else {
-			int days = daysAgo(date);
-			if (css) {
-				if (days <= 7) {
-					return "age2";
-				} if (days <= 30) {
-					return "age3";
-				} else {
-					return "age4";
-				}
-			}
-			if (days < 365) {
-				if (days <= 30) {
-					return translate(days, "gb.time.daysAgo", "{0} days ago");
-				} else if (days <= 90) {
-					int weeks = days / 7;
-					if (weeks == 12) {
-						return translate(3, "gb.time.monthsAgo", "{0} months ago");
-					} else {
-						return translate(weeks, "gb.time.weeksAgo", "{0} weeks ago");
-					}
-				}
-				int months = days / 30;
-				int weeks = (days % 30) / 7;
-				if (weeks >= 2) {
-					months++;
-				}
-				return translate(months, "gb.time.monthsAgo", "{0} months ago");
-			} else if (days == 365) {
-				return translate("gb.time.oneYearAgo", "1 year ago");
-			} else {
-				int yr = days / 365;
-				days = days % 365;
-				int months = (yr * 12) + (days / 30);
-				if (months > 23) {
-					return translate(yr, "gb.time.yearsAgo", "{0} years ago");
-				} else {
-					return translate(months, "gb.time.monthsAgo", "{0} months ago");
-				}
-			}
-		}
-	}
-	
-	public String inFuture(Date date) {
-		long diff = date.getTime() - System.currentTimeMillis();
-		if (diff > ONEDAY) {
-			double days = ((double) diff)/ONEDAY;
-			return translate((int) Math.round(days), "gb.time.inDays", "in {0} days");
-		} else {
-			double hours = ((double) diff)/ONEHOUR;
-			if (hours > 2) {
-				return translate((int) Math.round(hours), "gb.time.inHours", "in {0} hours");
-			} else {
-				int mins = (int) (diff/MIN);
-				return translate(mins, "gb.time.inMinutes", "in {0} minutes");
-			}
-		}
-	}
-	
-	private String translate(String key, String defaultValue) {
-		String value = defaultValue;
-		if (translation != null && translation.containsKey(key)) {
-			String aValue = translation.getString(key);
-			if (!StringUtils.isEmpty(aValue)) {
-				value = aValue;
-			}
-		}
-		return value;
-	}
-	
-	private String translate(int val, String key, String defaultPattern) {
-		String pattern = defaultPattern;
-		if (translation != null && translation.containsKey(key)) {
-			String aValue = translation.getString(key);
-			if (!StringUtils.isEmpty(aValue)) {
-				pattern = aValue;
-			}
-		}
-		return MessageFormat.format(pattern, val);
-	}
-
-	/**
-	 * Convert a frequency string into minutes.
-	 * 
-	 * @param frequency
-	 * @return minutes
-	 */
-	public static int convertFrequencyToMinutes(String frequency) {
-		// parse the frequency
-		frequency = frequency.toLowerCase();
-		int mins = 60;
-		if (!StringUtils.isEmpty(frequency)) {
-			try {
-				String str = frequency.trim();
-				if (frequency.indexOf(' ') > -1) {
-					str = str.substring(0, str.indexOf(' ')).trim();
-				}
-				mins = (int) Float.parseFloat(str);
-			} catch (NumberFormatException e) {
-			}
-			if (mins < 5) {
-				mins = 5;
-			}
-		}
-		if (frequency.indexOf("day") > -1) {
-			// convert to minutes
-			mins *= 1440;
-		} else if (frequency.indexOf("hour") > -1) {
-			// convert to minutes
-			mins *= 60;
-		}
-		return mins;
-	}
-}
diff --git a/src/com/gitblit/wicket/AuthorizationStrategy.java b/src/com/gitblit/wicket/AuthorizationStrategy.java
deleted file mode 100644
index 21bd1b7..0000000
--- a/src/com/gitblit/wicket/AuthorizationStrategy.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * 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.wicket;
-
-import org.apache.wicket.Component;
-import org.apache.wicket.RestartResponseException;
-import org.apache.wicket.authorization.IUnauthorizedComponentInstantiationListener;
-import org.apache.wicket.authorization.strategies.page.AbstractPageAuthorizationStrategy;
-
-import com.gitblit.GitBlit;
-import com.gitblit.Keys;
-import com.gitblit.models.UserModel;
-import com.gitblit.wicket.pages.BasePage;
-import com.gitblit.wicket.pages.RepositoriesPage;
-
-public class AuthorizationStrategy extends AbstractPageAuthorizationStrategy implements
-		IUnauthorizedComponentInstantiationListener {
-
-	public AuthorizationStrategy() {
-	}
-
-	@SuppressWarnings({ "unchecked", "rawtypes" })
-	@Override
-	protected boolean isPageAuthorized(Class pageClass) {
-		if (RepositoriesPage.class.equals(pageClass)) {
-			// allow all requests to get to the RepositoriesPage with its inline
-			// authentication form
-			return true;
-		}
-
-		if (BasePage.class.isAssignableFrom(pageClass)) {
-			boolean authenticateView = GitBlit.getBoolean(Keys.web.authenticateViewPages, true);
-			boolean authenticateAdmin = GitBlit.getBoolean(Keys.web.authenticateAdminPages, true);
-			boolean allowAdmin = GitBlit.getBoolean(Keys.web.allowAdministration, true);
-
-			GitBlitWebSession session = GitBlitWebSession.get();
-			if (authenticateView && !session.isLoggedIn()) {
-				// authentication required
-				session.cacheRequest(pageClass);
-				return false;
-			}
-
-			UserModel user = session.getUser();
-			if (pageClass.isAnnotationPresent(RequiresAdminRole.class)) {
-				// admin page
-				if (allowAdmin) {
-					if (authenticateAdmin) {
-						// authenticate admin
-						if (user != null) {
-							return user.canAdmin();
-						}
-						return false;
-					} else {
-						// no admin authentication required
-						return true;
-					}
-				} else {
-					// admin prohibited
-					return false;
-				}
-			}
-		}
-		return true;
-	}
-
-	@Override
-	public void onUnauthorizedInstantiation(Component component) {
-		if (component instanceof BasePage) {
-			throw new RestartResponseException(RepositoriesPage.class);
-		}
-	}
-}
diff --git a/src/com/gitblit/wicket/GitBlitWebApp.java b/src/com/gitblit/wicket/GitBlitWebApp.java
deleted file mode 100644
index 4e32daa..0000000
--- a/src/com/gitblit/wicket/GitBlitWebApp.java
+++ /dev/null
@@ -1,158 +0,0 @@
-/*
- * 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.wicket;
-
-import org.apache.wicket.Application;
-import org.apache.wicket.Page;
-import org.apache.wicket.Request;
-import org.apache.wicket.Response;
-import org.apache.wicket.Session;
-import org.apache.wicket.markup.html.WebPage;
-import org.apache.wicket.protocol.http.WebApplication;
-
-import com.gitblit.GitBlit;
-import com.gitblit.Keys;
-import com.gitblit.wicket.pages.ActivityPage;
-import com.gitblit.wicket.pages.BlamePage;
-import com.gitblit.wicket.pages.BlobDiffPage;
-import com.gitblit.wicket.pages.BlobPage;
-import com.gitblit.wicket.pages.BranchesPage;
-import com.gitblit.wicket.pages.CommitDiffPage;
-import com.gitblit.wicket.pages.CommitPage;
-import com.gitblit.wicket.pages.DocsPage;
-import com.gitblit.wicket.pages.FederationRegistrationPage;
-import com.gitblit.wicket.pages.ForkPage;
-import com.gitblit.wicket.pages.ForksPage;
-import com.gitblit.wicket.pages.GitSearchPage;
-import com.gitblit.wicket.pages.GravatarProfilePage;
-import com.gitblit.wicket.pages.HistoryPage;
-import com.gitblit.wicket.pages.LogPage;
-import com.gitblit.wicket.pages.LuceneSearchPage;
-import com.gitblit.wicket.pages.MarkdownPage;
-import com.gitblit.wicket.pages.MetricsPage;
-import com.gitblit.wicket.pages.PatchPage;
-import com.gitblit.wicket.pages.ProjectPage;
-import com.gitblit.wicket.pages.ProjectsPage;
-import com.gitblit.wicket.pages.RawPage;
-import com.gitblit.wicket.pages.RepositoriesPage;
-import com.gitblit.wicket.pages.ReviewProposalPage;
-import com.gitblit.wicket.pages.SummaryPage;
-import com.gitblit.wicket.pages.TagPage;
-import com.gitblit.wicket.pages.TagsPage;
-import com.gitblit.wicket.pages.TicketPage;
-import com.gitblit.wicket.pages.TicketsPage;
-import com.gitblit.wicket.pages.TreePage;
-import com.gitblit.wicket.pages.UserPage;
-import com.gitblit.wicket.pages.UsersPage;
-
-public class GitBlitWebApp extends WebApplication {
-
-	@Override
-	public void init() {
-		super.init();
-
-		// Setup page authorization mechanism
-		boolean useAuthentication = GitBlit.getBoolean(Keys.web.authenticateViewPages, false)
-				|| GitBlit.getBoolean(Keys.web.authenticateAdminPages, false);
-		if (useAuthentication) {
-			AuthorizationStrategy authStrategy = new AuthorizationStrategy();
-			getSecuritySettings().setAuthorizationStrategy(authStrategy);
-			getSecuritySettings().setUnauthorizedComponentInstantiationListener(authStrategy);
-		}
-
-		// Grab Browser info (like timezone, etc)
-		if (GitBlit.getBoolean(Keys.web.useClientTimezone, false)) {
-			getRequestCycleSettings().setGatherExtendedBrowserInfo(true);
-		}
-
-		// configure the resource cache duration to 90 days for deployment
-		if (!GitBlit.isDebugMode()) {
-			getResourceSettings().setDefaultCacheDuration(90 * 86400);
-		}
-
-		// setup the standard gitweb-ish urls
-		mount("/summary", SummaryPage.class, "r");
-		mount("/log", LogPage.class, "r", "h");
-		mount("/tags", TagsPage.class, "r");
-		mount("/branches", BranchesPage.class, "r");
-		mount("/commit", CommitPage.class, "r", "h");
-		mount("/tag", TagPage.class, "r", "h");
-		mount("/tree", TreePage.class, "r", "h", "f");
-		mount("/blob", BlobPage.class, "r", "h", "f");
-		mount("/raw", RawPage.class, "r", "h", "f");
-		mount("/blobdiff", BlobDiffPage.class, "r", "h", "f");
-		mount("/commitdiff", CommitDiffPage.class, "r", "h");
-		mount("/patch", PatchPage.class, "r", "h", "f");
-		mount("/history", HistoryPage.class, "r", "h", "f");
-		mount("/search", GitSearchPage.class);
-		mount("/metrics", MetricsPage.class, "r");
-		mount("/blame", BlamePage.class, "r", "h", "f");
-		mount("/users", UsersPage.class);
-
-		// setup ticket urls
-		mount("/tickets", TicketsPage.class, "r");
-		mount("/ticket", TicketPage.class, "r", "f");
-
-		// setup the markdown urls
-		mount("/docs", DocsPage.class, "r");
-		mount("/markdown", MarkdownPage.class, "r", "h", "f");
-
-		// federation urls
-		mount("/proposal", ReviewProposalPage.class, "t");
-		mount("/registration", FederationRegistrationPage.class, "u", "n");
-
-		mount("/activity", ActivityPage.class, "r", "h");
-		mount("/gravatar", GravatarProfilePage.class, "h");
-		mount("/lucene", LuceneSearchPage.class);
-		mount("/project", ProjectPage.class, "p");
-		mount("/projects", ProjectsPage.class);
-		mount("/user", UserPage.class, "user");
-		mount("/forks", ForksPage.class, "r");
-		mount("/fork", ForkPage.class, "r");
-	}
-
-	private void mount(String location, Class<? extends WebPage> clazz, String... parameters) {
-		if (parameters == null) {
-			parameters = new String[] {};
-		}
-		if (!GitBlit.getBoolean(Keys.web.mountParameters, true)) {
-			parameters = new String[] {};
-		}
-		mount(new GitblitParamUrlCodingStrategy(location, clazz, parameters));
-	}
-
-	@Override
-	public Class<? extends Page> getHomePage() {
-		return RepositoriesPage.class;
-	}
-	
-	@Override
-	public final Session newSession(Request request, Response response) {
-		return new GitBlitWebSession(request);
-	}
-
-	@Override
-	public final String getConfigurationType() {
-		if (GitBlit.isDebugMode()) {
-			return Application.DEVELOPMENT;
-		}
-		return Application.DEPLOYMENT;
-	}
-
-	public static GitBlitWebApp get() {
-		return (GitBlitWebApp) WebApplication.get();
-	}
-}
diff --git a/src/com/gitblit/wicket/GitBlitWebApp.properties b/src/com/gitblit/wicket/GitBlitWebApp.properties
deleted file mode 100644
index a993f9f..0000000
--- a/src/com/gitblit/wicket/GitBlitWebApp.properties
+++ /dev/null
@@ -1,445 +0,0 @@
-gb.repository = repository
-gb.owner = owner
-gb.description = description
-gb.lastChange = last change
-gb.refs = refs
-gb.tag = tag
-gb.tags = tags
-gb.author = author
-gb.committer = committer
-gb.commit = commit
-gb.tree = tree
-gb.parent = parent
-gb.url = URL
-gb.history = history
-gb.raw = raw
-gb.object = object
-gb.ticketId = ticket id
-gb.ticketAssigned = assigned
-gb.ticketOpenDate = open date
-gb.ticketState = state
-gb.ticketComments = comments
-gb.view = view
-gb.local = local
-gb.remote = remote
-gb.branches = branches
-gb.patch = patch
-gb.diff = diff
-gb.log = log
-gb.moreLogs = more commits...
-gb.allTags = all tags...
-gb.allBranches = all branches...
-gb.summary = summary
-gb.ticket = ticket
-gb.newRepository = new repository
-gb.newUser = new user
-gb.commitdiff = commitdiff
-gb.tickets = tickets
-gb.pageFirst = first
-gb.pagePrevious prev
-gb.pageNext = next
-gb.head = HEAD
-gb.blame = blame
-gb.login = login
-gb.logout = logout
-gb.username = username
-gb.password = password
-gb.tagger = tagger
-gb.moreHistory = more history...
-gb.difftocurrent = diff to current
-gb.search = search
-gb.searchForAuthor = Search for commits authored by
-gb.searchForCommitter = Search for commits committed by
-gb.addition = addition
-gb.modification = modification
-gb.deletion = deletion
-gb.rename = rename
-gb.metrics = metrics
-gb.stats = stats
-gb.markdown = markdown
-gb.changedFiles = changed files 
-gb.filesAdded = {0} files added
-gb.filesModified = {0} files modified
-gb.filesDeleted = {0} files deleted
-gb.filesCopied = {0} files copied
-gb.filesRenamed = {0} files renamed
-gb.missingUsername = Missing Username
-gb.edit = edit
-gb.searchTypeTooltip = Select Search Type
-gb.searchTooltip = Search {0}
-gb.delete = delete
-gb.docs = docs
-gb.accessRestriction = access restriction
-gb.name = name
-gb.enableTickets = enable tickets
-gb.enableDocs = enable docs
-gb.save = save
-gb.showRemoteBranches = show remote branches
-gb.editUsers = edit users
-gb.confirmPassword = confirm password
-gb.restrictedRepositories = restricted repositories
-gb.canAdmin = can admin
-gb.notRestricted = anonymous view, clone, & push
-gb.pushRestricted = authenticated push
-gb.cloneRestricted = authenticated clone & push
-gb.viewRestricted = authenticated view, clone, & push
-gb.useTicketsDescription = readonly, distributed Ticgit issues
-gb.useDocsDescription = enumerates Markdown documentation in repository
-gb.showRemoteBranchesDescription = show remote branches
-gb.canAdminDescription = can administer Gitblit server
-gb.permittedUsers = permitted users
-gb.isFrozen = is frozen
-gb.isFrozenDescription = deny push operations
-gb.zip = zip
-gb.showReadme = show readme
-gb.showReadmeDescription = show a \"readme\" Markdown file on the summary page
-gb.nameDescription = use '/' to group repositories.  e.g. libraries/mycoollib.git
-gb.ownerDescription = the owner may edit repository settings
-gb.blob = blob
-gb.commitActivityTrend = commit activity trend
-gb.commitActivityDOW = commit activity by day of week
-gb.commitActivityAuthors = primary authors by commit activity
-gb.feed = feed
-gb.cancel = cancel
-gb.changePassword = change password
-gb.isFederated = is federated
-gb.federateThis = federate this repository
-gb.federateOrigin = federate the origin
-gb.excludeFromFederation = exclude from federation
-gb.excludeFromFederationDescription = block federated Gitblit instances from pulling this account
-gb.tokens = federation tokens
-gb.tokenAllDescription = all repositories, users, & settings
-gb.tokenUnrDescription = all repositories & users
-gb.tokenJurDescription = all repositories
-gb.federatedRepositoryDefinitions = repository definitions
-gb.federatedUserDefinitions = user definitions
-gb.federatedSettingDefinitions = setting definitions
-gb.proposals = federation proposals
-gb.received = received
-gb.type = type
-gb.token = token
-gb.repositories = repositories
-gb.proposal = proposal
-gb.frequency = frequency
-gb.folder = folder
-gb.lastPull = last pull
-gb.nextPull = next pull
-gb.inclusions = inclusions
-gb.exclusions = exclusions
-gb.registration = registration
-gb.registrations = federation registrations
-gb.sendProposal = propose
-gb.status = status
-gb.origin = origin
-gb.headRef = default branch (HEAD)
-gb.headRefDescription = change the ref that HEAD links to. e.g. refs/heads/master
-gb.federationStrategy = federation strategy
-gb.federationRegistration = federation registration
-gb.federationResults = federation pull results
-gb.federationSets = federation sets
-gb.message = message
-gb.myUrlDescription = the publicly accessible url for your Gitblit instance
-gb.destinationUrl = send to
-gb.destinationUrlDescription = the url of the Gitblit instance to send your proposal
-gb.users = users
-gb.federation = federation
-gb.error = error
-gb.refresh = refresh
-gb.browse = browse
-gb.clone = clone
-gb.filter = filter
-gb.create = create
-gb.servers = servers
-gb.recent = recent
-gb.available = available
-gb.selected = selected
-gb.size = size
-gb.downloading = downloading
-gb.loading = loading
-gb.starting = starting
-gb.general = general
-gb.settings = settings
-gb.manage = manage
-gb.lastLogin = last login
-gb.skipSizeCalculation = skip size calculation
-gb.skipSizeCalculationDescription = do not calculate the repository size (reduces page load time)
-gb.skipSummaryMetrics = skip summary metrics
-gb.skipSummaryMetricsDescription = do not calculate metrics on the summary page (reduces page load time)
-gb.accessLevel = access level
-gb.default = default
-gb.setDefault = set default
-gb.since = since
-gb.status = status
-gb.bootDate = boot date
-gb.servletContainer = servlet container
-gb.heapMaximum = maximum heap
-gb.heapAllocated = allocated heap
-gb.heapUsed = used heap
-gb.free = free
-gb.version = version
-gb.releaseDate = release date
-gb.date = date
-gb.activity = activity
-gb.subscribe = subscribe
-gb.branch = branch
-gb.maxHits = max hits
-gb.recentActivity = recent activity
-gb.recentActivityStats = last {0} days / {1} commits by {2} authors
-gb.recentActivityNone = last {0} days / none
-gb.dailyActivity = daily activity
-gb.activeRepositories = active repositories
-gb.activeAuthors = active authors
-gb.commits = commits
-gb.teams = teams
-gb.teamName = team name
-gb.teamMembers = team members
-gb.teamMemberships = team memberships
-gb.newTeam = new team
-gb.permittedTeams = permitted teams
-gb.emptyRepository = empty repository
-gb.repositoryUrl = repository url
-gb.mailingLists = mailing lists
-gb.preReceiveScripts = pre-receive scripts
-gb.postReceiveScripts = post-receive scripts
-gb.hookScripts = hook scripts
-gb.customFields = custom fields
-gb.customFieldsDescription = custom fields available to Groovy hooks
-gb.accessPermissions = access permissions
-gb.filters = filters
-gb.generalDescription = common settings
-gb.accessPermissionsDescription = restrict access by users and teams
-gb.accessPermissionsForUserDescription = set team memberships or grant access to specific restricted repositories
-gb.accessPermissionsForTeamDescription = set team members and grant access to specific restricted repositories
-gb.federationRepositoryDescription = share this repository with other Gitblit servers
-gb.hookScriptsDescription = run Groovy scripts on pushes to this Gitblit server
-gb.reset = reset
-gb.pages = pages
-gb.workingCopy = working copy
-gb.workingCopyWarning = this repository has a working copy and can not receive pushes
-gb.query = query
-gb.queryHelp = Standard query syntax is supported.<p/><p/>Please see <a target="_new" href="http://lucene.apache.org/core/old_versioned_docs/versions/3_5_0/queryparsersyntax.html">Lucene Query Parser Syntax</a> for details.
-gb.queryResults = results {0} - {1} ({2} hits)
-gb.noHits = no hits
-gb.authored = authored
-gb.committed = committed
-gb.indexedBranches = indexed branches
-gb.indexedBranchesDescription = select the branches to include in your Lucene index
-gb.noIndexedRepositoriesWarning = none of your repositories are configured for Lucene indexing
-gb.undefinedQueryWarning = query is undefined!
-gb.noSelectedRepositoriesWarning = please select one or more repositories!
-gb.luceneDisabled = Lucene indexing is disabled
-gb.failedtoRead = Failed to read
-gb.isNotValidFile = is not a valid file
-gb.failedToReadMessage = Failed to read default message from {0}!
-gb.passwordsDoNotMatch = Passwords do not match!
-gb.passwordTooShort = Password is too short. Minimum length is {0} characters.
-gb.passwordChanged = Password successfully changed.
-gb.passwordChangeAborted = Password change aborted.
-gb.pleaseSetRepositoryName = Please set repository name!
-gb.illegalLeadingSlash = Leading root folder references (/) are prohibited.
-gb.illegalRelativeSlash = Relative folder references (../) are prohibited.
-gb.illegalCharacterRepositoryName = Illegal character ''{0}'' in repository name!
-gb.selectAccessRestriction = Please select access restriction!
-gb.selectFederationStrategy = Please select federation strategy!
-gb.pleaseSetTeamName = Please enter a teamname!
-gb.teamNameUnavailable = Team name ''{0}'' is unavailable.
-gb.teamMustSpecifyRepository = A team must specify at least one repository.
-gb.teamCreated = New team ''{0}'' successfully created.
-gb.pleaseSetUsername = Please enter a username!
-gb.usernameUnavailable = Username ''{0}'' is unavailable.
-gb.combinedMd5Rename = Gitblit is configured for combined-md5 password hashing. You must enter a new password on account rename.
-gb.userCreated = New user ''{0}'' successfully created.
-gb.couldNotFindFederationRegistration = Could not find federation registration!
-gb.failedToFindGravatarProfile = Failed to find Gravatar profile for {0}
-gb.branchStats = {0} commits and {1} tags in {2}
-gb.repositoryNotSpecified = Repository not specified!
-gb.repositoryNotSpecifiedFor = Repository not specified for {0}!
-gb.canNotLoadRepository = Can not load repository
-gb.commitIsNull = Commit is null
-gb.unauthorizedAccessForRepository = Unauthorized access for repository
-gb.failedToFindCommit = Failed to find commit \"{0}\" in {1} for {2} page!
-gb.couldNotFindFederationProposal = Could not find federation proposal!
-gb.invalidUsernameOrPassword = Invalid username or password!
-gb.OneProposalToReview = There is 1 federation proposal awaiting review. 
-gb.nFederationProposalsToReview = There are {0} federation proposals awaiting review.
-gb.couldNotFindTag = Could not find tag {0}
-gb.couldNotCreateFederationProposal = Could not create federation proposal!
-gb.pleaseSetGitblitUrl = Please enter your Gitblit url!
-gb.pleaseSetDestinationUrl = Please enter a destination url for your proposal!
-gb.proposalReceived = Proposal successfully received by {0}.
-gb.noGitblitFound = Sorry, {0} could not find a Gitblit instance at {1}.
-gb.noProposals = Sorry, {0} is not accepting proposals at this time.
-gb.noFederation = Sorry, {0} is not configured to federate with any Gitblit instances.
-gb.proposalFailed = Sorry, {0} did not receive any proposal data!
-gb.proposalError = Sorry, {0} reports that an unexpected error occurred!
-gb.failedToSendProposal = Failed to send proposal!
-gb.userServiceDoesNotPermitAddUser = {0} does not permit adding a user account!
-gb.userServiceDoesNotPermitPasswordChanges = {0} does not permit password changes!
-gb.displayName = display name
-gb.emailAddress = email address
-gb.errorAdminLoginRequired = Administration requires a login
-gb.errorOnlyAdminMayCreateRepository = Only an administrator may create a repository
-gb.errorOnlyAdminOrOwnerMayEditRepository = Only an administrator or the owner may edit a repository
-gb.errorAdministrationDisabled = Administration is disabled
-gb.lastNDays = last {0} days
-gb.completeGravatarProfile = Complete profile on Gravatar.com
-gb.none = none
-gb.line = line
-gb.content = content
-gb.empty = empty
-gb.inherited = inherited
-gb.deleteRepository = Delete repository \"{0}\"?
-gb.repositoryDeleted = Repository ''{0}'' deleted.
-gb.repositoryDeleteFailed = Failed to delete repository ''{0}''!
-gb.deleteUser = Delete user \"{0}\"?
-gb.userDeleted = User ''{0}'' deleted.
-gb.userDeleteFailed = Failed to delete user ''{0}''!
-gb.time.justNow = just now
-gb.time.today = today
-gb.time.yesterday = yesterday
-gb.time.minsAgo = {0} mins ago
-gb.time.hoursAgo = {0} hours ago
-gb.time.daysAgo = {0} days ago
-gb.time.weeksAgo = {0} weeks ago
-gb.time.monthsAgo = {0} months ago
-gb.time.oneYearAgo = 1 year ago
-gb.time.yearsAgo = {0} years ago
-gb.duration.oneDay = 1 day
-gb.duration.days = {0} days
-gb.duration.oneMonth = 1 month
-gb.duration.months = {0} months
-gb.duration.oneYear = 1 year
-gb.duration.years = {0} years
-gb.authorizationControl = authorization control
-gb.allowAuthenticatedDescription = grant RW+ permission to all authenticated users
-gb.allowNamedDescription = grant fine-grained permissions to named users or teams
-gb.markdownFailure = Failed to parse Markdown content!
-gb.clearCache = clear cache
-gb.projects = projects
-gb.project = project
-gb.allProjects = all projects
-gb.copyToClipboard = copy to clipboard
-gb.fork = fork
-gb.forks = forks
-gb.forkRepository = fork {0}?
-gb.repositoryForked = {0} has been forked
-gb.repositoryForkFailed= fork has failed
-gb.personalRepositories = personal repositories
-gb.allowForks = allow forks
-gb.allowForksDescription = allow authorized users to fork this repository
-gb.forkedFrom = forked from
-gb.canFork = can fork
-gb.canForkDescription = can fork authorized repositories to personal repositories
-gb.myFork = view my fork
-gb.forksProhibited = forks prohibited
-gb.forksProhibitedWarning = this repository forbids forks
-gb.noForks = {0} has no forks
-gb.forkNotAuthorized = sorry, you are not authorized to fork {0}
-gb.forkInProgress = fork in progress
-gb.preparingFork = preparing your fork...
-gb.isFork = is fork
-gb.canCreate = can create
-gb.canCreateDescription = can create personal repositories
-gb.illegalPersonalRepositoryLocation = your personal repository must be located at \"{0}\"
-gb.verifyCommitter = verify committer
-gb.verifyCommitterDescription = require committer identity to match pushing Gitblt user account
-gb.verifyCommitterNote = all merges require "--no-ff" to enforce committer identity
-gb.repositoryPermissions = repository permissions
-gb.userPermissions = user permissions
-gb.teamPermissions = team permissions
-gb.add = add
-gb.noPermission = DELETE THIS PERMISSION
-gb.excludePermission = {0} (exclude)
-gb.viewPermission = {0} (view)
-gb.clonePermission = {0} (clone)
-gb.pushPermission = {0} (push)
-gb.createPermission = {0} (push, ref creation)
-gb.deletePermission = {0} (push, ref creation+deletion)
-gb.rewindPermission = {0} (push, ref creation+deletion+rewind)
-gb.permission = permission
-gb.regexPermission = this permission is set from regular expression \"{0}\"
-gb.accessDenied = access denied
-gb.busyCollectingGarbage = sorry, Gitblit is busy collecting garbage in {0}
-gb.gcPeriod = GC period
-gb.gcPeriodDescription = duration between garbage collections
-gb.gcThreshold = GC threshold
-gb.gcThresholdDescription = minimum total size of loose objects to trigger early garbage collection
-gb.ownerPermission = repository owner
-gb.administrator = admin
-gb.administratorPermission = Gitblit administrator
-gb.team = team
-gb.teamPermission = permission set by \"{0}\" team membership
-gb.missing = missing!
-gb.missingPermission = the repository for this permission is missing!
-gb.mutable = mutable
-gb.specified = specified
-gb.effective = effective
-gb.organizationalUnit = organizational unit
-gb.organization = organization
-gb.locality = locality
-gb.stateProvince = state or province
-gb.countryCode = country code
-gb.properties = properties
-gb.issued = issued
-gb.expires = expires
-gb.expired = expired
-gb.expiring = expiring
-gb.revoked = revoked
-gb.serialNumber = serial number
-gb.certificates = certificates
-gb.newCertificate = new certificate
-gb.revokeCertificate = revoke certificate
-gb.sendEmail = send email
-gb.passwordHint = password hint
-gb.ok = ok
-gb.invalidExpirationDate = invalid expiration date!
-gb.passwordHintRequired = password hint required!
-gb.viewCertificate = view certificate
-gb.subject = subject
-gb.issuer = issuer
-gb.validFrom = valid from
-gb.validUntil = valid until
-gb.publicKey = public key
-gb.signatureAlgorithm = signature algorithm
-gb.sha1FingerPrint = SHA-1 Fingerprint
-gb.md5FingerPrint = MD5 Fingerprint
-gb.reason = reason
-gb.revokeCertificateReason = Please select a reason for certificate revocation
-gb.unspecified = unspecified
-gb.keyCompromise = key compromise
-gb.caCompromise = CA compromise
-gb.affiliationChanged = affiliation changed
-gb.superseded = superseded
-gb.cessationOfOperation = cessation of operation
-gb.privilegeWithdrawn = privilege withdrawn
-gb.time.inMinutes = in {0} mins
-gb.time.inHours = in {0} hours
-gb.time.inDays = in {0} days
-gb.hostname = hostname
-gb.hostnameRequired = Please enter a hostname
-gb.newSSLCertificate = new server SSL certificate
-gb.newCertificateDefaults = new certificate defaults
-gb.duration = duration
-gb.certificateRevoked = Certificate {0,number,0} has been revoked
-gb.clientCertificateGenerated = Successfully generated new client certificate for {0}
-gb.sslCertificateGenerated = Successfully generated new server SSL certificate for {0}
-gb.newClientCertificateMessage = NOTE:\nThe 'password' is not the user's password, it is the password to protect the user's keystore.  This password is not saved so you must also enter a 'hint' which will be included in the user's README instructions.
-gb.certificate = certificate
-gb.emailCertificateBundle = email client certificate bundle
-gb.pleaseGenerateClientCertificate = Please generate a client certificate for {0}
-gb.clientCertificateBundleSent = Client certificate bundle for {0} sent
-gb.enterKeystorePassword = Please enter the Gitblit keystore password
-gb.warning = warning
-gb.jceWarning = Your Java Runtime Environment does not have the \"JCE Unlimited Strength Jurisdiction Policy\" files.\nThis will limit the length of passwords you may use to encrypt your keystores to 7 characters.\nThese policy files are an optional download from Oracle.\n\nWould you like to continue and generate the certificate infrastructure anyway?\n\nAnswering No will direct your browser to Oracle's download page so that you may download the policy files.
-gb.maxActivityCommits = max activity commits
-gb.maxActivityCommitsDescription = maximum number of commits to contribute to the Activity page
-gb.noMaximum = no maximum
-gb.attributes = attributes
-gb.serveCertificate = serve https with this certificate
-gb.sslCertificateGeneratedRestart = Successfully generated new server SSL certificate for {0}.\nYou must restart Gitblit to use the new certificate.\n\nIf you are launching with the '--alias' parameter you will have to set that to ''--alias {0}''.
-gb.validity = validity
-gb.siteName = site name
-gb.siteNameDescription = short, descriptive name of your server 
-gb.excludeFromActivity = exclude from activity page
-gb.isSparkleshared = repository is Sparkleshared
-gb.owners = owners
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/GitBlitWebApp_es.properties b/src/com/gitblit/wicket/GitBlitWebApp_es.properties
deleted file mode 100644
index 64c9ca1..0000000
--- a/src/com/gitblit/wicket/GitBlitWebApp_es.properties
+++ /dev/null
@@ -1,443 +0,0 @@
-gb.repository = Repositorio
-gb.owner = Propietario
-gb.description = Descripci\u00F3n
-gb.lastChange = Actualizado
-gb.refs = Refs
-gb.tag = Etiqueta
-gb.tags = Etiquetas
-gb.author = Autor
-gb.committer = Consignador
-gb.commit = Consigna
-gb.tree = \u00C1rbol
-gb.parent = Antecesor
-gb.url = URL
-gb.history = Hist\u00F3rico
-gb.raw = Bruto
-gb.object = Objeto
-gb.ticketId = Id Ticket
-gb.ticketAssigned = Asignado
-gb.ticketOpenDate = Fecha de apertura
-gb.ticketState = Estado
-gb.ticketComments = Comentarios
-gb.view = Ver
-gb.local = Local
-gb.remote = Remoto
-gb.branches = Ramas
-gb.patch = Parche
-gb.diff = Dif
-gb.log = Reg.
-gb.moreLogs = M\u00E1s Consignas...
-gb.allTags = Todas las Etiquetas...
-gb.allBranches = Todas las Ramas...
-gb.summary = Resumen
-gb.ticket = Ticket
-gb.newRepository = Nuevo Repositorio
-gb.newUser = Nuevo usuario
-gb.commitdiff = Dif Consigna
-gb.tickets = Tickets
-gb.pageFirst = Primera
-gb.pagePrevious = Anterior
-gb.pageNext = Siguiente
-gb.head = HEAD
-gb.blame = Acuse
-gb.login = Idenfiticarse
-gb.logout = Salir
-gb.username = Usuario
-gb.password = Contrase\u00F1a
-gb.tagger = Etiquetador
-gb.moreHistory = M\u00E1s hist\u00F3ricos...
-gb.difftocurrent = Dif con actual
-gb.search = Buscar
-gb.searchForAuthor = Buscar consignas de autor por
-gb.searchForCommitter = Buscar consignas enviadas por
-gb.addition = Adici\u00F3n
-gb.modification = Modificaci\u00F3n
-gb.deletion = Eliminado
-gb.rename = Renombrar
-gb.metrics = Movimientos
-gb.stats = Estad&iacute;sticas
-gb.markdown = Markdown
-gb.changedFiles = Archivos cambiados 
-gb.filesAdded = {0} Archivos a\u00F1adidos
-gb.filesModified = {0} Archivos modificados 
-gb.filesDeleted = {0} Archivos eliminados 
-gb.filesCopied = {0} Archivos copiados 
-gb.filesRenamed = {0} Archivos renombrados 
-gb.missingUsername = Falta usuario
-gb.edit = Editar
-gb.searchTypeTooltip = Seleccionar tipo de b\u00FAsqueda
-gb.searchTooltip = Buscar {0}
-gb.delete = Eliminar
-gb.docs = Docs
-gb.accessRestriction = Restricci\u00F3n de acceso
-gb.name = Nombre
-gb.enableTickets = Habilitar tickets
-gb.enableDocs = Habilitar Docs
-gb.save = Guardar
-gb.showRemoteBranches = Mostrar ramas remotas
-gb.editUsers = Editar usuarios
-gb.confirmPassword = Confirmar contrase\u00F1a
-gb.restrictedRepositories = Repositorios restringidos
-gb.canAdmin = Puede Administrar
-gb.notRestricted = An\u00F3nimos pueden Ver, clonar y empujar
-gb.pushRestricted = Autentificados pueden empujar
-gb.cloneRestricted = Autentificados pueden clonar y empujar
-gb.viewRestricted = Autentificados pueden Ver, clonar y empujar
-gb.useTicketsDescription = Distribuir temas mediante Ticgit (s\u00F3lo lecura)
-gb.useDocsDescription = Enumerar documentaci\u00F3n Markdown en el Repositorio.
-gb.showRemoteBranchesDescription = Mostrar Ramas Remotas
-gb.canAdminDescription = Puede administrar el servidor de GitBlit
-gb.permittedUsers = Usuarios permitidos
-gb.isFrozen = Est\u00E1 congelado
-gb.isFrozenDescription = No se le puede empujar
-gb.zip = Zip
-gb.showReadme = Ver l\u00E9eme
-gb.showReadmeDescription = Mostrar el archivo \"l\u00E9eme\" de Markdown en la p\u00E1gina resumen
-gb.nameDescription = Usa '/' para agrupar repositorios. ej. librerias/mylibreria.git
-gb.ownerDescription = El propietario puede editar la configuraci\u00F3n del repositorio
-gb.blob = Objeto
-gb.commitActivityTrend = Tendencia de actividad del repositorio
-gb.commitActivityDOW = Actividad de consignas por d\u00EDa de la semana
-gb.commitActivityAuthors = Principales autores por actividad de consignas
-gb.feed = Sindicaci\u00F3n
-gb.cancel = Cancelar
-gb.changePassword = Cambiar contrase\u00F1a
-gb.isFederated = Est\u00E1 federado
-gb.federateThis = Federar este repositorio
-gb.federateOrigin = Federar desde el origen
-gb.excludeFromFederation = Excluir de la federaci\u00F3n
-gb.excludeFromFederationDescription = Bloquear a esta cuenta el recibir de instancias federadas de GitBlit
-gb.tokens = Tarjetas de federaci\u00F3n
-gb.tokenAllDescription = Todos los repositorios, usuarios y configuraciones
-gb.tokenUnrDescription = Todos los repositorios y usuarios
-gb.tokenJurDescription = Todos los repositorios
-gb.federatedRepositoryDefinitions = Definiciones del repositorio
-gb.federatedUserDefinitions = Definiciones del usuario
-gb.federatedSettingDefinitions = Definiciones de configuraci\u00F3n
-gb.proposals = Propuestas de federaci\u00F3n
-gb.received = Recibida
-gb.type = Tipo
-gb.token = Tarjeta
-gb.repositories = Repositorios
-gb.proposal = Propuesta
-gb.frequency = Frecuencia
-gb.folder = Carpeta
-gb.lastPull = \u00DAltimo recibo
-gb.nextPull = Siguiente recibo
-gb.inclusions = Inclusiones
-gb.exclusions = Exclusiones
-gb.registration = Registro
-gb.registrations = Registros de federaci\u00F3n
-gb.sendProposal = Proponer
-gb.status = Estado
-gb.origin = Origen
-gb.headRef = Rama por defecto (HEAD)
-gb.headRefDescription = Cambiar la Ref. a la que apunta HEAD ej. refs/heads/master
-gb.federationStrategy = Estrategia de federaci\u00F3n
-gb.federationRegistration = Registro de federaci\u00F3n
-gb.federationResults = Resultados de recibos federados
-gb.federationSets = Grupos de federaci\u00F3n
-gb.message = Mensaje
-gb.myUrlDescription = La URL p\u00FAblica y accesible de tu instancia de GitBlit
-gb.destinationUrl = Enviar a
-gb.destinationUrlDescription = La URL de la instancia de GitBlit a la que env\u00EDas tu propuesta
-gb.users = Usuarios
-gb.federation = Federaci\u00F3n
-gb.error = Error
-gb.refresh = Actualizar
-gb.browse = Buscar
-gb.clone = Clonar
-gb.filter = Filtrar
-gb.create = Crear
-gb.servers = Servidores
-gb.recent = Recientes
-gb.available = Disponible
-gb.selected = Seleccionados
-gb.size = Tama\u00F1o
-gb.downloading = Descargando
-gb.loading = Cargando
-gb.starting = Iniciando
-gb.general = General
-gb.settings = Configuraci\u00F3n
-gb.manage = Administrar
-gb.lastLogin = \u00DAltimo acceso
-gb.skipSizeCalculation = Saltar comprobaciones de tama\u00F1o
-gb.skipSizeCalculationDescription = No calcular el tama\u00F1o del repositorio (Reduce tiempo de carga de la p\u00E1gina)
-gb.skipSummaryMetrics = Saltar resumen de estad\u00EDsticas
-gb.skipSummaryMetricsDescription = No calcular estad\u00EDsticas (Reduce tiempo de carga de la p\u00E1gina)
-gb.accessLevel = Nivel de acceso
-gb.default = Predeterminado
-gb.setDefault = Ajustar por defecto
-gb.since = Desde
-gb.status = Estado
-gb.bootDate = Fecha de inicio
-gb.servletContainer = Contenedor ServLet
-gb.heapMaximum = Pila m\u00E1xima
-gb.heapAllocated = Pila asignada
-gb.heapUsed = Pila usada
-gb.free = Libre
-gb.version = Versi\u00F3n
-gb.releaseDate = Fecha de lanzamiento
-gb.date = Fecha
-gb.activity = Actividad
-gb.subscribe = Suscribir
-gb.branch = Rama
-gb.maxHits = Coincidencias m\u00E1ximas
-gb.recentActivity = Actividad reciente
-gb.recentActivityStats = \u00DAltimo(s) {0} d\u00EDa(s) / {1} Consigna(s) de {2} Autor(es)
-gb.recentActivityNone = \u00DAltimo(s) {0} d\u00EDa(s) / Ninguna
-gb.dailyActivity = Actividad diaria
-gb.activeRepositories = Repositorios activos
-gb.activeAuthors = Autores activos
-gb.commits = Consignas
-gb.teams = Equipos
-gb.teamName = Nombre del Equipo
-gb.teamMembers = Suscriptores
-gb.teamMemberships = Inscripciones
-gb.newTeam = Nuevo Equipo
-gb.permittedTeams = Equipos permitidos
-gb.emptyRepository = Repositorio vac\u00EDo
-gb.repositoryUrl = URL del Repositorio
-gb.mailingLists = Listas de correo
-gb.preReceiveScripts = Scripts para pre-recibo
-gb.postReceiveScripts = Scripts para post-recibo
-gb.hookScripts = Scripts enganchados
-gb.customFields = Campos Propios
-gb.customFieldsDescription = Campos Propios disponibles para los engachados Groovy
-gb.accessPermissions = Permisos de acceso
-gb.filters = Filtros
-gb.generalDescription = Configuraciones comunes
-gb.accessPermissionsDescription = Restringir acceso a usuarios y Equipos
-gb.accessPermissionsForUserDescription = Modificar inscripciones y especificar acceso a repositorios o restringirlos
-gb.accessPermissionsForTeamDescription = A\u00F1ada miembros al Equipo y conceda o restrinja acceso a repositorios.
-gb.federationRepositoryDescription = Compartir este repositorio con otros servidores GitBlit
-gb.hookScriptsDescription = Scripts Groovy ha ejecutar cuando empujen a este servidor.
-gb.reset = Reinicializa
-gb.pages = P\u00E1ginas
-gb.workingCopy = Copia de trabajo
-gb.workingCopyWarning = Este repositorio tiene una copia de trabajo y no se le puede empujar
-gb.query = Consulta
-gb.queryHelp = Se admite la sintaxis de consulta est\u00E1ndar.<p/>Por favor, lee el: <a target="_new" href="http://lucene.apache.org/core/old_versioned_docs/versions/3_5_0/queryparsersyntax.html">Analizador sint\u00E1ctico de consultas de Lucene</a> para m\u00E1s detalles.
-gb.queryResults = Resultados {0} - {1} ({2} coincidencias)
-gb.noHits = Sin coincidencias
-gb.authored = Autor
-gb.committed = Consignado
-gb.indexedBranches = Ramas indexadas
-gb.indexedBranchesDescription = Selecciona las Ramas a incluir en tu \u00EDndice de Lucene
-gb.noIndexedRepositoriesWarning = Ninguno de tus repositorios est\u00E1 configurado para el indexado de Lucene
-gb.undefinedQueryWarning = \u00A1Consulta indefinida!
-gb.noSelectedRepositoriesWarning = \u00A1Por favor selecciona uno o m\u00E1s repositorios!
-gb.luceneDisabled = Indexado de Lucene deshabilitado
-gb.failedtoRead = Fallo de lectura
-gb.isNotValidFile = No es un archivo v\u00E1lido
-gb.failedToReadMessage = \u00A1Fallo al leer el mensaje por defecto de {0}!
-gb.passwordsDoNotMatch = \u00A1Las contrase\u00F1as no coinciden!
-gb.passwordTooShort = La contrase\u00F1a es muy corta. La longitud m\u00EDnima es de {0} caracteres.
-gb.passwordChanged = Contrase\u00F1a cambiada satisfactoriamente.
-gb.passwordChangeAborted = Cambio de contrase\u00F1a abortado.
-gb.pleaseSetRepositoryName = \u00A1Por favor introduce un nombre para el repositorio!
-gb.illegalLeadingSlash = Referencias a la carpeta ra\i00EDz (/) estu00E1n prohibidas.
-gb.illegalRelativeSlash = Referencias relativas a la carpeta (../) est\u00E1n prohibidas.
-gb.illegalCharacterRepositoryName = \u00A1Caracter ilegal ''{0}'' en el nombre del repositorio!
-gb.selectAccessRestriction = \u00A1Por favor selecciona la restricci\u00F3n de acceso!
-gb.selectFederationStrategy = \u00A1Por favor, selecciona la estrategia de federaci\u00F3n!
-gb.pleaseSetTeamName = \u00A1Por favor, introduce un nombre para el Equipo!
-gb.teamNameUnavailable = El nombre de Equipo ''{0}'' no est\u00E1 disponible.
-gb.teamMustSpecifyRepository = Debe especificar al menos un repositorio para el Equipo.
-gb.teamCreated = Nuevo Equipo ''{0}'' creado satisfactoriamente.
-gb.pleaseSetUsername = \u00A1Por favor, introduce un usuario!
-gb.usernameUnavailable = El usuario ''{0}'' no est\u00E1 disponible.
-gb.combinedMd5Rename = GitBlit est\u00E1 configurado para Hashes combinados md5. Debes introducir una nueva contrase\u00F1a para renombrar la cuenta.
-gb.userCreated = Nuevo usuario ''{0}'' creado satisfactoriamente.
-gb.couldNotFindFederationRegistration = \u00A1No se pudo encontrar el registro de federaci\u00F3n!
-gb.failedToFindGravatarProfile = Fallo al buscar el perfil Gravatar de {0}
-gb.branchStats = {0} consigna(s) y {1} etiqueta(s) en {2}
-gb.repositoryNotSpecified = /u00A1Repositorio no especificado!
-gb.repositoryNotSpecifiedFor = /u00A1Repositorio no especificado para {0}!
-gb.canNotLoadRepository = No se puede cargar el repositorio
-gb.commitIsNull = La consigna es nula
-gb.unauthorizedAccessForRepository = Acceso no autorizado al repositorio
-gb.failedToFindCommit = \u00A1Fallo al buscar la consigna \"{0}\" en {1} de {2} p\u00E1ginas!
-gb.couldNotFindFederationProposal = \u00A1No se puede encontrar una propuesta de federaci\u00F3n!
-gb.invalidUsernameOrPassword = \u00A1Usuario o contrase\u00F1a inv\u00E1lidos!
-gb.OneProposalToReview = Hay 1 petici\u00F3n de federaci\u00F3n esperando revisi\u00F3n.
-gb.nFederationProposalsToReview = Hay {0} peticiones de federaci\u00F3n esperando revisi\u00F3n.
-gb.couldNotFindTag = No se puede encontrar la etiqueta {0}
-gb.couldNotCreateFederationProposal = \u00A1No se puede crear una propuesta de federaci\u00F3n!
-gb.pleaseSetGitblitUrl = \u00A1Por favor, introduce la URL de tu GitBlit!
-gb.pleaseSetDestinationUrl = \u00A1Por favor, introduce la URL de destino para tu propuesta!
-gb.proposalReceived = Propuesta recibida satisfactoriamente por {0}.
-gb.noGitblitFound = Lo siento, {0} no puede encontrar una instancia de GitBlit en {1}.
-gb.noProposals = Lo siento, {0} no acepta propuestas en este momento.
-gb.noFederation = Lo siento, {0} no est\u00E1 configurado para Federar con ninguna instancia de GitBlit.
-gb.proposalFailed = /u00A1Lo siento, {0} no ha recibido ning\u00FAn dato de propuesta!
-gb.proposalError = /u00A1Lo siento, {0} informa de que ha ocurrido un error inesperado!
-gb.failedToSendProposal = /u00A1Fallo al enviar la propuesta!
-gb.userServiceDoesNotPermitAddUser = \u00A1{0} no permite a\u00F1adir una cuenta de usuario!
-gb.userServiceDoesNotPermitPasswordChanges = \u00A1{0} no permite cambio de contrase\u00F1a!
-gb.displayName = Nombre
-gb.emailAddress = Direcci\u00F3n de correo
-gb.errorAdminLoginRequired = La administraci&oacute;n requiere identificarse
-gb.errorOnlyAdminMayCreateRepository = S&oacute;lo un administrador puede crear un repositorio
-gb.errorOnlyAdminOrOwnerMayEditRepository = S&oacute;lo un administrador o el propietario puede editar un repositorio
-gb.errorAdministrationDisabled = La administraci&oacute;n est&aacute; desactivada
-gb.lastNDays = \u00FAltimos {0} d\u00EDas
-gb.completeGravatarProfile = Perfil completo en Gravatar.com
-gb.none = nadie
-gb.line = L\u00EDenea
-gb.content = Contenido
-gb.empty = vac\u00EDo
-gb.inherited = heredado
-gb.deleteRepository = \u00BFBorrar el repositorio \"{0}\"?
-gb.repositoryDeleted = Repositorio ''{0}'' borrado.
-gb.repositoryDeleteFailed = \u00A1Fallo al borrar el repositorio ''{0}''!
-gb.deleteUser = \u00BFEliminar usuario\"{0}\"?
-gb.userDeleted = Usuario ''{0}'' eliminado.
-gb.userDeleteFailed = \u00A1Fallo al eliminar usuario ''{0}''!
-gb.time.justNow = hace poco
-gb.time.today = hoy
-gb.time.yesterday = ayer
-gb.time.minsAgo = hace {0} min
-gb.time.hoursAgo = hace {0} horas
-gb.time.daysAgo = hace {0} d\u00EDas
-gb.time.weeksAgo = hace {0} semanas
-gb.time.monthsAgo = hace {0} meses
-gb.time.oneYearAgo = hace 1 a\u00F1o
-gb.time.yearsAgo = hace {0} a\u00F1os
-gb.duration.oneDay = 1 d\u00EDa
-gb.duration.days = {0} d\u00EDas
-gb.duration.oneMonth = 1 mes
-gb.duration.months = {0} meses
-gb.duration.oneYear = 1 a\u00F1o
-gb.duration.years = {0} a\u00F1os
-gb.authorizationControl = Control de autorizaciones
-gb.allowAuthenticatedDescription = Permitir acceso a todos los usuarios registrados
-gb.allowNamedDescription = Permitir acceso a usuarios inscritos \u00F3 equipos
-gb.markdownFailure = \u00A1Fallo al analizar el contenido Markdown!
-gb.clearCache = Limpiar cache
-gb.projects = Proyectos
-gb.project = Proyecto
-gb.allProjects = Todos los proyectos
-gb.copyToClipboard = Copiar al portapapeles
-gb.fork = Bifurcar
-gb.forks = Bifurcados
-gb.forkRepository = \u00BFBifurcar {0}?
-gb.repositoryForked = {0} se ha bifurcado
-gb.repositoryForkFailed= Bifurcaci\u00F3n fallida
-gb.personalRepositories = Repositorios personales
-gb.allowForks = Permitir bifurcados
-gb.allowForksDescription = Permitir a usuarios autorizados bifurcar este repositorio
-gb.forkedFrom = Bifurcado de
-gb.canFork = Puede bifurcar
-gb.canForkDescription = Puede bifurcar repositorios permitidos ha sus repositorios personales
-gb.myFork = Ver mi bifurcado
-gb.forksProhibited = Prohibido bifurcar
-gb.forksProhibitedWarning = Este repositorio proh\u00EDbe bifurcarse
-gb.noForks = {0} no tiene bifurcados
-gb.forkNotAuthorized = Perdona, no est\u00E1s autorizado a bifurcar {0}
-gb.forkInProgress = Bifurcar en curso
-gb.preparingFork = Preparando tu bifurcaci\u00F3n...
-gb.isFork = Es bifurcado
-gb.canCreate = Puede crear
-gb.canCreateDescription = Puede crear repositorios personales
-gb.illegalPersonalRepositoryLocation = Tu repositorio personal debe estar ubicado en \"{0}\"
-gb.verifyCommitter = Consignador acreditado
-gb.verifyCommitterDescription = Require que la acreditaci\u00F3n del consignador coincida con la de la cuenta del usuario en Gitblt
-gb.verifyCommitterNote = es obligatorio "--no-ff" al empujar para que el consignador se acredite
-gb.repositoryPermissions = Permisos del repositorio
-gb.userPermissions = Permisos de usuarios
-gb.teamPermissions = Permisos de equipos
-gb.add = A\u00F1adir
-gb.noPermission = BORRAR ESTE PERMISO
-gb.excludePermission = {0} (excluir)
-gb.viewPermission = {0} (ver)
-gb.clonePermission = {0} (clonar)
-gb.pushPermission = {0} (empujar)
-gb.createPermission = {0} (empujar, ref creaci\u00F3n)
-gb.deletePermission = {0} (empujar, ref creaci\u00F3n+borrado)
-gb.rewindPermission = {0} (empujar, ref creaci\u00F3n+borrado+supresi\u00F3n)
-gb.permission = Permisos
-gb.regexPermission = Estos permisos se ajustan desde la expresi\u00F3n regulare \"{0}\"
-gb.accessDenied = Acceso denegado
-gb.busyCollectingGarbage = Perd\u00F3n, Gitblit est\u00E1 ocupado quitando basura de {0}
-gb.gcPeriod = Periodo para GC
-gb.gcPeriodDescription = Duraci\u00F3n entre periodos de limpieza
-gb.gcThreshold = L\u00EDmites para GC
-gb.gcThresholdDescription = Tama\u00F1o m\u00EDnimo total de objetos sueltos para activar la recolecci\u00F3n inmediata de basura
-gb.ownerPermission = Propietario del repositorio
-gb.administrator = Admin
-gb.administratorPermission = Administrador de Gitblit
-gb.team = Equipo
-gb.teamPermission = Permisos ajustados para \"{0}\" mienbros de equipo
-gb.missing = \u00A1Omitido!
-gb.missingPermission = \u00A1Falta el repositorio de este permiso!
-gb.mutable = Alterables
-gb.specified = Espec\u00EDficos
-gb.effective = Efectivos
-gb.organizationalUnit = Unidad de organizaci\u00F3n
-gb.organization = Organizaci\u00F3n
-gb.locality = Localidad
-gb.stateProvince = Estado o provincia
-gb.countryCode = C\u00F3digo postal
-gb.properties = Propiedades
-gb.issued = Publicado
-gb.expires = Expira
-gb.expired = Expirado
-gb.expiring = Concluido
-gb.revoked = Revocado
-gb.serialNumber = N\u00FAmero de serie
-gb.certificates = Certificados
-gb.newCertificate = Nuevo certificado
-gb.revokeCertificate = Revocar certificado
-gb.sendEmail = Enviar correo
-gb.passwordHint = Recordatorio de contrase\u00F1a
-gb.ok = ok
-gb.invalidExpirationDate = \u00A1La fecha de expiraci\u00F3n no es v\u00E1lida!
-gb.passwordHintRequired = \u00A1Se requiere una pista para la contrase\u00F1a!
-gb.viewCertificate = Ver certificado
-gb.subject = Asunto
-gb.issuer = Emisor
-gb.validFrom = V\u00E1lido desde
-gb.validUntil = V\u00E1lido hasta
-gb.publicKey = Clave p\u00FAblica
-gb.signatureAlgorithm = Algoritmo de firma
-gb.sha1FingerPrint = Huella digital SHA-1
-gb.md5FingerPrint = Huella digital MD5
-gb.reason = Motivo
-gb.revokeCertificateReason = Por favor, selecciona un motivo por el que revocas el certificado
-gb.unspecified = Sin especificar
-gb.keyCompromise = Clave de compromiso
-gb.caCompromise = Compromiso CA
-gb.affiliationChanged = Afiliaci\u00F3n cambiada
-gb.superseded = Sustituida
-gb.cessationOfOperation = Cese de operaci\u00F3n 
-gb.privilegeWithdrawn = Privilegios retirados
-gb.time.inMinutes = en {0} mints
-gb.time.inHours = en {0} horas
-gb.time.inDays = en {0} d\u00EDas
-gb.hostname = Nombre de host
-gb.hostnameRequired = Por favor, introduzca un nombre de host
-gb.newSSLCertificate = Nuevo certificado SSL del servidor
-gb.newCertificateDefaults = Nuevo certificado predeterminado
-gb.duration = Duraci\u00F3n 
-gb.certificateRevoked = El cretificado {0,n\u00FAmero,0} ha sido revocado
-gb.clientCertificateGenerated = Nuevo certificado de cliente generado correctamente para {0}
-gb.sslCertificateGenerated = Nuevo certificado de SSL generado correctamente para {0}
-gb.newClientCertificateMessage = AVISO:\nLa 'contrase\u00F1a' no es la contrase\u00F1a del usuario, es la contrase\u00F1a para proteger el almac\u00E9n de claves del usuario. Esta contrase\u00F1a no se guarda por lo que tambi\u00E9n debe introducirse una "pista" que ser\u00E1 incluida en las instrucciones LEEME del usuario.
-gb.certificate = Certificado
-gb.emailCertificateBundle = Correo del cliente para el paquete del certificado
-gb.pleaseGenerateClientCertificate = Por favor, genera un certificado de cliente para {0}
-gb.clientCertificateBundleSent = Paquete de certificado de cliente {0} enviado
-gb.enterKeystorePassword =  Por favor, introduzca la contrase\u00F1a del almac\u00E9n de claves de Gitblit
-gb.warning = Advertencia
-gb.jceWarning = Tu entorno de trabajo JAVA no contiene los archivos \"JCE Unlimited Strength Jurisdiction Policy\".\nEsto limita la longitud de la contrase\u00F1a que puedes usuar para cifrar el almac\u00E9n de claves a 7 caracteres.\nEstos archivos opcionales puedes descargarlos desde Oracle.\n\n\u00BFQuieres continuar y generar la infraestructura de certificados de todos modos?\n\nSi respondes No tu navegador te dirigir\u00E1 a la p\u00E1gina de descarga de Oracle para que pueda descargar dichos archivos.
-gb.maxActivityCommits = Actividad m\u00E1xima de consignas
-gb.maxActivityCommitsDescription = N\u00FAmero m\u00E1ximo de consignas a incluir en la p\u00E1gina de actividad
-gb.noMaximum = Sin m\u00E1ximos
-gb.attributes = Atributos
-gb.serveCertificate = Servidor https con este certificado
-gb.sslCertificateGeneratedRestart = Certificado SSL generado correctamente para  {0}.\nDebes reiniciar Gitblit para usar el nuevo certificado.\n\nSi lo has iniciado con la opci\u00F3n  '--alias' deber\u00E1s ajustar dicha opci\u00F3n a ''--alias {0}''.
-gb.validity = Vigencia
-gb.siteName = Nombre del sitio
-gb.siteNameDescription = Nombre corto y descriptivo de tu servidor 
-gb.excludeFromActivity = Excluir de la p\u00E1gina de actividad
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/GitBlitWebApp_ja.properties b/src/com/gitblit/wicket/GitBlitWebApp_ja.properties
deleted file mode 100755
index 086df7b..0000000
--- a/src/com/gitblit/wicket/GitBlitWebApp_ja.properties
+++ /dev/null
@@ -1,316 +0,0 @@
-gb.repository = \u30ea\u30dd\u30b8\u30c8\u30ea
-gb.owner = \u6240\u6709\u8005
-gb.description = \u8aac\u660e
-gb.lastChange = \u6700\u5f8c\u306e\u5909\u66f4
-gb.refs = refs
-gb.tag = \u30bf\u30b0
-gb.tags = \u30bf\u30b0
-gb.author = \u4f5c\u8005
-gb.committer = \u30b3\u30df\u30c3\u30bf\u30fc
-gb.commit = \u30b3\u30df\u30c3\u30c8
-gb.tree = tree
-gb.parent = \u89aa
-gb.url = URL
-gb.history = \u5c65\u6b74
-gb.raw = raw
-gb.object = object
-gb.ticketId = \u30c1\u30b1\u30c3\u30c8ID
-gb.ticketAssigned = \u5272\u308a\u5f53\u3066\u6e08\u307f
-gb.ticketOpenDate = \u30aa\u30fc\u30d7\u30f3\u65e5
-gb.ticketState = \u72b6\u614b
-gb.ticketComments = \u30b3\u30e1\u30f3\u30c8
-gb.view = \u898b\u308b
-gb.local = \u30ed\u30fc\u30ab\u30eb
-gb.remote = \u30ea\u30e2\u30fc\u30c8
-gb.branches = \u30d6\u30e9\u30f3\u30c1
-gb.patch = \u30d1\u30c3\u30c1
-gb.diff = diff
-gb.log = \u30ed\u30b0
-gb.moreLogs = more commits...
-gb.allTags = all tags...
-gb.allBranches = all branches...
-gb.summary = \u6982\u8981
-gb.ticket = \u30c1\u30b1\u30c3\u30c8
-gb.newRepository = \u30ea\u30dd\u30b8\u30c8\u30ea\u4f5c\u6210
-gb.newUser = \u30e6\u30fc\u30b6\u30fc\u4f5c\u6210
-gb.commitdiff = commitdiff
-gb.tickets = \u30c1\u30b1\u30c3\u30c8
-gb.pageFirst = first
-gb.pagePrevious = prev
-gb.pageNext = next
-gb.head = HEAD
-gb.blame = \u6ce8\u91c8\u5c65\u6b74
-gb.login = \u30ed\u30b0\u30a4\u30f3
-gb.logout = \u30ed\u30b0\u30a2\u30a6\u30c8
-gb.username = \u30e6\u30fc\u30b6\u30fc\u540d
-gb.password = \u30d1\u30b9\u30ef\u30fc\u30c9
-gb.tagger = tagger
-gb.moreHistory = more history...
-gb.difftocurrent = diff to current
-gb.search = \u691c\u7d22
-gb.searchForAuthor = Search for commits authored by
-gb.searchForCommitter = Search for commits committed by
-gb.addition = \u8ffd\u52a0
-gb.modification = \u4fee\u6b63
-gb.deletion = \u524a\u9664
-gb.rename = \u30ea\u30cd\u30fc\u30e0
-gb.metrics = \u6307\u6a19
-gb.stats = \u7d71\u8a08
-gb.markdown = markdown
-gb.changedFiles = \u5909\u66f4\u3055\u308c\u305f\u30d5\u30a1\u30a4\u30eb 
-gb.filesAdded = {0} \u30d5\u30a1\u30a4\u30eb\u304c\u8ffd\u52a0
-gb.filesModified = {0} \u30d5\u30a1\u30a4\u30eb\u304c\u5909\u66f4
-gb.filesDeleted = {0} \u30d5\u30a1\u30a4\u30eb\u304c\u524a\u9664
-gb.filesCopied = {0} \u30d5\u30a1\u30a4\u30eb\u304c\u30b3\u30d4\u30fc
-gb.filesRenamed = {0} \u30d5\u30a1\u30a4\u30eb\u304c\u30ea\u30cd\u30fc\u30e0
-gb.missingUsername = Missing Username
-gb.edit = \u7de8\u96c6
-gb.searchTypeTooltip = Select Search Type
-gb.searchTooltip = Search {0}
-gb.delete = \u524a\u9664
-gb.docs = docs
-gb.accessRestriction = \u30a2\u30af\u30bb\u30b9\u5236\u9650
-gb.name = \u540d\u524d
-gb.enableTickets = \u30c1\u30b1\u30c3\u30c8\u3092\u6709\u52b9\u5316
-gb.enableDocs = \u30c9\u30ad\u30e5\u30e1\u30f3\u30c8\u3092\u6709\u52b9\u5316
-gb.save = \u4fdd\u5b58
-gb.showRemoteBranches = \u30ea\u30e2\u30fc\u30c8\u30d6\u30e9\u30f3\u30c1\u3092\u8868\u793a
-gb.editUsers = \u30e6\u30fc\u30b6\u30fc\u7de8\u96c6
-gb.confirmPassword = \u30d1\u30b9\u30ef\u30fc\u30c9(\u78ba\u8a8d)
-gb.restrictedRepositories = \u5236\u9650\u30ea\u30dd\u30b8\u30c8\u30ea
-gb.canAdmin = \u7ba1\u7406\u8005
-gb.notRestricted = \u533f\u540d view, clone, & push
-gb.pushRestricted = \u8a8d\u8a3c push
-gb.cloneRestricted = \u8a8d\u8a3c clone & push
-gb.viewRestricted = \u8a8d\u8a3c view, clone, & push
-gb.useTicketsDescription = \u5206\u6563\u30a4\u30b7\u30e5\u30fc\u7ba1\u7406\u30b7\u30b9\u30c6\u30e0 Ticgit \u3092\u5229\u7528\u3059\u308b
-gb.useDocsDescription = \u30ea\u30dd\u30b8\u30c8\u30ea\u5185\u306e Markdown \u30c9\u30ad\u30e5\u30e1\u30f3\u30c8\u3092\u5217\u6319\u3059\u308b
-gb.showRemoteBranchesDescription = \u30ea\u30e2\u30fc\u30c8\u30d6\u30e9\u30f3\u30c1\u3092\u8868\u793a\u3059\u308b
-gb.canAdminDescription = Gitblit\u30b5\u30fc\u30d0\u30fc\u306e\u7ba1\u7406\u8005
-gb.permittedUsers = \u8a31\u53ef\u3055\u308c\u305f\u30e6\u30fc\u30b6\u30fc
-gb.isFrozen = \u51cd\u7d50
-gb.isFrozenDescription = push\u64cd\u4f5c\u3092\u62d2\u5426\u3059\u308b
-gb.zip = zip
-gb.showReadme = readme\u8868\u793a
-gb.showReadmeDescription = \"readme\" Markdown\u30d5\u30a1\u30a4\u30eb\u3092\u6982\u8981\u30da\u30fc\u30b8\u306b\u8868\u793a\u3059\u308b
-gb.nameDescription = \u30ea\u30dd\u30b8\u30c8\u30ea\u3092\u30b0\u30eb\u30fc\u30d7\u5316\u3059\u308b\u306b\u306f '/' \u3092\u4f7f\u3046\u3002 e.g. libraries/mycoollib.git
-gb.ownerDescription = \u6240\u6709\u8005\u306f\u30ea\u30dd\u30b8\u30c8\u30ea\u306e\u8a2d\u5b9a\u3092\u5909\u66f4\u3067\u304d\u308b
-gb.blob = blob
-gb.commitActivityTrend = commit activity trend
-gb.commitActivityDOW = commit activity by day of week
-gb.commitActivityAuthors = primary authors by commit activity
-gb.feed = \u30d5\u30a3\u30fc\u30c9
-gb.cancel = \u30ad\u30e3\u30f3\u30bb\u30eb
-gb.changePassword = \u30d1\u30b9\u30ef\u30fc\u30c9\u5909\u66f4
-gb.isFederated = is federated
-gb.federateThis = federate this repository
-gb.federateOrigin = federate the origin
-gb.excludeFromFederation = \u30d5\u30a7\u30c7\u30ec\u30fc\u30b7\u30e7\u30f3\u304b\u3089\u9664\u5916\u3059\u308b
-gb.excludeFromFederationDescription = block federated Gitblit instances from pulling this account
-gb.tokens = federation tokens
-gb.tokenAllDescription = all repositories, users, & settings
-gb.tokenUnrDescription = all repositories & users
-gb.tokenJurDescription = all repositories
-gb.federatedRepositoryDefinitions = repository definitions
-gb.federatedUserDefinitions = user definitions
-gb.federatedSettingDefinitions = setting definitions
-gb.proposals = federation proposals
-gb.received = received
-gb.type = type
-gb.token = token
-gb.repositories = \u30ea\u30dd\u30b8\u30c8\u30ea
-gb.proposal = proposal
-gb.frequency = frequency
-gb.folder = \u30d5\u30a9\u30eb\u30c0\u30fc
-gb.lastPull = last pull
-gb.nextPull = next pull
-gb.inclusions = inclusions
-gb.exclusions = exclusions
-gb.registration = registration
-gb.registrations = federation registrations
-gb.sendProposal = propose
-gb.status = status
-gb.origin = origin
-gb.headRef = \u30c7\u30d5\u30a9\u30eb\u30c8\u30d6\u30e9\u30f3\u30c1 (HEAD) 
-gb.headRefDescription = HEAD \u306e\u30ea\u30f3\u30af\u5148 ref \u3092\u5909\u66f4\u3059\u308b e.g. refs/heads/master
-gb.federationStrategy = \u30d5\u30a7\u30c7\u30ec\u30fc\u30b7\u30e7\u30f3\u6226\u7565
-gb.federationRegistration = federation registration
-gb.federationResults = federation pull results
-gb.federationSets = federation sets
-gb.message = \u30e1\u30c3\u30bb\u30fc\u30b8
-gb.myUrlDescription = the publicly accessible url for your Gitblit instance
-gb.destinationUrl = send to
-gb.destinationUrlDescription = the url of the Gitblit instance to send your proposal
-gb.users = \u30e6\u30fc\u30b6\u30fc
-gb.federation = \u30d5\u30a7\u30c7\u30ec\u30fc\u30b7\u30e7\u30f3
-gb.error = \u30a8\u30e9\u30fc
-gb.refresh = \u66f4\u65b0
-gb.browse = \u95b2\u89a7
-gb.clone = clone
-gb.filter = \u30d5\u30a3\u30eb\u30bf\u30fc
-gb.create = \u4f5c\u6210
-gb.servers = \u30b5\u30fc\u30d0\u30fc
-gb.recent = recent
-gb.available = available
-gb.selected = selected
-gb.size = \u30b5\u30a4\u30ba
-gb.downloading = downloading
-gb.loading = loading
-gb.starting = starting
-gb.general = \u4e00\u822c
-gb.settings = \u8a2d\u5b9a
-gb.manage = \u7ba1\u7406
-gb.lastLogin = last login
-gb.skipSizeCalculation = \u30b5\u30a4\u30ba\u8a08\u7b97\u3092\u30b9\u30ad\u30c3\u30d7
-gb.skipSizeCalculationDescription = \u30ea\u30dd\u30b8\u30c8\u30ea\u306e\u30b5\u30a4\u30ba\u3092\u8a08\u7b97\u3057\u306a\u3044 (\u30da\u30fc\u30b8\u306e\u30ed\u30fc\u30c9\u6642\u9593\u3092\u524a\u6e1b)
-gb.skipSummaryMetrics = \u6982\u8981\u3067\u306e\u6307\u6a19\u3092\u30b9\u30ad\u30c3\u30d7
-gb.skipSummaryMetricsDescription = \u6982\u8981\u30da\u30fc\u30b8\u3067\u6307\u6a19\u3092\u8a08\u7b97\u3057\u306a\u3044 (\u30da\u30fc\u30b8\u306e\u30ed\u30fc\u30c9\u6642\u9593\u3092\u524a\u6e1b)
-gb.accessLevel = \u30a2\u30af\u30bb\u30b9\u30ec\u30d9\u30eb 
-gb.default = \u30c7\u30d5\u30a9\u30eb\u30c8
-gb.setDefault = \u30c7\u30d5\u30a9\u30eb\u30c8\u306b\u8a2d\u5b9a
-gb.since = since
-gb.status = status
-gb.bootDate = boot date
-gb.servletContainer = \u30b5\u30fc\u30d6\u30ec\u30c3\u30c8\u30b3\u30f3\u30c6\u30ca
-gb.heapMaximum = \u6700\u5927\u30d2\u30fc\u30d7
-gb.heapAllocated = \u78ba\u4fdd\u6e08\u307f\u30d2\u30fc\u30d7
-gb.heapUsed = \u4f7f\u7528\u30d2\u30fc\u30d7
-gb.free = free
-gb.version = \u30d0\u30fc\u30b8\u30e7\u30f3
-gb.releaseDate = \u30ea\u30ea\u30fc\u30b9\u65e5
-gb.date = date
-gb.activity = \u6d3b\u52d5
-gb.subscribe = \u8cfc\u8aad
-gb.branch = \u30d6\u30e9\u30f3\u30c1
-gb.maxHits = \u6700\u5927\u30d2\u30c3\u30c8\u6570
-gb.recentActivity = \u6700\u8fd1\u306e\u6d3b\u52d5
-gb.recentActivityStats = \u3053\u3053{0}\u65e5\u9593 / {2}\u4eba\u306e\u4f5c\u8005\u304b\u3089 {1}\u30b3\u30df\u30c3\u30c8
-gb.recentActivityNone = \u3053\u3053{0}\u65e5\u9593 / \u306a\u3057
-gb.dailyActivity = \u6bce\u65e5\u306e\u6d3b\u52d5
-gb.activeRepositories = \u6d3b\u767a\u306a\u30ea\u30dd\u30b8\u30c8\u30ea
-gb.activeAuthors = \u6d3b\u767a\u306a\u4f5c\u8005
-gb.commits = \u30b3\u30df\u30c3\u30c8
-gb.teams = \u30c1\u30fc\u30e0
-gb.teamName = \u30c1\u30fc\u30e0\u540d
-gb.teamMembers = \u30c1\u30fc\u30e0\u30e1\u30f3\u30d0\u30fc
-gb.teamMemberships = \u30c1\u30fc\u30e0
-gb.newTeam = \u30c1\u30fc\u30e0\u4f5c\u6210
-gb.permittedTeams = \u8a31\u53ef\u3055\u308c\u305f\u30c1\u30fc\u30e0
-gb.emptyRepository = \u7a7a\u306e\u30ea\u30dd\u30b8\u30c8\u30ea
-gb.repositoryUrl = \u30ea\u30dd\u30b8\u30c8\u30ea\u306eURL
-gb.mailingLists = \u30e1\u30fc\u30ea\u30f3\u30b0\u30ea\u30b9\u30c8
-gb.preReceiveScripts = pre-receive \u30b9\u30af\u30ea\u30d7\u30c8
-gb.postReceiveScripts = post-receive \u30b9\u30af\u30ea\u30d7\u30c8
-gb.hookScripts = \u30d5\u30c3\u30af\u30b9\u30af\u30ea\u30d7\u30c8
-gb.customFields = custom fields
-gb.customFieldsDescription = custom fields available to Groovy hooks
-gb.accessPermissions = \u30a2\u30af\u30bb\u30b9\u6a29\u9650
-gb.filters = \u30d5\u30a3\u30eb\u30bf\u30fc
-gb.generalDescription = \u4e00\u822c\u7684\u306a\u8a2d\u5b9a
-gb.accessPermissionsDescription = \u30e6\u30fc\u30b6\u30fc\u3068\u30c1\u30fc\u30e0\u3067\u30a2\u30af\u30bb\u30b9\u3092\u5236\u9650\u3059\u308b
-gb.accessPermissionsForUserDescription = \u30c1\u30fc\u30e0\u3092\u8a2d\u5b9a\u3059\u308b\u3001\u7279\u5b9a\u306e\u5236\u9650\u30ea\u30dd\u30b8\u30c8\u30ea\u3078\u306e\u30a2\u30af\u30bb\u30b9\u3092\u8a31\u53ef\u3059\u308b
-gb.accessPermissionsForTeamDescription = \u30c1\u30fc\u30e0\u30e1\u30f3\u30d0\u30fc\u3092\u8a2d\u5b9a\u3059\u308b\u3001\u7279\u5b9a\u306e\u5236\u9650\u30ea\u30dd\u30b8\u30c8\u30ea\u3078\u306e\u30a2\u30af\u30bb\u30b9\u3092\u8a31\u53ef\u3059\u308b
-gb.federationRepositoryDescription = \u3053\u306e\u30ea\u30dd\u30b8\u30c8\u30ea\u3092\u4ed6\u306e Gitblit \u30b5\u30fc\u30d0\u30fc\u3068\u5171\u6709\u3059\u308b
-gb.hookScriptsDescription = \u3053\u306e Gitblit \u30b5\u30fc\u30d0\u30fc\u306b push \u3055\u308c\u305f\u6642\u306b Groovy \u30b9\u30af\u30ea\u30d7\u30c8\u3092\u5b9f\u884c\u3059\u308b
-gb.reset = \u30ea\u30bb\u30c3\u30c8
-gb.pages = \u30da\u30fc\u30b8
-gb.workingCopy = \u4f5c\u696d\u30b3\u30d4\u30fc
-gb.workingCopyWarning = \u3053\u306e\u30ea\u30dd\u30b8\u30c8\u30ea\u306b\u306f\u4f5c\u696d\u30b3\u30d4\u30fc\u304c\u3042\u308b\u305f\u3081 push \u3067\u304d\u307e\u305b\u3093
-gb.query = \u30af\u30a8\u30ea\u30fc
-gb.queryHelp = \u6a19\u6e96\u7684\u306a\u30af\u30a8\u30ea\u30fc\u66f8\u5f0f\u3092\u30b5\u30dd\u30fc\u30c8\u3057\u3066\u3044\u307e\u3059\u3002<p/><p/>\u8a73\u7d30\u306f <a target="_new" href="http://lucene.apache.org/core/old_versioned_docs/versions/3_5_0/queryparsersyntax.html">Lucene Query Parser Syntax</a> \u3092\u53c2\u7167\u3057\u3066\u4e0b\u3055\u3044\u3002
-gb.queryResults = results {0} - {1} ({2} hits)
-gb.noHits = no hits
-gb.authored = authored
-gb.committed = committed
-gb.indexedBranches = \u30a4\u30f3\u30c7\u30c3\u30af\u30b9\u3059\u308b\u30d6\u30e9\u30f3\u30c1
-gb.indexedBranchesDescription = Lucene \u3067\u30a4\u30f3\u30c7\u30c3\u30af\u30b9\u3059\u308b\u30d6\u30e9\u30f3\u30c1\u3092\u9078\u629e
-gb.noIndexedRepositoriesWarning = Lucene \u3067\u30a4\u30f3\u30c7\u30c3\u30af\u30b9\u3059\u308b\u3088\u3046\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u308b\u30ea\u30dd\u30b8\u30c8\u30ea\u304c\u3042\u308a\u307e\u305b\u3093
-gb.undefinedQueryWarning = \u30af\u30a8\u30ea\u30fc\u304c\u672a\u5b9a\u7fa9\u3067\u3059!
-gb.noSelectedRepositoriesWarning = \u3072\u3068\u3064\u4ee5\u4e0a\u306e\u30ea\u30dd\u30b8\u30c8\u30ea\u3092\u9078\u629e\u3057\u3066\u4e0b\u3055\u3044!
-gb.luceneDisabled = Lucene \u30a4\u30f3\u30c7\u30c3\u30af\u30b9\u306f\u7121\u52b9\u5316\u3055\u308c\u3066\u3044\u307e\u3059
-gb.failedtoRead = \u8aad\u307f\u8fbc\u307f\u5931\u6557
-gb.isNotValidFile = is not a valid file
-gb.failedToReadMessage = {0}\u304b\u3089\u306e\u30c7\u30d5\u30a9\u30eb\u30c8\u30e1\u30c3\u30bb\u30fc\u30b8\u306e\u8aad\u307f\u8fbc\u307f\u306b\u5931\u6557\u3057\u307e\u3057\u305f!
-gb.passwordsDoNotMatch = \u30d1\u30b9\u30ef\u30fc\u30c9\u304c\u4e00\u81f4\u3057\u307e\u305b\u3093!
-gb.passwordTooShort = \u30d1\u30b9\u30ef\u30fc\u30c9\u304c\u77ed\u3059\u304e\u307e\u3059\u3002\u6700\u4f4e\u3067{0}\u6587\u5b57\u5fc5\u8981\u3067\u3059\u3002
-gb.passwordChanged = \u30d1\u30b9\u30ef\u30fc\u30c9\u3092\u5909\u66f4\u3057\u307e\u3057\u305f\u3002
-gb.passwordChangeAborted = \u30d1\u30b9\u30ef\u30fc\u30c9\u306e\u5909\u66f4\u3092\u4e2d\u6b62\u3057\u307e\u3057\u305f\u3002
-gb.pleaseSetRepositoryName = \u30ea\u30dd\u30b8\u30c8\u30ea\u540d\u3092\u8a2d\u5b9a\u3057\u3066\u4e0b\u3055\u3044\u3002
-gb.illegalLeadingSlash = \u5148\u982d\u306e\u30eb\u30fc\u30c8\u30d5\u30a9\u30eb\u30c0\u53c2\u7167(/)\u306f\u7981\u6b62\u3067\u3059\u3002
-gb.illegalRelativeSlash = \u76f8\u5bfe\u30d5\u30a9\u30eb\u30c0\u53c2\u7167(../)\u306f\u7981\u6b62\u3067\u3059\u3002
-gb.illegalCharacterRepositoryName = \u30ea\u30dd\u30b8\u30c8\u30ea\u540d\u306b''{0}''\u4e0d\u6b63\u306a\u6587\u5b57\u304c\u542b\u307e\u308c\u3066\u3044\u307e\u3059!
-gb.selectAccessRestriction = \u30a2\u30af\u30bb\u30b9\u5236\u9650\u3092\u9078\u629e\u3057\u3066\u4e0b\u3055\u3044!
-gb.selectFederationStrategy = \u30d5\u30a7\u30c7\u30ec\u30fc\u30b7\u30e7\u30f3\u6226\u7565\u3092\u9078\u629e\u3057\u3066\u4e0b\u3055\u3044!
-gb.pleaseSetTeamName = \u30c1\u30fc\u30e0\u540d\u3092\u5165\u529b\u3057\u3066\u4e0b\u3055\u3044!
-gb.teamNameUnavailable = \u30c1\u30fc\u30e0\u540d''{0}''\u306f\u5229\u7528\u3067\u304d\u307e\u305b\u3093\u3002.
-gb.teamMustSpecifyRepository = \u6700\u4f4e\u3067\u3082\u30ea\u30dd\u30b8\u30c8\u30ea\u3092\u30c1\u30fc\u30e0\u306b\u4e00\u3064\u6307\u5b9a\u3057\u3066\u4e0b\u3055\u3044\u3002
-gb.teamCreated = \u65b0\u3057\u3044\u30c1\u30fc\u30e0''{0}''\u3092\u4f5c\u6210\u3057\u307e\u3057\u305f\u3002
-gb.pleaseSetUsername = \u30e6\u30fc\u30b6\u30fc\u540d\u3092\u5165\u529b\u3057\u3066\u4e0b\u3055\u3044!
-gb.usernameUnavailable = \u30e6\u30fc\u30b6\u30fc\u540d''{0}''\u306f\u5229\u7528\u3067\u304d\u307e\u305b\u3093\u3002
-gb.combinedMd5Rename = Gitblit\u306fcombined-md5\u30d1\u30b9\u30ef\u30fc\u30c9\u30cf\u30c3\u30b7\u30e5\u304c\u6709\u52b9\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059\u3002\u30a2\u30ab\u30a6\u30f3\u30c8\u540d\u306e\u5909\u66f4\u3067\u306f\u65b0\u3057\u3044\u30d1\u30b9\u30ef\u30fc\u30c9\u3092\u5165\u529b\u3057\u3066\u4e0b\u3055\u3044\u3002
-gb.userCreated = \u65b0\u3057\u3044\u30e6\u30fc\u30b6\u30fc''{0}''\u3092\u4f5c\u6210\u3057\u307e\u3057\u305f\u3002
-gb.couldNotFindFederationRegistration = \u30d5\u30a7\u30c7\u30ec\u30fc\u30b7\u30e7\u30f3\u767b\u9332\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093\u3067\u3057\u305f!
-gb.failedToFindGravatarProfile = Failed to find Gravatar profile for {0}
-gb.branchStats = {0} commits and {1} tags in {2}
-gb.repositoryNotSpecified = Repository not specified!
-gb.repositoryNotSpecifiedFor = Repository not specified for {0}!
-gb.canNotLoadRepository = \u30ea\u30dd\u30b8\u30c8\u30ea\u3092\u30ed\u30fc\u30c9\u3067\u304d\u307e\u305b\u3093
-gb.commitIsNull = \u30b3\u30df\u30c3\u30c8\u304c\u7a7a\u3067\u3059
-gb.unauthorizedAccessForRepository = \u30ea\u30dd\u30b8\u30c8\u30ea\u3078\u306e\u30a2\u30af\u30bb\u30b9\u6a29\u304c\u3042\u308a\u307e\u305b\u3093
-gb.failedToFindCommit = Failed to find commit \"{0}\" in {1} for {2} page!
-gb.couldNotFindFederationProposal = Could not find federation proposal!
-gb.invalidUsernameOrPassword = \u30e6\u30fc\u30b6\u30fc\u540d\u307e\u305f\u306f\u30d1\u30b9\u30ef\u30fc\u30c9\u304c\u7121\u52b9\u3067\u3059!
-gb.OneProposalToReview = There is 1 federation proposal awaiting review. 
-gb.nFederationProposalsToReview = There are {0} federation proposals awaiting review.
-gb.couldNotFindTag = {0} \u30bf\u30b0\u3092\u898b\u3064\u304b\u308a\u307e\u305b\u3093\u3067\u3057\u305f
-gb.couldNotCreateFederationProposal = Could not create federation proposal!
-gb.pleaseSetGitblitUrl = Please enter your Gitblit url!
-gb.pleaseSetDestinationUrl = Please enter a destination url for your proposal!
-gb.proposalReceived = Proposal successfully received by {0}.
-gb.noGitblitFound = Sorry, {0} could not find a Gitblit instance at {1}.
-gb.noProposals = Sorry, {0} is not accepting proposals at this time.
-gb.noFederation = Sorry, {0} is not configured to federate with any Gitblit instances.
-gb.proposalFailed = Sorry, {0} did not receive any proposal data!
-gb.proposalError = Sorry, {0} reports that an unexpected error occurred!
-gb.failedToSendProposal = Failed to send proposal!
-gb.userServiceDoesNotPermitAddUser = {0} does not permit adding a user account!
-gb.userServiceDoesNotPermitPasswordChanges = {0} does not permit password changes!
-gb.displayName = display name
-gb.emailAddress = email address
-gb.errorAdminLoginRequired = Administration requires a login
-gb.errorOnlyAdminMayCreateRepository = Only an administrator may create a repository
-gb.errorOnlyAdminOrOwnerMayEditRepository = Only an administrator or the owner may edit a repository
-gb.errorAdministrationDisabled = Administration is disabled
-gb.lastNDays = last {0} days
-gb.completeGravatarProfile = Complete profile on Gravatar.com
-gb.none = none
-gb.line = line
-gb.content = content
-gb.empty = empty
-gb.inherited = inherited
-gb.deleteRepository = Delete repository \"{0}\"?
-gb.repositoryDeleted = Repository ''{0}'' deleted.
-gb.repositoryDeleteFailed = Failed to delete repository ''{0}''!
-gb.deleteUser = Delete user \"{0}\"?
-gb.userDeleted = User ''{0}'' deleted.
-gb.userDeleteFailed = Failed to delete user ''{0}''!
-gb.time.justNow = \u305f\u3063\u305f\u4eca
-gb.time.today = \u4eca\u65e5
-gb.time.yesterday = \u6628\u65e5
-gb.time.minsAgo = {0}\u5206\u524d
-gb.time.hoursAgo = {0}\u6642\u9593\u524d
-gb.time.daysAgo = {0}\u65e5\u524d
-gb.time.weeksAgo = {0}\u9031\u524d
-gb.time.monthsAgo = {0}\u6708\u524d
-gb.time.oneYearAgo = 1\u5e74\u524d
-gb.time.yearsAgo = {0}\u5e74\u524d
-gb.duration.oneDay = 1\u65e5
-gb.duration.days = {0}\u65e5
-gb.duration.oneMonth = 1\u30f6\u6708
-gb.duration.months = {0}\u30f6\u6708
-gb.duration.oneYear = 1\u5e74
-gb.duration.years = {0}\u5e74
-gb.authorizationControl = \u6a29\u9650\u5236\u5fa1
-gb.allowAuthenticatedDescription = \u5168\u3066\u306e\u8a8d\u8a3c\u6e08\u307f\u30e6\u30fc\u30b6\u30fc\u3078\u30a2\u30af\u30bb\u30b9\u3092\u8a31\u53ef\u3059\u308b
-gb.allowNamedDescription = \u6307\u5b9a\u3057\u305f\u540d\u524d\u306e\u30e6\u30fc\u30b6\u30fc/\u30c1\u30fc\u30e0\u3078\u30a2\u30af\u30bb\u30b9\u3092\u8a31\u53ef\u3059\u308b
-gb.markdownFailure = Markdown \u306e\u30d1\u30fc\u30b9\u306b\u5931\u6557\u3057\u307e\u3057\u305f!
diff --git a/src/com/gitblit/wicket/GitBlitWebApp_ko.properties b/src/com/gitblit/wicket/GitBlitWebApp_ko.properties
deleted file mode 100644
index 18eda26..0000000
--- a/src/com/gitblit/wicket/GitBlitWebApp_ko.properties
+++ /dev/null
@@ -1,443 +0,0 @@
-gb.repository = \uC800\uC7A5\uC18C
-gb.owner = \uC18C\uC720\uC790
-gb.description = \uC124\uBA85
-gb.lastChange = \uCD5C\uADFC \uBCC0\uACBD
-gb.refs = refs
-gb.tag = \uD0DC\uADF8
-gb.tags = \uD0DC\uADF8
-gb.author = \uC791\uC131\uC790
-gb.committer = \uCEE4\uBBF8\uD130
-gb.commit = \uCEE4\uBC0B
-gb.tree = \uD2B8\uB9AC
-gb.parent = \uBD80\uBAA8
-gb.url = URL
-gb.history = \uD788\uC2A4\uD1A0\uB9AC
-gb.raw = raw
-gb.object = object
-gb.ticketId = \uD2F0\uCF13 id
-gb.ticketAssigned = \uD560\uB2F9
-gb.ticketOpenDate = \uC5F4\uB9B0 \uB0A0\uC790
-gb.ticketState = \uC0C1\uD0DC
-gb.ticketComments = \uCF54\uBA58\uD2B8
-gb.view = \uBCF4\uAE30
-gb.local = \uB85C\uCEEC
-gb.remote = \uB9AC\uBAA8\uD2B8
-gb.branches = \uBE0C\uB79C\uCE58
-gb.patch = \uD328\uCE58
-gb.diff = \uCC28\uC774
-gb.log = \uB85C\uADF8
-gb.moreLogs = \uCEE4\uBC0B \uB354 \uBCF4\uAE30...
-gb.allTags = \uBAA8\uB4E0 \uD0DC\uADF8...
-gb.allBranches = \uBAA8\uB4E0 \uBE0C\uB79C\uCE58...
-gb.summary = \uC694\uC57D
-gb.ticket = \uD2F0\uCF13
-gb.newRepository = \uC0C8 \uC800\uC7A5\uC18C
-gb.newUser = \uB0B4 \uC0AC\uC6A9\uC790
-gb.commitdiff = \uCEE4\uBC0B\uBE44\uAD50
-gb.tickets = \uD2F0\uCF13
-gb.pageFirst = \uCCAB
-gb.pagePrevious = \uC774\uC804
-gb.pageNext = \uB2E4\uC74C
-gb.head = HEAD
-gb.blame = blame
-gb.login = \uB85C\uADF8\uC778
-gb.logout = \uB85C\uADF8\uC544\uC6C3
-gb.username = \uC720\uC800\uB124\uC784
-gb.password = \uD328\uC2A4\uC6CC\uB4DC
-gb.tagger = \uD0DC\uAC70
-gb.moreHistory = \uD788\uC2A4\uD1A0\uB9AC \uB354 \uBCF4\uAE30...
-gb.difftocurrent = \uD604\uC7AC\uC640 \uBE44\uAD50
-gb.search = \uAC80\uC0C9
-gb.searchForAuthor = \uCEE4\uBC0B\uC744 \uC791\uC131\uC790\uB85C \uAC80\uC0C9
-gb.searchForCommitter = \uCEE4\uBC0B\uC744 \uCEE4\uBC0B\uD130\uB85C \uAC80\uC0C9
-gb.addition = \uCD94\uAC00
-gb.modification = \uBCC0\uACBD
-gb.deletion = \uC0AD\uC81C
-gb.rename = \uC774\uB984\uBCC0\uACBD
-gb.metrics = \uBA54\uD2B8\uB9AD
-gb.stats = \uC0C1\uD0DC
-gb.markdown = \uB9C8\uD06C\uB2E4\uC6B4
-gb.changedFiles = \uD30C\uC77C \uBCC0\uACBD\uB428 
-gb.filesAdded = {0} \uD30C\uC77C \uCD94\uAC00\uB428
-gb.filesModified = {0} \uD30C\uC77C \uBCC0\uACBD\uB428
-gb.filesDeleted = {0} \uD30C\uC77C \uC0AD\uC81C\uB428
-gb.filesCopied = {0} \uD30C\uC77C \uBCF5\uC0AC\uB428
-gb.filesRenamed = {0} \uD30C\uC77C \uC774\uB984 \uBCC0\uACBD\uB428
-gb.missingUsername = \uC720\uC800\uB124\uC784 \uB204\uB77D
-gb.edit = \uC218\uC815
-gb.searchTypeTooltip = \uAC80\uC0C9 \uD0C0\uC785 \uC120\uD0DD
-gb.searchTooltip = {0} \uAC80\uC0C9
-gb.delete = \uC0AD\uC81C
-gb.docs = \uBB38\uC11C
-gb.accessRestriction = \uC811\uC18D \uC81C\uD55C
-gb.name = \uC774\uB984
-gb.enableTickets = \uD2F0\uCF13 \uC0AC\uC6A9
-gb.enableDocs = \uBB38\uC11C \uC0AC\uC6A9
-gb.save = \uC800\uC7A5
-gb.showRemoteBranches = \uB9AC\uBAA8\uD2B8 \uBE0C\uB79C\uCE58 \uBCF4\uAE30
-gb.editUsers = \uC720\uC800 \uC218\uC815
-gb.confirmPassword = \uD328\uC2A4\uC6CC\uB4DC \uD655\uC778
-gb.restrictedRepositories = \uC81C\uD55C\uB41C \uC800\uC7A5\uC18C
-gb.canAdmin = \uAD00\uB9AC \uAC00\uB2A5
-gb.notRestricted = \uC775\uBA85 view, clone, & push
-gb.pushRestricted = \uD5C8\uC6A9\uB41C \uC720\uC800\uB9CC push
-gb.cloneRestricted = \uD5C8\uC6A9\uB41C \uC720\uC800\uB9CC clone & push
-gb.viewRestricted = \uD5C8\uC6A9\uB41C \uC720\uC800\uB9CC view, clone, & push
-gb.useTicketsDescription = Ticgit(\uBD84\uC0B0 \uD2F0\uCF13 \uC2DC\uC2A4\uD15C) \uC774\uC288 \uC0AC\uC6A9
-gb.useDocsDescription = \uC800\uC7A5\uC18C \uC788\uB294 \uB9C8\uD06C\uB2E4\uC6B4 \uBB38\uC11C \uC0AC\uC6A9
-gb.showRemoteBranchesDescription = \uB9AC\uBAA8\uD2B8 \uBE0C\uB79C\uCE58 \uBCF4\uAE30
-gb.canAdminDescription = Gitblit \uAD00\uB9AC \uAD8C\uD55C \uBD80\uC5EC
-gb.permittedUsers = \uD5C8\uC6A9\uB41C \uC0AC\uC6A9\uC790
-gb.isFrozen = \uD504\uB9AC\uC9D5\uB428
-gb.isFrozenDescription = \uD478\uC2DC \uCC28\uB2E8
-gb.zip = zip
-gb.showReadme = \uB9AC\uB4DC\uBBF8(readme) \uBCF4\uAE30
-gb.showReadmeDescription = \uC694\uC57D\uD398\uC774\uC9C0\uC5D0\uC11C \"readme\" \uB9C8\uD06C\uB2E4\uC6B4 \uD30C\uC77C \uBCF4\uAE30
-gb.nameDescription = \uC800\uC7A5\uC18C\uB97C \uADF8\uB8F9\uC73C\uB85C \uBB36\uC73C\uB824\uBA74 '/' \uB97C \uC0AC\uC6A9. \uC608) libraries/reponame.git
-gb.ownerDescription = \uC18C\uC720\uC790\uB294 \uC800\uC7A5\uC18C \uC124\uC815\uC744 \uBCC0\uACBD\uD560 \uC218 \uC788\uC74C
-gb.blob = blob
-gb.commitActivityTrend = \uCEE4\uBC0B \uD65C\uB3D9 \uD2B8\uB79C\uB4DC
-gb.commitActivityDOW = 1\uC8FC\uC77C\uC758 \uC77C\uB2E8\uC704 \uCEE4\uBC0B \uD65C\uB3D9
-gb.commitActivityAuthors = \uCEE4\uBC0B \uD65C\uB3D9\uC758 \uC8FC \uC791\uC131\uC790
-gb.feed = \uD53C\uB4DC
-gb.cancel = \uCDE8\uC18C
-gb.changePassword = \uD328\uC2A4\uC6CC\uB4DC \uBCC0\uACBD
-gb.isFederated = \uD398\uB354\uB808\uC774\uC158\uB428
-gb.federateThis = \uC774 \uC800\uC7A5\uC18C\uB97C \uD398\uB354\uB808\uC774\uC158\uD568
-gb.federateOrigin = origin \uC5D0 \uD398\uB354\uB808\uC774\uC158
-gb.excludeFromFederation = \uD398\uB354\uB808\uC774\uC158 \uC81C\uC678
-gb.excludeFromFederationDescription = \uC774 \uACC4\uC815\uC73C\uB85C \uD480\uB9C1\uB418\uB294 \uD398\uB7EC\uB808\uC774\uC158 \uB41C Gitblit \uC778\uC2A4\uD134\uC2A4 \uCC28\uB2E8
-gb.tokens = \uD398\uB354\uB808\uC774\uC158 \uD1A0\uD070
-gb.tokenAllDescription = \uBAA8\uB4E0 \uC800\uC7A5\uC18C, \uD1A0\uD070, \uC0AC\uC6A9\uC790 & \uC124\uC815
-gb.tokenUnrDescription = \uBAA8\uB4E0 \uC800\uC7A5\uC18C & \uC0AC\uC6A9\uC790
-gb.tokenJurDescription = \uBAA8\uB4E0 \uC800\uC7A5\uC18C
-gb.federatedRepositoryDefinitions = \uC800\uC7A5\uC18C \uC815\uC758
-gb.federatedUserDefinitions = \uC0AC\uC6A9\uC790 \uC815\uC758
-gb.federatedSettingDefinitions = \uC124\uC815 \uC815\uC758
-gb.proposals = \uD398\uB354\uB808\uC774\uC158 \uC81C\uC548
-gb.received = \uC218\uC2E0\uD568
-gb.type = \uD0C0\uC785
-gb.token = \uD1A0\uD070
-gb.repositories = \uC800\uC7A5\uC18C
-gb.proposal = \uC81C\uC548
-gb.frequency = \uBE48\uB3C4
-gb.folder = \uD3F4\uB354
-gb.lastPull = \uB9C8\uC9C0\uB9C9 \uD480
-gb.nextPull = \uB2E4\uC74C \uD480
-gb.inclusions = \uD3EC\uD568
-gb.exclusions = \uC81C\uC678
-gb.registration = \uB4F1\uB85D
-gb.registrations = \uD398\uB354\uB808\uC774\uC158 \uB4F1\uB85D
-gb.sendProposal = \uC81C\uC548\uD558\uAE30
-gb.status = \uC0C1\uD0DC
-gb.origin = origin
-gb.headRef = \uB514\uD3F4\uD2B8 \uBE0C\uB79C\uCE58(HEAD)
-gb.headRefDescription = \uB514\uD3F4\uD2B8 \uBE0C\uB79C\uCE58\uB97C \uC785\uB825. \uC608) refs/heads/master
-gb.federationStrategy = \uD398\uB354\uB808\uC774\uC158 \uC815\uCC45
-gb.federationRegistration = \uD398\uB354\uB808\uC774\uC158 \uB4F1\uB85D
-gb.federationResults = \uD398\uB354\uB808\uC774\uC158 \uD480 \uACB0\uACFC
-gb.federationSets = \uD398\uB354\uB808\uC774\uC158 \uC14B
-gb.message = \uBA54\uC2DC\uC9C0
-gb.myUrlDescription = \uACF5\uAC1C\uB418\uC5B4 \uC811\uC18D\uD560 \uC218 \uC788\uB294 Gitblit \uC778\uC2A4\uD134\uC2A4 url
-gb.destinationUrl = \uB85C \uBCF4\uB0C4
-gb.destinationUrlDescription = \uC81C\uC548\uC744 \uC804\uC1A1\uD560 \uB300\uC0C1 Gitblit \uC778\uC2A4\uD134\uC2A4\uC758 url
-gb.users = \uC720\uC800
-gb.federation = \uD398\uB354\uB808\uC774\uC158
-gb.error = \uC5D0\uB7EC
-gb.refresh = \uC0C8\uB85C\uACE0\uCE68
-gb.browse = \uBE0C\uB77C\uC6B0\uC988
-gb.clone = clone
-gb.filter = \uD544\uD130
-gb.create = \uC0DD\uC131
-gb.servers = \uC11C\uBC84
-gb.recent = recent
-gb.available = \uAC00\uB2A5\uD55C
-gb.selected = \uC120\uD0DD\uB41C
-gb.size = \uD06C\uAE30
-gb.downloading = \uB2E4\uC6B4\uB85C\uB4DC\uC911
-gb.loading = \uB85C\uB529\uC911
-gb.starting = \uC2DC\uC791\uC911
-gb.general = \uC77C\uBC18
-gb.settings = \uC138\uD305
-gb.manage = \uAD00\uB9AC
-gb.lastLogin = \uB9C8\uC9C0\uB9C9 \uB85C\uADF8\uC778
-gb.skipSizeCalculation = \uD06C\uAE30 \uACC4\uC0B0 \uBB34\uC2DC
-gb.skipSizeCalculationDescription = \uC800\uC7A5\uC18C \uD06C\uAE30 \uACC4\uC0B0\uD558\uC9C0 \uC54A\uC74C (\uD398\uC774\uC9C0 \uB85C\uB529 \uC2DC\uAC04 \uB2E8\uCD95\uB428)
-gb.skipSummaryMetrics = \uBA54\uD2B8\uB9AD \uC694\uC57D \uBB34\uC2DC
-gb.skipSummaryMetricsDescription = \uC694\uC57D \uD398\uC9C0\uC774\uC5D0\uC11C \uBA54\uD2B8\uB9AD \uACC4\uC0B0\uD558\uC9C0 \uC54A\uC74C (\uD398\uC774\uC9C0 \uB85C\uB529 \uC2DC\uAC04 \uB2E8\uCD95\uB428)
-gb.accessLevel = \uC811\uC18D \uB808\uBCA8
-gb.default = \uB514\uD3F4\uD2B8
-gb.setDefault = \uB514\uD3F4\uD2B8 \uC124\uC815
-gb.since = since
-gb.status = \uC0C1\uD0DC
-gb.bootDate = \uBD80\uD305 \uC77C\uC790
-gb.servletContainer = \uC11C\uBE14\uB9BF \uCEE8\uD14C\uC774\uB108
-gb.heapMaximum = \uB9E5\uC2DC\uBA48 \uD799
-gb.heapAllocated = \uD560\uB2F9\uB41C \uD799
-gb.heapUsed = \uC0AC\uC6A9\uB41C \uD799
-gb.free = \uD504\uB9AC
-gb.version = \uBC84\uC804
-gb.releaseDate = \uB9B4\uB9AC\uC988 \uB0A0\uC9DC
-gb.date = date
-gb.activity = \uC561\uD2F0\uBE44\uD2F0
-gb.subscribe = \uAD6C\uB3C5
-gb.branch = \uBE0C\uB79C\uCE58
-gb.maxHits = \uB9E5\uC2A4\uD788\uD2B8
-gb.recentActivity = \uCD5C\uADFC \uC561\uD2F0\uBE44\uD2F0
-gb.recentActivityStats = \uC9C0\uB09C {0} \uC77C / {2} \uC5D0 \uC758\uD574 {1} \uAC1C \uCEE4\uBC0B \uB428
-gb.recentActivityNone = \uC9C0\uB09C {0} \uC77C / \uC5C6\uC74C
-gb.dailyActivity = \uC77C\uC77C \uC561\uD2F0\uBE44\uD2F0
-gb.activeRepositories = \uC0AC\uC6A9\uC911\uC778 \uC800\uC7A5\uC18C
-gb.activeAuthors = \uC0AC\uC6A9\uC911\uC778 \uC791\uC131\uC790
-gb.commits = \uCEE4\uBC0B
-gb.teams = \uD300
-gb.teamName = \uD300 \uC774\uB984
-gb.teamMembers = \uD300 \uBA64\uBC84
-gb.teamMemberships = \uD300 \uB9F4\uBC84\uC27D
-gb.newTeam = \uC0C8\uB85C\uC6B4 \uD300
-gb.permittedTeams = \uD5C8\uC6A9\uB41C \uD300
-gb.emptyRepository = \uBE48 \uC800\uC7A5\uC18C
-gb.repositoryUrl = \uC800\uC7A5\uC18C url
-gb.mailingLists = \uBA54\uC77C\uB9C1 \uB9AC\uC2A4\uD2B8
-gb.preReceiveScripts = pre-receive \uC2A4\uD06C\uB9BD\uD2B8
-gb.postReceiveScripts = post-receive \uC2A4\uD06C\uB9BD\uD2B8
-gb.hookScripts = \uD6C4\uD06C \uC2A4\uD06C\uB9BD\uD2B8
-gb.customFields = \uC0AC\uC6A9\uC790 \uD544\uB4DC
-gb.customFieldsDescription = \uADF8\uB8E8\uBE44 \uD6C5\uC5D0 \uC0AC\uC6A9\uC790 \uD544\uB4DC \uC0AC\uC6A9 \uAC00\uB2A5
-gb.accessPermissions = \uC811\uC18D \uAD8C\uD55C
-gb.filters = \uD544\uD130
-gb.generalDescription = \uC77C\uBC18 \uC124\uC815
-gb.accessPermissionsDescription = \uC720\uC800\uC640 \uD300\uC73C\uB85C \uC811\uC18D\uAD8C\uD55C \uBD80\uC5EC
-gb.accessPermissionsForUserDescription = \uD300\uC744 \uC9C0\uC815\uD558\uAC70\uB098 \uC811\uC18D \uAD8C\uD55C\uC744 \uC9C0\uC815\uD560 \uC800\uC7A5\uC18C \uC120\uD0DD
-gb.accessPermissionsForTeamDescription = \uD300 \uB9F4\uBC84\uB97C \uC120\uD0DD\uD558\uACE0, \uC811\uC18D \uAD8C\uD55C\uC744 \uC9C0\uC815\uD560 \uC800\uC7A5\uC18C \uC120\uD0DD
-gb.federationRepositoryDescription = \uC774 \uC800\uC7A5\uC18C\uB97C \uB2E4\uB978 Gitblit \uC11C\uBC84\uC640 \uACF5\uC720
-gb.hookScriptsDescription = \uC774 Gitblit \uC11C\uBC84\uC5D0 \uD478\uC2DC\uB418\uBA74 \uADF8\uB8E8\uBE44(Groovy) \uC2A4\uD06C\uB9BD\uD2B8\uB97C \uC2E4\uD589
-gb.reset = \uB9AC\uC14B
-gb.pages = \uD398\uC774\uC9C0
-gb.workingCopy = \uC6CC\uD0B9 \uCE74\uD53C
-gb.workingCopyWarning = \uC774 \uC800\uC7A5\uC18C\uB294 \uC6CC\uD0B9\uCE74\uD53C\uB97C \uAC00\uC9C0\uACE0 \uC788\uACE0 \uD478\uC2DC\uB97C \uBC1B\uC744 \uC218 \uC5C6\uC74C
-gb.query = \uCFFC\uB9AC
-gb.queryHelp = \uD45C\uC900 \uCFFC\uB9AC \uBB38\uBC95\uC744 \uC9C0\uC6D0.<p/><p/>\uC790\uC138\uD55C \uAC83\uC744 \uC6D0\uD55C\uB2E4\uBA74 <a target="_new" href="http://lucene.apache.org/core/old_versioned_docs/versions/3_5_0/queryparsersyntax.html">Lucene Query Parser Syntax</a> \uC744 \uBC29\uBB38\uD574 \uC8FC\uC138\uC694.
-gb.queryResults = \uAC80\uC0C9\uACB0\uACFC {0} - {1} ({2}\uAC1C \uAC80\uC0C9\uB428)
-gb.noHits = \uAC80\uC0C9 \uACB0\uACFC \uC5C6\uC74C
-gb.authored = \uAC00 \uC791\uC131\uD568.
-gb.committed = \uCEE4\uBC0B\uB428
-gb.indexedBranches = \uC778\uB371\uC2F1\uD560 \uBE0C\uB79C\uCE58
-gb.indexedBranchesDescription = \uB8E8\uC2E0 \uC778\uB371\uC2A4\uC5D0 \uD3EC\uD568\uD560 \uBE0C\uB79C\uCE58 \uC120\uD0DD
-gb.noIndexedRepositoriesWarning = \uC800\uC7A5\uC18C\uAC00 \uB8E8\uC2E0 \uC778\uB371\uC2F1\uC5D0 \uC124\uC815\uB418\uC9C0 \uC54A\uC74C
-gb.undefinedQueryWarning = \uCFFC\uB9AC \uC9C0\uC815\uB418\uC9C0 \uC54A\uC74C!
-gb.noSelectedRepositoriesWarning = \uD558\uB098 \uB610\uB294 \uADF8 \uC774\uC0C1\uC758 \uC800\uC7A5\uC18C\uB97C \uC120\uD0DD\uD558\uC138\uC694!
-gb.luceneDisabled = \uB8E8\uC2E0 \uC778\uB371\uC2F1 \uC911\uC9C0\uB428
-gb.failedtoRead = \uC77C\uAE30 \uC2E4\uD328
-gb.isNotValidFile = \uC720\uD6A8\uD55C \uD30C\uC77C\uC774 \uC544\uB2D8
-gb.failedToReadMessage = {0}\uC5D0\uC11C \uB514\uD3F4\uD2B8 \uBA54\uC2DC\uC9C0 \uC77C\uAE30 \uC2E4\uD328!
-gb.passwordsDoNotMatch = \uD328\uC2A4\uC6CC\uB4DC\uAC00 \uC77C\uCE58\uD558\uC9C0 \uC54A\uC544\uC694!
-gb.passwordTooShort = \uD328\uC2A4\uC6CC\uB4DC\uAC00 \uB108\uBB34 \uC9E7\uC544\uC694. \uC801\uC5B4\uB3C4 {0} \uAC1C \uBB38\uC790\uC5EC\uC57C \uD569\uB2C8\uB2E4.
-gb.passwordChanged = \uD328\uC2A4\uC6CC\uB4DC\uAC00 \uBCC0\uACBD \uC131\uACF5.
-gb.passwordChangeAborted = \uD328\uC2A4\uC6CC\uB4DC \uBCC0\uACBD \uCDE8\uC18C\uB428.
-gb.pleaseSetRepositoryName = \uC800\uC7A5\uC18C \uC774\uB984\uC744 \uC785\uB825\uD558\uC138\uC694!
-gb.illegalLeadingSlash = \uC800\uC7A5\uC18C \uC774\uB984 \uB610\uB294 \uD3F4\uB354\uB294 (/) \uB85C \uC2DC\uC791\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.
-gb.illegalRelativeSlash = \uC0C1\uB300 \uACBD\uB85C \uC9C0\uC815 (../) \uC740 \uC0AC\uC6A9\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4..
-gb.illegalCharacterRepositoryName = \uBB38\uC790 ''{0}'' \uC800\uC7A5\uC18C \uC774\uB984\uC5D0 \uC0AC\uC6A9\uD560 \uC218 \uC5C6\uC5B4\uC694!
-gb.selectAccessRestriction = \uC811\uC18D \uAD8C\uD55C\uC744 \uC120\uD0DD\uD558\uC138\uC694!
-gb.selectFederationStrategy = \uD398\uB354\uB808\uC774\uC158 \uC815\uCC45\uC744 \uC120\uD0DD\uD558\uC138\uC694!
-gb.pleaseSetTeamName = \uD300\uC774\uB984\uC744 \uC785\uB825\uD558\uC138\uC694!
-gb.teamNameUnavailable = ''{0}'' \uD300\uC740 \uC0AC\uC6A9\uD560 \uC218 \uC5C6\uC5B4\uC694.
-gb.teamMustSpecifyRepository = \uD300\uC740 \uC801\uC5B4\uB3C4 \uD558\uB098\uC758 \uC800\uC7A5\uC18C\uB97C \uC9C0\uC815\uD574\uC57C \uD569\uB2C8\uB2E4.
-gb.teamCreated = \uC0C8\uB85C\uC6B4 \uD300 ''{0}'' \uC0DD\uC131 \uC644\uB8CC.
-gb.pleaseSetUsername = \uC720\uC800\uB124\uC784\uC744 \uC785\uB825\uD558\uC138\uC694!
-gb.usernameUnavailable = ''{0}'' \uC720\uC800\uB124\uC784\uC740 \uC0AC\uC6A9\uD560 \uC218 \uC5C6\uC5B4\uC694.
-gb.combinedMd5Rename = Gitblit \uC740 combined-md5 \uD574\uC2F1 \uD328\uC2A4\uC6CC\uB4DC\uB85C \uC124\uC815\uB418\uC5C8\uC2B5\uB2C8\uB2E4. \uACC4\uC815 \uC774\uB984 \uBCC0\uACBD \uC2DC \uC0C8 \uD328\uC2A4\uC6CC\uB4DC\uB97C \uC785\uB825\uD574\uC57C \uD569\uB2C8\uB2E4.
-gb.userCreated = \uC0C8\uB85C\uC6B4 \uC720\uC800 ''{0}'' \uC0DD\uC131 \uC644\uB8CC.
-gb.couldNotFindFederationRegistration = \uD398\uB354\uB808\uC774\uC158 \uB4F1\uB85D\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4!
-gb.failedToFindGravatarProfile = {0} \uC758 Gravatar \uD504\uB85C\uD30C\uC77C \uCC3E\uAE30 \uC2E4\uD328
-gb.branchStats = {2} \uC548\uC5D0 {0} \uCEE4\uBC0B {1} \uD0DC\uADF8
-gb.repositoryNotSpecified = \uC800\uC7A5\uC18C\uAC00 \uC9C0\uC815\uB418\uC9C0 \uC54A\uC74C!
-gb.repositoryNotSpecifiedFor = {0} \uB97C \uC704\uD55C \uC800\uC7A5\uC18C\uAC00 \uC9C0\uC815\uB418\uC9C0 \uC54A\uC74C!
-gb.canNotLoadRepository = \uC800\uC7A5\uC18C\uB97C \uBD88\uB7EC\uC62C \uC218 \uC5C6\uC74C
-gb.commitIsNull = \uB110 \uCEE4\uBC0B
-gb.unauthorizedAccessForRepository = \uC800\uC7A5\uC18C\uC5D0 \uC811\uADFC \uD5C8\uC6A9\uB418\uC9C0 \uC54A\uC74C
-gb.failedToFindCommit = \uCEE4\uBC0B\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC74C \"{0}\" in {1} for {2} \uD398\uC774\uC9C0!
-gb.couldNotFindFederationProposal = \uD398\uB354\uB808\uC774\uC158 \uC81C\uC548\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4!
-gb.invalidUsernameOrPassword = \uC798\uBABB\uB41C \uC720\uC800\uB124\uC784 \uB610\uB294 \uD328\uC2A4\uC6CC\uB4DC!
-gb.OneProposalToReview = \uB9AC\uBDF0\uB97C \uAE30\uB2E4\uB9AC\uACE0 \uC788\uB294 1\uAC1C\uC758 \uD398\uB354\uB808\uC774\uC158 \uC81C\uC548\uC774 \uC788\uC2B5\uB2C8\uB2E4.
-gb.nFederationProposalsToReview = \uB9AC\uBDF0\uB97C \uAE30\uB2E4\uB9AC\uACE0 \uC788\uB294 {0} \uAC1C\uC758 \uD398\uB354\uB808\uC774\uC158 \uC81C\uC548\uC774 \uC788\uC2B5\uB2C8\uB2E4.
-gb.couldNotFindTag = \uD0DC\uADF8 {0} \uB97C(\uC744) \uCC3E\uC744 \uC218 \uC5C6\uC74C
-gb.couldNotCreateFederationProposal = \uD398\uB354\uB808\uC774\uC158 \uC81C\uC548 \uC0DD\uC131 \uC2E4\uD328!
-gb.pleaseSetGitblitUrl = Gitblit url \uC744 \uC785\uB825\uD558\uC138\uC694!
-gb.pleaseSetDestinationUrl = \uB2F9\uC2E0\uC758 \uC81C\uC548\uC5D0 \uB300\uD55C \uB300\uC0C1 url \uC744 \uC785\uB825\uD558\uC138\uC694!
-gb.proposalReceived = {0} \uC758 \uC81C\uC548 \uC131\uACF5\uC801 \uC218\uC2E0
-gb.noGitblitFound = \uC8C4\uC1A1\uD569\uB2C8\uB2E4, Gitblit \uC778\uC2A4\uD134\uC2A4 {1} \uC5D0\uC11C {0} \uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.
-gb.noProposals = \uC8C4\uC1A1\uD569\uB2C8\uB2E4, \uC774\uBC88\uC5D0\uB294 {0} \uC758 \uC81C\uC548\uC744 \uC218\uC6A9\uD558\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.
-gb.noFederation = \uC8C4\uC1A1\uD569\uB2C8\uB2E4, {0} \uC5D0\uB294 \uD398\uB354\uB808\uC774\uC158 \uC124\uC815\uB41C Gitblit \uC778\uC2A4\uD134\uC2A4\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.
-gb.proposalFailed = \uC8C4\uC1A1\uD569\uB2C8\uB2E4, {0} \uC5D0\uB294 \uC81C\uC548 \uB370\uC774\uD130\uB97C \uBC1B\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.
-gb.proposalError = \uC8C4\uC1A1\uD569\uB2C8\uB2E4, {0} \uC5D0 \uB300\uD55C \uC624\uB958 \uBC1C\uC0DD \uBCF4\uACE0
-gb.failedToSendProposal = \uC81C\uC548 \uBCF4\uB0B4\uAE30 \uC2E4\uD328!
-gb.userServiceDoesNotPermitAddUser = {0} \uC0C8\uB85C\uC6B4 \uC720\uC800\uB97C \uCD94\uAC00\uD560 \uC218 \uC5C6\uC74C!
-gb.userServiceDoesNotPermitPasswordChanges = {0} \uD328\uC2A4\uC6CC\uB4DC\uB97C \uBCC0\uACBD\uD560 \uC218 \uC5C6\uC74C!
-gb.displayName = \uD45C\uC2DC\uB418\uB294 \uC774\uB984
-gb.emailAddress = \uC774\uBA54\uC77C \uC8FC\uC18C
-gb.errorAdminLoginRequired = \uAD00\uB9AC\uB97C \uC704\uD574\uC11C\uB294 \uB85C\uADF8\uC778\uC774 \uD544\uC694
-gb.errorOnlyAdminMayCreateRepository = \uAD00\uB9AC\uC790\uB9CC \uC800\uC7A5\uC18C\uB97C \uB9CC\uB4E4\uC218 \uC788\uC74C
-gb.errorOnlyAdminOrOwnerMayEditRepository = \uAD00\uB9AC\uC790\uC640 \uC18C\uC720\uC790\uB9CC \uC800\uC7A5\uC18C\uB97C \uC218\uC815\uD560 \uC218 \uC788\uC74C
-gb.errorAdministrationDisabled = \uAD00\uB9AC\uAE30\uB2A5 \uBE44\uD65C\uC131\uD654\uB428
-gb.lastNDays = {0} \uC77C\uC804
-gb.completeGravatarProfile = Gravatar.com \uC5D0 \uD504\uB85C\uD30C\uC77C \uC0DD\uC131\uB428
-gb.none = none
-gb.line = \uB77C\uC778
-gb.content = \uB0B4\uC6A9
-gb.empty = empty
-gb.inherited = \uC0C1\uC18D
-gb.deleteRepository = \"{0}\" \uC800\uC7A5\uC18C\uB97C \uC0AD\uC81C\uD560\uAE4C\uC694?
-gb.repositoryDeleted = ''{0}'' \uC800\uC7A5\uC18C \uC0AD\uC81C\uB428.
-gb.repositoryDeleteFailed = ''{0}'' \uC800\uC7A5\uC18C \uC0AD\uC81C \uC2E4\uD328!
-gb.deleteUser = \"{0}\" \uC0AC\uC6A9\uC790 \uC0AD\uC81C?
-gb.userDeleted = ''{0}'' \uC0AC\uC6A9\uC790 \uC0AD\uC81C\uB428.
-gb.userDeleteFailed = ''{0}'' \uC0AC\uC6A9\uC790 \uC0AD\uC81C \uC2E4\uD328!
-gb.time.justNow = \uC9C0\uAE08
-gb.time.today = \uC624\uB298
-gb.time.yesterday = \uC5B4\uC81C
-gb.time.minsAgo = {0} \uBD84 \uC804
-gb.time.hoursAgo = {0} \uC2DC\uAC04 \uC804
-gb.time.daysAgo = {0} \uC77C \uC804
-gb.time.weeksAgo = {0} \uC8FC \uC804
-gb.time.monthsAgo = {0} \uB2EC \uC804
-gb.time.oneYearAgo = 1 \uB144 \uC804
-gb.time.yearsAgo = {0} \uB144 \uC804
-gb.duration.oneDay = 1 \uC77C
-gb.duration.days = {0} \uC77C
-gb.duration.oneMonth = 1 \uAC1C\uC6D4
-gb.duration.months = {0} \uAC1C\uC6D4
-gb.duration.oneYear = 1 \uB144
-gb.duration.years = {0} \uB144
-gb.authorizationControl = \uC778\uC99D \uC81C\uC5B4
-gb.allowAuthenticatedDescription = \uBAA8\uB4E0 \uC778\uC99D\uB41C \uC720\uC800\uC5D0\uAC8C \uAD8C\uD55C \uBD80\uC5EC
-gb.allowNamedDescription = \uC774\uB984\uC73C\uB85C \uC720\uC800\uB098 \uD300\uC5D0\uAC8C \uAD8C\uD55C \uBD80\uC5EC
-gb.markdownFailure = \uB9C8\uD06C\uB2E4\uC6B4 \uCEE8\uD150\uD2B8 \uD30C\uC2F1 \uC624\uB958!
-gb.clearCache = \uCE90\uC2DC \uC9C0\uC6B0\uAE30
-gb.projects = \uD504\uB85C\uC81D\uD2B8\uB4E4
-gb.project = \uD504\uB85C\uC81D\uD2B8
-gb.allProjects = \uBAA8\uB4E0 \uD504\uB85C\uC81D\uD2B8
-gb.copyToClipboard = \uD074\uB9BD\uBCF4\uB4DC\uC5D0 \uBCF5\uC0AC
-gb.fork = \uD3EC\uD06C
-gb.forks = \uD3EC\uD06C
-gb.forkRepository = fork {0}?
-gb.repositoryForked = {0} \uD3EC\uD06C\uB428
-gb.repositoryForkFailed= \uD3EC\uD06C\uC2E4\uD328
-gb.personalRepositories = \uAC1C\uC778 \uC800\uC7A5\uC18C
-gb.allowForks = \uD3EC\uD06C \uD5C8\uC6A9
-gb.allowForksDescription = \uC774 \uC800\uC7A5\uC18C\uB97C \uC778\uC99D\uB41C \uC720\uC800\uC5D0\uAC70 \uD3EC\uD06C \uD5C8\uC6A9
-gb.forkedFrom = forked from
-gb.canFork = \uD3EC\uD06C \uAC00\uB2A5
-gb.canForkDescription = \uD5C8\uC6A9\uB41C \uC800\uC7A5\uC18C\uB97C \uAC1C\uC778 \uC800\uC7A5\uC18C\uC5D0 \uD3EC\uD06C\uD560 \uC218 \uC788\uC74C
-gb.myFork = \uB0B4 \uD3EC\uD06C \uBCF4\uAE30
-gb.forksProhibited = \uD3EC\uD06C \uCC28\uB2E8\uB428
-gb.forksProhibitedWarning = \uC774 \uC800\uC7A5\uC18C\uB294 \uD3EC\uD06C \uCC28\uB2E8\uB418\uC5B4 \uC788\uC74C
-gb.noForks = {0} \uB294 \uD3EC\uD06C \uC5C6\uC74C
-gb.forkNotAuthorized = \uC8C4\uC1A1\uD569\uB2C8\uB2E4. {0} \uD3EC\uD06C\uC5D0 \uC811\uC18D \uC778\uC99D\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.
-gb.forkInProgress = \uD504\uD06C \uC9C4\uD589 \uC911
-gb.preparingFork = \uD3EC\uD06C \uC900\uBE44 \uC911...
-gb.isFork = \uD3EC\uD06C\uD55C
-gb.canCreate = \uC0DD\uC131 \uAC00\uB2A5
-gb.canCreateDescription = \uAC1C\uC778 \uC800\uC7A5\uC18C\uB97C \uB9CC\uB4E4 \uC218 \uC788\uC74C
-gb.illegalPersonalRepositoryLocation = \uAC1C\uC778 \uC800\uC7A5\uC18C\uB294 \uBC18\uB4DC\uC2DC \"{0}\" \uC5D0 \uC704\uCE58\uD574\uC57C \uD569\uB2C8\uB2E4.
-gb.verifyCommitter = \uCEE4\uBBF8\uD130 \uD655\uC778
-gb.verifyCommitterDescription = \uCEE4\uBBF8\uD130 ID \uB294 Gitblit ID \uC640 \uB9E4\uCE58\uB418\uC5B4\uC57C \uD568
-gb.verifyCommitterNote = \uBAA8\uB4E0 \uBA38\uC9C0\uB294 \uCEE4\uBBF8\uD130 ID \uB97C \uC801\uC6A9\uD558\uAE30 \uC704\uD574 "--no-ff" \uC635\uC158 \uD544\uC694
-gb.repositoryPermissions = \uC800\uC7A5\uC18C \uAD8C\uD55C
-gb.userPermissions = \uC720\uC800 \uAD8C\uD55C
-gb.teamPermissions = \uD300 \uAD8C\uD55C
-gb.add = \uCD94\uAC00
-gb.noPermission = \uC774 \uAD8C\uD55C \uC0AD\uC81C
-gb.excludePermission = {0} (\uC81C\uC678)
-gb.viewPermission = {0} (\uBCF4\uAE30)
-gb.clonePermission = {0} (\uD074\uB860)
-gb.pushPermission = {0} (\uD478\uC2DC)
-gb.createPermission = {0} (\uD478\uC2DC, ref \uC0DD\uC131)
-gb.deletePermission = {0} (\uD478\uC2DC, ref \uC0DD\uC131+\uC0AD\uC81C)
-gb.rewindPermission = {0} (\uD478\uC2DC, ref \uC0DD\uC131+\uC0AD\uC81C+\uB418\uB3CC\uB9AC\uAE30)
-gb.permission = \uAD8C\uD55C
-gb.regexPermission = \uC774 \uAD8C\uD55C\uC740 \uC815\uADDC\uC2DD \"{0}\" \uB85C\uBD80\uD130 \uC124\uC815\uB428
-gb.accessDenied = \uC811\uC18D \uAC70\uBD80
-gb.busyCollectingGarbage = \uC8C4\uC1A1\uD569\uB2C8\uB2E4. Gitblit \uC740 \uAC00\uBE44\uC9C0 \uCEEC\uB809\uC158 \uC911\uC785\uB2C8\uB2E4. {0}
-gb.gcPeriod = GC \uC8FC\uAE30
-gb.gcPeriodDescription = \uAC00\uBE44\uC9C0 \uD074\uB809\uC158\uAC04\uC758 \uC2DC\uAC04 \uAC04\uACA9
-gb.gcThreshold = GC \uAE30\uC900\uC810
-gb.gcThresholdDescription = \uC870\uAE30 \uAC00\uBE44\uC9C0 \uCEEC\uB809\uC158\uC744 \uBC1C\uC0DD\uC2DC\uD0A4\uAE30 \uC704\uD55C \uC624\uBE0C\uC81D\uD2B8\uB4E4\uC758 \uCD5C\uC18C \uC804\uCCB4 \uD06C\uAE30
-gb.ownerPermission = \uC800\uC7A5\uC18C \uC624\uB108
-gb.administrator = \uAD00\uB9AC\uC790
-gb.administratorPermission = Gitblit \uAD00\uB9AC\uC790
-gb.team = \uD300
-gb.teamPermission = \"{0}\" \uD300 \uBA64\uBC84\uC5D0 \uAD8C\uD55C \uC124\uC815\uB428
-gb.missing = \uB204\uB77D!
-gb.missingPermission = \uC774 \uAD8C\uD55C\uC744 \uC704\uD55C \uC800\uC7A5\uC18C \uB204\uB77D!
-gb.mutable = \uAC00\uBCC0
-gb.specified = \uC9C0\uC815\uB41C
-gb.effective = \uD6A8\uACFC\uC801
-gb.organizationalUnit = \uC870\uC9C1
-gb.organization = \uAE30\uAD00
-gb.locality = \uC704\uCE58
-gb.stateProvince = \uB3C4 \uB610\uB294 \uC8FC
-gb.countryCode = \uAD6D\uAC00\uCF54\uB4DC
-gb.properties = \uC18D\uC131
-gb.issued = \uBC1C\uAE09\uB428
-gb.expires = \uB9CC\uB8CC
-gb.expired = \uB9CC\uB8CC\uB428
-gb.expiring = \uB9CC\uB8CC\uC911
-gb.revoked = \uD3D0\uAE30\uB428
-gb.serialNumber = \uC77C\uB828\uBC88\uD638
-gb.certificates = \uC778\uC99D\uC11C
-gb.newCertificate = \uC0C8 \uC778\uC99D\uC11C
-gb.revokeCertificate = \uC778\uC99D\uC11C \uD3D0\uAE30
-gb.sendEmail = \uBA54\uC77C \uBCF4\uB0B4\uAE30
-gb.passwordHint = \uD328\uC2A4\uC6CC\uB4DC \uD78C\uD2B8
-gb.ok = ok
-gb.invalidExpirationDate = \uB9D0\uB8CC\uC77C\uC790 \uC624\uB958!
-gb.passwordHintRequired = \uD328\uC2A4\uC6CC\uB4DC \uD78C\uD2B8 \uD544\uC218!
-gb.viewCertificate = \uC778\uC99D\uC11C \uBCF4\uAE30
-gb.subject = \uC774\uB984
-gb.issuer = \uBC1C\uAE09\uC790
-gb.validFrom = \uC720\uD6A8\uAE30\uAC04 (\uC2DC\uC791)
-gb.validUntil = \uC720\uD6A8\uAE30\uAC04 (\uB05D)
-gb.publicKey = \uACF5\uAC1C\uD0A4
-gb.signatureAlgorithm = \uC11C\uBA85 \uC54C\uACE0\uB9AC\uC998
-gb.sha1FingerPrint = SHA-1 \uC9C0\uBB38 \uC54C\uACE0\uB9AC\uC998
-gb.md5FingerPrint = MD5 \uC9C0\uBB38 \uC54C\uACE0\uB9AC\uC998
-gb.reason = \uC774\uC720
-gb.revokeCertificateReason = \uC778\uC99D\uC11C \uD574\uC9C0\uC774\uC720\uB97C \uC120\uD0DD\uD558\uC138\uC694
-gb.unspecified = \uD45C\uC2DC\uD558\uC9C0 \uC54A\uC74C
-gb.keyCompromise = \uD0A4 \uC190\uC0C1
-gb.caCompromise = CA \uC190\uC0C1
-gb.affiliationChanged = \uAD00\uACC4 \uBCC0\uACBD\uB428
-gb.superseded = \uB300\uCCB4\uB428
-gb.cessationOfOperation = \uC6B4\uC601 \uC911\uC9C0
-gb.privilegeWithdrawn = \uAD8C\uD55C \uCCA0\uD68C\uB428
-gb.time.inMinutes = {0} \uBD84
-gb.time.inHours = {0} \uC2DC\uAC04
-gb.time.inDays = {0} \uC77C
-gb.hostname = \uD638\uC2A4\uD2B8\uBA85
-gb.hostnameRequired = \uD638\uC2A4\uD2B8\uBA85\uC744 \uC785\uB825\uD558\uC138\uC694
-gb.newSSLCertificate = \uC0C8 \uC11C\uBC84 SSL \uC778\uC99D\uC11C
-gb.newCertificateDefaults = \uC0C8 \uC778\uC99D\uC11C \uAE30\uBCF8
-gb.duration = \uAE30\uAC04
-gb.certificateRevoked = \uC778\uC99D\uC11C {0,number,0} \uB294 \uD3D0\uAE30\uB418\uC5C8\uC2B5\uB2C8\uB2E4
-gb.clientCertificateGenerated = {0} \uC744(\uB97C) \uC704\uD55C \uC0C8\uB85C\uC6B4 \uD074\uB77C\uC774\uC5B8\uD2B8 \uC778\uC99D\uC11C \uC0DD\uC131 \uC131\uACF5
-gb.sslCertificateGenerated = {0} \uC744(\uB97C) \uC704\uD55C \uC0C8\uB85C\uC6B4 \uC11C\uBC84 SSL \uC778\uC99D\uC11C \uC0DD\uC131 \uC131\uACF5
-gb.newClientCertificateMessage = \uB178\uD2B8:\n'\uD328\uC2A4\uC6CC\uB4DC' \uB294 \uC720\uC800\uC758 \uD328\uC2A4\uC6CC\uB4DC\uAC00 \uC544\uB2C8\uB77C \uC720\uC800\uC758 \uD0A4\uC2A4\uD1A0\uC5B4 \uB97C \uBCF4\uD638\uD558\uAE30 \uC704\uD55C \uAC83\uC785\uB2C8\uB2E4. \uC774 \uD328\uC2A4\uC6CC\uB4DC\uB294 \uC800\uC7A5\uB418\uC9C0 \uC54A\uC73C\uBBC0\uB85C \uC0AC\uC6A9\uC790 README \uC9C0\uCE68\uC5D0 \uD3EC\uD568\uB420 '\uD78C\uD2B8' \uB97C \uBC18\uB4DC\uC2DC \uC785\uB825\uD574\uC57C \uD569\uB2C8\uB2E4.
-gb.certificate = \uC778\uC99D\uC11C
-gb.emailCertificateBundle = \uC774\uBA54\uC77C \uD074\uB77C\uC774\uC5B8\uD2B8 \uC778\uC99D\uC11C \uBC88\uB4E4
-gb.pleaseGenerateClientCertificate = {0} \uC744(\uB97C) \uC704\uD55C \uD074\uB77C\uC774\uC5B8\uD2B8 \uC778\uC99D\uC11C\uB97C \uC0DD\uC131\uD558\uC138\uC694
-gb.clientCertificateBundleSent = {0} \uC744(\uB97C) \uC704\uD55C \uD074\uB77C\uC774\uC5B8\uD2B8 \uC778\uC99D\uC11C \uBC88\uB4E4 \uBC1C\uC1A1\uB428
-gb.enterKeystorePassword = Gitblit \uD0A4\uC2A4\uD1A0\uC5B4 \uD328\uC2A4\uC6CC\uB4DC\uB97C \uC785\uB825\uD558\uC138\uC694
-gb.warning = \uACBD\uACE0
-gb.jceWarning = \uC790\uBC14 \uC2E4\uD589\uD658\uACBD\uC5D0 \"JCE Unlimited Strength Jurisdiction Policy\" \uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.\n\uC774\uAC83\uC740 \uD0A4\uC800\uC7A5\uC18C \uC554\uD638\uD654\uC5D0 \uC0AC\uC6A9\uB418\uB294 \uD328\uC2A4\uC6CC\uB4DC\uC758 \uAE38\uC774\uB294 7\uC790\uB85C \uC81C\uD55C\uD569\uB2C8\uB2E4.\n\uC774 \uC815\uCC45 \uD30C\uC77C\uC740 Oracle \uC5D0\uC11C \uC120\uD0DD\uC801\uC73C\uB85C \uB2E4\uC6B4\uB85C\uB4DC\uD574\uC57C \uD569\uB2C8\uB2E4.\n\n\uBB34\uC2DC\uD558\uACE0 \uC778\uC99D\uC11C \uC778\uD504\uB77C\uB97C \uC0DD\uC131\uD558\uC2DC\uACA0\uC2B5\uB2C8\uAE4C?\n\n\uC544\uB2C8\uC624(No) \uB77C\uACE0 \uB2F5\uD558\uBA74 \uC815\uCC45\uD30C\uC77C\uC744 \uB2E4\uC6B4\uBC1B\uC744 \uC218 \uC788\uB294 Oracle \uB2E4\uC6B4\uB85C\uB4DC \uD398\uC774\uC9C0\uB97C \uBE0C\uB77C\uC6B0\uC800\uB85C \uC548\uB0B4\uD560 \uAC83\uC785\uB2C8\uB2E4.
-gb.maxActivityCommits = \uCD5C\uB300 \uC561\uD2F0\uBE44\uD2F0 \uCEE4\uBC0B
-gb.maxActivityCommitsDescription = \uC561\uD2F0\uBE44\uD2F0 \uD398\uC774\uC9C0\uC5D0 \uD45C\uC2DC\uD560 \uCD5C\uB300 \uCEE4\uBC0B \uC218
-gb.noMaximum = \uBB34\uC81C\uD55C
-gb.attributes = \uC18D\uC131
-gb.serveCertificate = \uC774 \uC778\uC99D\uC11C\uB85C https \uC81C\uACF5
-gb.sslCertificateGeneratedRestart = {0} \uC744(\uB97C) \uC704\uD55C \uC0C8 \uC11C\uBC84 SSL \uC778\uC99D\uC11C\uB97C \uC131\uACF5\uC801\uC73C\uB85C \uC0DD\uC131\uD558\uC600\uC2B5\uB2C8\uB2E4. \n\uC0C8 \uC778\uC99D\uC11C\uB97C \uC0AC\uC6A9\uD558\uB824\uBA74 Gitblit \uC744 \uC7AC\uC2DC\uC791 \uD574\uC57C \uD569\uB2C8\uB2E4.\n\n'--alias' \uD30C\uB77C\uBBF8\uD130\uB85C \uC2E4\uD589\uD55C\uB2E4\uBA74 ''--alias {0}'' \uB85C \uC124\uC815\uD574\uC57C \uD569\uB2C8\uB2E4.
-gb.validity = \uC720\uD6A8\uC131
-gb.siteName = \uC0AC\uC774\uD2B8 \uC774\uB984
-gb.siteNameDescription = \uC11C\uBC84\uC758 \uC9E6\uC740 \uC124\uBA85\uC774 \uD3EC\uD568\uB41C \uC774\uB984
-gb.excludeFromActivity = \uC561\uD2F0\uBE44\uD2F0 \uD398\uC774\uC9C0\uC5D0\uC11C \uC81C\uC678
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/GitBlitWebApp_nl.properties b/src/com/gitblit/wicket/GitBlitWebApp_nl.properties
deleted file mode 100644
index 5471ad8..0000000
--- a/src/com/gitblit/wicket/GitBlitWebApp_nl.properties
+++ /dev/null
@@ -1,443 +0,0 @@
-gb.repository = repositorie
-gb.owner = eigenaar
-gb.description = omschrijving
-gb.lastChange = laatste wijziging
-gb.refs = refs
-gb.tag = tag
-gb.tags = tags
-gb.author = auteur
-gb.committer = committer
-gb.commit = commit
-gb.tree = tree
-gb.parent = parent
-gb.url = URL
-gb.history = historie
-gb.raw = raw
-gb.object = object
-gb.ticketId = ticket id
-gb.ticketAssigned = toegewezen
-gb.ticketOpenDate = open datum
-gb.ticketState = status
-gb.ticketComments = commentaar
-gb.view = view
-gb.local = local
-gb.remote = remote
-gb.branches = branches
-gb.patch = patch
-gb.diff = diff
-gb.log = log
-gb.moreLogs = meer commits...
-gb.allTags = alle tags...
-gb.allBranches = alle branches...
-gb.summary = samenvatting
-gb.ticket = ticket
-gb.newRepository = nieuwe repositorie
-gb.newUser = nieuwe gebruiker
-gb.commitdiff = commitdiff
-gb.tickets = tickets
-gb.pageFirst = eerste
-gb.pagePrevious = vorige
-gb.pageNext = volgende
-gb.head = HEAD
-gb.blame = blame
-gb.login = aanmelden
-gb.logout = afmelden
-gb.username = gebruikersnaam
-gb.password = wachtwoord
-gb.tagger = tagger
-gb.moreHistory = meer historie...
-gb.difftocurrent = diff naar current
-gb.search = zoeken
-gb.searchForAuthor = Zoeken naar commits authored door
-gb.searchForCommitter = Zoeken naar commits committed door
-gb.addition = additie
-gb.modification = wijziging
-gb.deletion = verwijdering
-gb.rename = hernoem
-gb.metrics = metrieken
-gb.stats = stats
-gb.markdown = markdown
-gb.changedFiles = gewijzigde bestanden
-gb.filesAdded = {0} bestanden toegevoegd
-gb.filesModified = {0} bestanden gewijzigd
-gb.filesDeleted = {0} bestanden verwijderd
-gb.filesCopied = {0} bestanden gekopieerd
-gb.filesRenamed = {0} bestanden hernoemd
-gb.missingUsername = Ontbrekende Gebruikersnaam
-gb.edit = edit
-gb.searchTypeTooltip = Selecteer Zoek Type
-gb.searchTooltip = Zoek {0}
-gb.delete = verwijder
-gb.docs = docs
-gb.accessRestriction = toegangsbeperking
-gb.name = naam
-gb.enableTickets = enable tickets
-gb.enableDocs = enable docs
-gb.save = opslaan
-gb.showRemoteBranches = toon remote branches
-gb.editUsers = wijzig gebruikers
-gb.confirmPassword = bevestig wachtwoord
-gb.restrictedRepositories = restricted repositories
-gb.canAdmin = kan beheren
-gb.notRestricted = anoniem view, clone, & push
-gb.pushRestricted = geauthenticeerde push
-gb.cloneRestricted = geauthenticeerde clone & push
-gb.viewRestricted = geauthenticeerde view, clone, & push
-gb.useTicketsDescription = readonly, gedistribueerde Ticgit issues
-gb.useDocsDescription = enumereer Markdown documentatie in repositorie
-gb.showRemoteBranchesDescription = toon remote branches
-gb.canAdminDescription = kan Gitblit server beheren
-gb.permittedUsers = toegestane gebruikers
-gb.isFrozen = is bevroren
-gb.isFrozenDescription = weiger push operaties
-gb.zip = zip
-gb.showReadme = toon readme
-gb.showReadmeDescription = toon een \"readme\" Markdown bestand in de samenvattingspagina
-gb.nameDescription = gebruik '/' voor het groeperen van repositories.  bijv. libraries/mycoollib.git
-gb.ownerDescription = de eigenaar mag repository instellingen wijzigen
-gb.blob = blob
-gb.commitActivityTrend = commit activiteit trend
-gb.commitActivityDOW = commit activiteit per dag van de week
-gb.commitActivityAuthors = primaire auteurs op basis van commit activiteit
-gb.feed = feed
-gb.cancel = afbreken
-gb.changePassword = wijzig wachtwoord
-gb.isFederated = is gefedereerd
-gb.federateThis = federeer deze repositorie
-gb.federateOrigin = federeer deze origin
-gb.excludeFromFederation = uitsluiten van federatie
-gb.excludeFromFederationDescription = sluit gefedereerde Gitblit instances uit van het pullen van dit account
-gb.tokens = federatie tokens
-gb.tokenAllDescription = alle repositories, gebruikers, & instellingen
-gb.tokenUnrDescription = alle repositories & gebruikers
-gb.tokenJurDescription = alle repositories
-gb.federatedRepositoryDefinitions = repositorie definities
-gb.federatedUserDefinitions = gebruikersdefinities
-gb.federatedSettingDefinitions = instellingendefinities
-gb.proposals = federatie voorstellen
-gb.received = ontvangen
-gb.type = type
-gb.token = token
-gb.repositories = repositories
-gb.proposal = voorstel
-gb.frequency = frequentie
-gb.folder = map
-gb.lastPull = laatste pull
-gb.nextPull = volgende pull
-gb.inclusions = inclusies
-gb.exclusions = exclusies
-gb.registration = registratie
-gb.registrations = federatie registraties
-gb.sendProposal = voorstel
-gb.status = status
-gb.origin = origin
-gb.headRef = default branch (HEAD)
-gb.headRefDescription = wijzig de ref waar HEAD naar linkt naar bijv. refs/heads/master
-gb.federationStrategy = federatie strategie
-gb.federationRegistration = federatie registratie
-gb.federationResults = federatie pull resultaten
-gb.federationSets = federatie sets
-gb.message = melding
-gb.myUrlDescription = de publiek toegankelijke url voor uw Gitblit instantie
-gb.destinationUrl = zend naar
-gb.destinationUrlDescription = de url van de Gitblit instantie voor het verzenden van uw voorstel
-gb.users = gebruikers
-gb.federation = federatie
-gb.error = fout
-gb.refresh = ververs
-gb.browse = blader
-gb.clone = clone
-gb.filter = filter
-gb.create = maak
-gb.servers = servers
-gb.recent = recent
-gb.available = beschikbaar
-gb.selected = geselecteerd
-gb.size = grootte
-gb.downloading = downloading
-gb.loading = laden
-gb.starting = starten
-gb.general = algemeen
-gb.settings = instellingen
-gb.manage = beheer
-gb.lastLogin = laatste login
-gb.skipSizeCalculation = geen berekening van de omvang
-gb.skipSizeCalculationDescription = geen berekening van de repositoriegrootte (beperkt laadtijd pagina)
-gb.skipSummaryMetrics = geen metrieken samenvatting
-gb.skipSummaryMetricsDescription = geen berekening van metrieken op de samenvattingspagina (beperkt laadtijd pagina)
-gb.accessLevel = toegangsniveau
-gb.default = standaard
-gb.setDefault = instellen als standaard
-gb.since = sinds
-gb.status = status
-gb.bootDate = boot datum
-gb.servletContainer = servlet container
-gb.heapMaximum = maximum heap
-gb.heapAllocated = toegewezen heap
-gb.heapUsed = gebruikte heap
-gb.free = beschikbaar
-gb.version = versie
-gb.releaseDate = release datum
-gb.date = datum
-gb.activity = activiteit
-gb.subscribe = aboneer
-gb.branch = branch
-gb.maxHits = max hits
-gb.recentActivity = recente activiteit
-gb.recentActivityStats = laatste {0} dagen / {1} commits door {2} auteurs
-gb.recentActivityNone = laatste {0} dagen / geen
-gb.dailyActivity = dagelijkse activiteit
-gb.activeRepositories = actieve repositories
-gb.activeAuthors = actieve auteurs
-gb.commits = commits
-gb.teams = teams
-gb.teamName = teamnaam
-gb.teamMembers = teamleden
-gb.teamMemberships = teamlidmaatschappen
-gb.newTeam = nieuw team
-gb.permittedTeams = toegestane teams
-gb.emptyRepository = lege repositorie
-gb.repositoryUrl = repositorie url
-gb.mailingLists = mailing lijsten
-gb.preReceiveScripts = pre-receive scripts
-gb.postReceiveScripts = post-receive scripts
-gb.hookScripts = hook scripts
-gb.customFields = custom velden
-gb.customFieldsDescription = custom velden beschikbaar voor Groovy hooks
-gb.accessPermissions = toegangsrechten
-gb.filters = filters
-gb.generalDescription = algemene instellingen
-gb.accessPermissionsDescription = beperk toegang voor gebruikers en teams
-gb.accessPermissionsForUserDescription = stel teamlidmaatschappen in of geef toegang tot specifieke besloten repositories
-gb.accessPermissionsForTeamDescription = stel teamlidmaatschappen in en geef toegang tot specifieke besloten repositories 
-gb.federationRepositoryDescription = deel deze repositorie met andere Gitblit servers
-gb.hookScriptsDescription = run Groovy scripts bij pushes naar deze Gitblit server
-gb.reset = reset
-gb.pages = paginas
-gb.workingCopy = werkkopie
-gb.workingCopyWarning = deze repositorie heeft een werkkopie en kan geen pushes ontvangen
-gb.query = query
-gb.queryHelp = Standaard query syntax wordt ondersteund.<p/><p/>Zie aub <a target="_new" href="http://lucene.apache.org/core/old_versioned_docs/versions/3_5_0/queryparsersyntax.html">Lucene Query Parser Syntax</a> voor informatie.
-gb.queryResults = resultaten {0} - {1} ({2} hits)
-gb.noHits = geen hits
-gb.authored = authored
-gb.committed = committed
-gb.indexedBranches = geïndexeerde branches
-gb.indexedBranchesDescription = kies de branches voor opname in uw Lucene index
-gb.noIndexedRepositoriesWarning = geen van uw repositories is geconfigureerd voor Lucene indexering
-gb.undefinedQueryWarning = query is niet gedefinieerd!
-gb.noSelectedRepositoriesWarning = kies aub één of meerdere repositories!
-gb.luceneDisabled = Lucene indexering staat uit
-gb.failedtoRead = Lezen is mislukt
-gb.isNotValidFile = is geen valide bestand
-gb.failedToReadMessage = Het lezen van de standaard boodschap van {0} is mislukt!
-gb.passwordsDoNotMatch = Wachtwoorden komen niet overeen!
-gb.passwordTooShort = Wachtwoord is te kort. Minimum lengte is {0} karakters.
-gb.passwordChanged = Wachtwoord succesvol gewijzigd.
-gb.passwordChangeAborted = Wijziging wachtwoord afgebroken.
-gb.pleaseSetRepositoryName = Vul aub een repositorie naam in!
-gb.illegalLeadingSlash = Leidende root folder referenties (/) zijn niet toegestaan.
-gb.illegalRelativeSlash = Relatieve folder referenties (../) zijn niet toegestaan.
-gb.illegalCharacterRepositoryName = Illegaal karakter ''{0}'' in repositorie naam!
-gb.selectAccessRestriction = Stel aub een toegangsbeperking in!
-gb.selectFederationStrategy = Selecteer aub een federatie strategie!
-gb.pleaseSetTeamName = Vul aub een teamnaam in!
-gb.teamNameUnavailable = Teamnaam ''{0}'' is niet beschikbaar.
-gb.teamMustSpecifyRepository = Een team moet minimaal één repositorie specificeren.
-gb.teamCreated = Nieuw team ''{0}'' successvol aangemaakt.
-gb.pleaseSetUsername = Vul aub een gebruikersnaam in!
-gb.usernameUnavailable = Gebruikersnaam ''{0}'' is niet beschikbaar.
-gb.combinedMd5Rename = Gitblit is geconfigureerd voor combined-md5 wachtwoord hashing. U moet een nieuw wachtwoord opgeven bij het hernoemen van een account.
-gb.userCreated = Nieuwe gebruiker ''{0}'' succesvol aangemaakt.
-gb.couldNotFindFederationRegistration = Kon de federatie registratie niet vinden!
-gb.failedToFindGravatarProfile = Kon het Gravatar profiel voor {0} niet vinden
-gb.branchStats = {0} commits en {1} tags in {2}
-gb.repositoryNotSpecified = Repositorie niet gespecificeerd!
-gb.repositoryNotSpecifiedFor = Repositorie niet gespecificeerd voor {0}!
-gb.canNotLoadRepository = Kan repositorie niet laden
-gb.commitIsNull = Commit is null
-gb.unauthorizedAccessForRepository = Niet toegestane toegang tot repositorie
-gb.failedToFindCommit = Het vinden van commit \"{0}\" in {1} voor {2} pagina is mislukt!
-gb.couldNotFindFederationProposal = Kon federatievoorstel niet vinden!
-gb.invalidUsernameOrPassword = Onjuiste gebruikersnaam of wachtwoord!
-gb.OneProposalToReview = Er is 1 federatie voorstel dat wacht op review. 
-gb.nFederationProposalsToReview = Er zijn {0} federatie verzoeken die wachten op review.
-gb.couldNotFindTag = Kon tag {0} niet vinden
-gb.couldNotCreateFederationProposal = Kon geen federatie voorstel maken!
-gb.pleaseSetGitblitUrl = Vul aub uw Gitblit url in!
-gb.pleaseSetDestinationUrl = Vul aub een bestemmings-url in voor uw voorstel!
-gb.proposalReceived = Voorstel correct ontvangen door {0}.
-gb.noGitblitFound = Sorry, {0} kon geen Gitblit instance vinden op {1}.
-gb.noProposals = Sorry, {0} accepteert geen voorstellen op dit moment.
-gb.noFederation = Sorry, {0} is niet geconfigureerd voor het federeren met een Gitblit instance.
-gb.proposalFailed = Sorry, {0} ontving geen voorstelgegevens!
-gb.proposalError = Sorry, {0} rapporteert dat een onverwachte fout is opgetreden!
-gb.failedToSendProposal = Voorstel verzenden is niet gelukt!
-gb.userServiceDoesNotPermitAddUser = {0} staat het toevoegen van een gebruikersaccount niet toe!
-gb.userServiceDoesNotPermitPasswordChanges = {0} staat wachtwoord wijzigingen niet toe!
-gb.displayName = display naam
-gb.emailAddress = emailadres
-gb.errorAdminLoginRequired = Aanmelden vereist voor beheerwerk
-gb.errorOnlyAdminMayCreateRepository = Alleen een beheerder kan een repositorie maken
-gb.errorOnlyAdminOrOwnerMayEditRepository = Alleen een beheerder of de eigenaar kan een repositorie wijzigen
-gb.errorAdministrationDisabled = Beheer is uitgeschakeld
-gb.lastNDays = laatste {0} dagen
-gb.completeGravatarProfile = Completeer profiel op Gravatar.com
-gb.none = geen
-gb.line = regel
-gb.content = inhoud
-gb.empty = leeg
-gb.inherited = geërfd
-gb.deleteRepository = Verwijder repositorie \"{0}\"?
-gb.repositoryDeleted = Repositorie ''{0}'' verwijderd.
-gb.repositoryDeleteFailed = Verwijdering van repositorie ''{0}'' mislukt! 
-gb.deleteUser = Verwijder gebruiker \"{0}\"?
-gb.userDeleted = Gebruiker ''{0}'' verwijderd.
-gb.userDeleteFailed = Verwijdering van gebruiker ''{0}'' mislukt!
-gb.time.justNow = net
-gb.time.today = vandaag
-gb.time.yesterday = gisteren
-gb.time.minsAgo = {0} minuten geleden
-gb.time.hoursAgo = {0} uren geleden
-gb.time.daysAgo = {0} dagen geleden
-gb.time.weeksAgo = {0} weken geleden
-gb.time.monthsAgo = {0} maanden geleden
-gb.time.oneYearAgo = 1 jaar geleden
-gb.time.yearsAgo = {0} jaren geleden
-gb.duration.oneDay = 1 dag
-gb.duration.days = {0} dagen
-gb.duration.oneMonth = 1 maand
-gb.duration.months = {0} maanden
-gb.duration.oneYear = 1 jaar
-gb.duration.years = {0} jaren
-gb.authorizationControl = authorisatiebeheer
-gb.allowAuthenticatedDescription = ken RW+ rechten toe aan alle geautoriseerde gebruikers
-gb.allowNamedDescription = ken verfijnde rechten toe aan genoemde gebruikers of teams
-gb.markdownFailure = Het parsen van Markdown content is mislukt!
-gb.clearCache = maak cache leeg
-gb.projects = projecten
-gb.project = project
-gb.allProjects = alle projecten
-gb.copyToClipboard = kopieer naar clipboard
-gb.fork = fork
-gb.forks = forks
-gb.forkRepository = fork {0}?
-gb.repositoryForked = {0} is geforked
-gb.repositoryForkFailed= fork is mislukt
-gb.personalRepositories = personlijke repositories
-gb.allowForks = sta forks toe
-gb.allowForksDescription = sta geauthoriseerde gebruikers toe om deze repositorie te forken
-gb.forkedFrom = geforked vanaf
-gb.canFork = kan geforked worden
-gb.canForkDescription = kan geauthoriseerde repositories forken naar persoonlijke repositories
-gb.myFork = toon mijn fork
-gb.forksProhibited = forks niet toegestaan
-gb.forksProhibitedWarning = deze repositorie staat forken niet toe
-gb.noForks = {0} heeft geen forks
-gb.forkNotAuthorized = sorry, u bent niet geautoriseerd voor het forken van {0}
-gb.forkInProgress = bezig met forken
-gb.preparingFork = bezig met het maken van uw fork...
-gb.isFork = is een fork
-gb.canCreate = mag maken
-gb.canCreateDescription = mag persoonlijke repositories maken
-gb.illegalPersonalRepositoryLocation = uw persoonlijke repositorie moet te vinden zijn op \"{0}\"
-gb.verifyCommitter = controleer committer
-gb.verifyCommitterDescription = vereis dat committer identiteit overeen komt met pushing Gitblt gebruikersaccount
-gb.verifyCommitterNote = alle merges vereisen "--no-ff" om committer identiteit af te dwingen
-gb.repositoryPermissions = repository rechten
-gb.userPermissions = gebruikersrechten
-gb.teamPermissions = teamrechten
-gb.add = toevoegen
-gb.noPermission = VERWIJDER DIT RECHT
-gb.excludePermission = {0} (exclude)
-gb.viewPermission = {0} (view)
-gb.clonePermission = {0} (clone)
-gb.pushPermission = {0} (push)
-gb.createPermission = {0} (push, ref creëer)
-gb.deletePermission = {0} (push, ref creëer+verwijdering)
-gb.rewindPermission = {0} (push, ref creëer+verwijdering+rewind)
-gb.permission = recht
-gb.regexPermission = dit recht is gezet vanaf de reguliere expressie \"{0}\"
-gb.accessDenied = toegang geweigerd
-gb.busyCollectingGarbage = sorry, Gitblit is bezig met opruimen in {0}
-gb.gcPeriod = opruim periode
-gb.gcPeriodDescription = tijdsduur tussen opruimacties
-gb.gcThreshold = opruim drempel
-gb.gcThresholdDescription = minimum totaalomvang van losse objecten voor het starten van opruimactie
-gb.ownerPermission = repositorie eigenaar
-gb.administrator = beheer
-gb.administratorPermission = Gitblit beheerder
-gb.team = team
-gb.teamPermission = permissie ingesteld via \"{0}\" teamlidmaatschap
-gb.missing = ontbrekend!
-gb.missingPermission = de repositorie voor deze permissie ontbreekt!
-gb.mutable = te wijzigen
-gb.specified = gespecificeerd
-gb.effective = geldig
-gb.organizationalUnit = organisatie eenheid
-gb.organization = organisatie
-gb.locality = localiteit
-gb.stateProvince = staat of provincie
-gb.countryCode = landcode
-gb.properties = eigenschappen
-gb.issued = uitgegeven
-gb.expires = verloopt op
-gb.expired = verlopen
-gb.expiring = verloopt
-gb.revoked = ingetrokken
-gb.serialNumber = serie nummer
-gb.certificates = certificaten
-gb.newCertificate = nieuwe certificaten
-gb.revokeCertificate = trek certificaat in
-gb.sendEmail = zend email
-gb.passwordHint = wachtwoord hint
-gb.ok = ok
-gb.invalidExpirationDate = ongeldige verloopdatum!
-gb.passwordHintRequired = wachtwoord hint vereist!
-gb.viewCertificate = toon certificaat
-gb.subject = onderwerp
-gb.issuer = issuer
-gb.validFrom = geldig vanaf
-gb.validUntil = geldig tot
-gb.publicKey = publieke sleutel
-gb.signatureAlgorithm = signature algoritme
-gb.sha1FingerPrint = SHA-1 Fingerprint
-gb.md5FingerPrint = MD5 Fingerprint
-gb.reason = reden
-gb.revokeCertificateReason = Kies aub een reden voor het intrekken van het certificaat
-gb.unspecified = niet gespecificeerd
-gb.keyCompromise = sleutel gecompromitteerd
-gb.caCompromise = CA gecompromitteerd
-gb.affiliationChanged = affiliatie gewijzigd
-gb.superseded = opgevolgd
-gb.cessationOfOperation = gestaakt
-gb.privilegeWithdrawn = privilege ingetrokken
-gb.time.inMinutes = in {0} minuten
-gb.time.inHours = in {0} uren
-gb.time.inDays = in {0} dagen
-gb.hostname = hostnaam
-gb.hostnameRequired = Vul aub een hostnaam in
-gb.newSSLCertificate = nieuw server SSL certificaat
-gb.newCertificateDefaults = nieuw certificaat defaults
-gb.duration = duur
-gb.certificateRevoked = Certificaat {0,number,0} is ingetrokken
-gb.clientCertificateGenerated =  Nieuw client certificaat voor {0} succesvol gegenereerd
-gb.sslCertificateGenerated = Nieuw server SSL certificaat voor {0} succesvol gegenereerd
-gb.newClientCertificateMessage = MERK OP:\nHet 'wachtwoord' is niet het wachtwoord van de gebruiker. Het is het wachtwoord voor het afschermen van de sleutelring van de gebruiker.  Dit wachtwoord wordt niet opgeslagen dus moet u ook een 'hint' invullen die zal worden opgenomen in de README instructies van de gebruiker.
-gb.certificate = certificaat
-gb.emailCertificateBundle = email client certificaat bundel
-gb.pleaseGenerateClientCertificate = Genereer aub een client certificaat voor {0}
-gb.clientCertificateBundleSent = Client certificaat bundel voor {0} verzonden
-gb.enterKeystorePassword = Vul aub het Gitblit keystore wachtwoord in
-gb.warning = waarschuwing
-gb.jceWarning = Uw Java Runtime Environment heeft geen \"JCE Unlimited Strength Jurisdiction Policy\" bestanden.\nDit zal de lengte van wachtwoorden voor het eventueel versleutelen van uw keystores beperken tot 7 karakters.\nDeze policy bestanden zijn een optionele download van Oracle.\n\nWilt u toch doorgaan en de certificaat infrastructuur genereren?\n\nNee antwoorden zal uw browser doorsturen naar de downloadpagina van Oracle zodat u de policybestanden kunt downloaden.
-gb.maxActivityCommits = maximum activiteit commits
-gb.maxActivityCommitsDescription = maximum aantal commits om bij te dragen aan de Activiteitspagina
-gb.noMaximum = geen maximum
-gb.attributes = attributen
-gb.serveCertificate = gebruik deze certificaten voor https
-gb.sslCertificateGeneratedRestart = Nieuwe SSL certificaten voor {0} succesvol gegenereerd.\nU dient Gitblit te herstarten om de nieuwe certificaten te gebruiken.\n\nAls u opstart met de '--alias' parameter moet u die wijzigen naar ''--alias {0}''.
-gb.validity = geldigheid
-gb.siteName = site naam
-gb.siteNameDescription = korte, verduidelijkende naam van deze server
-gb.excludeFromActivity = sluit uit van activiteitspagina
diff --git a/src/com/gitblit/wicket/GitBlitWebApp_pl.properties b/src/com/gitblit/wicket/GitBlitWebApp_pl.properties
deleted file mode 100644
index 82ffa63..0000000
--- a/src/com/gitblit/wicket/GitBlitWebApp_pl.properties
+++ /dev/null
@@ -1,321 +0,0 @@
-gb.repository = Repozytorium
-gb.owner = W\u0142a\u015Bciciel
-gb.description = Opis
-gb.lastChange = Ostatnia zmiana
-gb.refs = Refs
-gb.tag = Tag
-gb.tags = Tagi
-gb.author = Autor
-gb.committer = Wgrywaj\u0105cy
-gb.commit = Commit
-gb.tree = Drzewo
-gb.parent = Rodzic
-gb.url = URL
-gb.history = Historia
-gb.raw = Raw
-gb.object = Obiekt
-gb.ticketId = Id ticketu
-gb.ticketAssigned = Przydzielony
-gb.ticketOpenDate = Data otwarcia
-gb.ticketState = Status
-gb.ticketComments = Komentarze
-gb.view = Widok
-gb.local = Lokalne
-gb.remote = Zdalne
-gb.branches = Rozga\u0142\u0119zienia
-gb.patch = \u0141atki
-gb.diff = R\u00F3\u017Cnice
-gb.log = Log
-gb.moreLogs = Wi\u0119cej log\u00F3w...
-gb.allTags = Wszystkie tagi...
-gb.allBranches = Wszystkie rozga\u0142\u0119zienia...
-gb.summary = Podsumowanie
-gb.ticket = Ticket
-gb.newRepository = Nowe repozytorium
-gb.newUser = Nowy u\u017Cytkownik
-gb.commitdiff = Diff zmiany
-gb.tickets = Tickets
-gb.pageFirst = Pierwsza strona
-gb.pagePrevious = Poprzednia strona
-gb.pageNext = Nast\u0119pna strona
-gb.head = HEAD
-gb.blame = Blame
-gb.login = Zaloguj
-gb.logout = Wyloguj
-gb.username = U\u017Cytkownik
-gb.password = Has\u0142o
-gb.tagger = Taguj\u0105cy
-gb.moreHistory = Wi\u0119cej historii...
-gb.difftocurrent = Por\u00F3wnaj z obecnym
-gb.search = Szukaj
-gb.searchForAuthor = Szukaj po autorze
-gb.searchForCommitter = Szukaj po wgrywaj\u0105cym
-gb.addition = Dodane
-gb.modification = Zmodyfikowane
-gb.deletion = Usuni\u0119te
-gb.rename = Zmiana nazwy
-gb.metrics = Metryki
-gb.stats = Statystyki
-gb.markdown = Markdown
-gb.changedFiles = Zmienione pliki
-gb.filesAdded = {0} plik\u00F3w dodano
-gb.filesModified = {0} plik\u00F3w zmieniono
-gb.filesDeleted = {0} plik\u00F3w usuni\u0119to
-gb.filesCopied = {0} plik\u00F3w skopiowano
-gb.filesRenamed = {0} plik\u00F3w przemianowano
-gb.missingUsername = Brak nazwy u\u017Cytkownika
-gb.edit = Edytuj
-gb.searchTypeTooltip = Szukaj typu
-gb.searchTooltip = Szukaj {0}
-gb.delete = Usu\u0144
-gb.docs = Dokumentacja
-gb.accessRestriction = Uprawnienia dost\u0119pu
-gb.name = Nazwa
-gb.enableTickets = Uaktywnij tickety
-gb.enableDocs = Uaktywnij dokumentacj\u0119
-gb.save = Zapisz
-gb.showRemoteBranches = Poka\u017C zdalne rozga\u0142\u0119zienia
-gb.editUsers = Edytuj u\u017Cytkownika
-gb.confirmPassword = Potwierd\u017A has\u0142o
-gb.restrictedRepositories = Chronione repozytoria
-gb.canAdmin = Mo\u017Ce administrowa\u0107
-gb.notRestricted = Anonimowy podgl\u0105d, klonowanie i zapis
-gb.pushRestricted = Uwierzytelniony zapis
-gb.cloneRestricted = Uwierzytelnione klonowanie i zapis
-gb.viewRestricted = Uwierzytelniony podgl\u0105d, klonowanie i zapis
-gb.useTicketsDescription = Rozproszone zg\u0142oszenia Ticgit 
-gb.useDocsDescription = Parsuj znaczniki Markdown w repozytorium
-gb.showRemoteBranchesDescription = Poka\u017C zdalne rozga\u0142\u0119zienia
-gb.canAdminDescription = Mo\u017Ce administrowa\u0107 serwerem Gitblit
-gb.permittedUsers = Uprawnieni u\u017Cytkownicy
-gb.isFrozen = jest zamro\u017Cony
-gb.isFrozenDescription = Odrzucaj operacje zapisu
-gb.zip = zip
-gb.showReadme = Poka\u017C readme
-gb.showReadmeDescription = Poka\u017C sparsowany \"readme\" na stronie podsumowania
-gb.nameDescription = u\u017Cyj '/' do grupowania repozytori\u00F3w, np. libraries/server-lib.git
-gb.ownerDescription = W\u0142a\u015Bciciel mo\u017Ce edytowa\u0107 ustawienia repozytorium
-gb.blob = blob
-gb.commitActivityTrend = Aktywno\u015B\u0107 zmian
-gb.commitActivityDOW = Aktywno\u015B\u0107 zmian wed\u0142ug dnia tygodnia
-gb.commitActivityAuthors = G\u0142\u00F3wni aktywni autorzy
-gb.feed = feed
-gb.cancel = Anuluj
-gb.changePassword = Zmie\u0144 has\u0142o
-gb.isFederated = Jest sfederowany
-gb.federateThis = Federuj to repozytorium
-gb.federateOrigin = Federuj origin
-gb.excludeFromFederation = Wy\u0142\u0105cz z federacji
-gb.excludeFromFederationDescription = Blokuj sfederowane instancje Gitblit od pobierania tego konta
-gb.tokens = Tokeny federacji
-gb.tokenAllDescription = Wszystkie repozytoria, u\u017Cytkownicy i ustawienia 
-gb.tokenUnrDescription = Wszystkie repozytoria i u\u017Cytkownicy
-gb.tokenJurDescription = Wszystkie repozytoria
-gb.federatedRepositoryDefinitions = Definicje repozytori\u00F3w
-gb.federatedUserDefinitions = Definicje u\u017Cytkownik\u00F3w
-gb.federatedSettingDefinitions = Definicje ustawie\u0144
-gb.proposals = Propozycje federacji
-gb.received = Otrzymane
-gb.type = Typ
-gb.token = Token
-gb.repositories = Repozytoria
-gb.proposal = Propozycja
-gb.frequency = Cz\u0119stotliwo\u015B\u0107
-gb.folder = Folder
-gb.lastPull = Ostatnie pobranie
-gb.nextPull = Nast\u0119pne pobranie
-gb.inclusions = Do\u0142\u0105czenia
-gb.exclusions = Wy\u0142\u0105czenia
-gb.registration = Rejestracja
-gb.registrations = Sfederowane rejestracje
-gb.sendProposal = Zaproponuj
-gb.status = Status
-gb.origin = origin
-gb.headRef = Domy\u015Blne rozga\u0142\u0119zienie (HEAD)
-gb.headRefDescription = Zmie\u0144 ref aby wskazywa\u0142o na to co HEAD np. refs/heads/master
-gb.federationStrategy = Strategia federowania
-gb.federationRegistration = Rejestracja federowania
-gb.federationResults = Wyniki sfederowanego pobierania
-gb.federationSets = Zbiory federacji
-gb.message = Wiadomo\u015B\u0107
-gb.myUrlDescription = Zewn\u0119trznie widoczy url do tej instancji Gitblit
-gb.destinationUrl = Wy\u015Blij do
-gb.destinationUrlDescription = Url instacji Gitblit, gdzie chcesz wys\u0142a\u0107 swoj\u0105 propozycj\u0119
-gb.users = U\u017Cytkownicy
-gb.federation = Federacja
-gb.error = B\u0142\u0105d
-gb.refresh = Od\u015Bwie\u017C
-gb.browse = Przegl\u0105daj
-gb.clone = Klonuj
-gb.filter = Filtr
-gb.create = Stw\u00F3rz
-gb.servers = Serwery
-gb.recent = Ostatnie
-gb.available = Dost\u0119pne
-gb.selected = Wybrane
-gb.size = Rozmiar
-gb.downloading = Pobieranie
-gb.loading = \u0141adowanie
-gb.starting = Startowanie
-gb.general = Og\u00F3lne
-gb.settings = Ustawienia
-gb.manage = Zarz\u0105dzaj
-gb.lastLogin = Ostatni login
-gb.skipSizeCalculation = Pomi\u0144 obliczanie rozmiaru
-gb.skipSizeCalculationDescription = Nie obliczaj rozmiaru repozytorium (przyspiesza \u0142adowanie strony)
-gb.skipSummaryMetrics = Pomi\u0144 obliczanie metryk
-gb.skipSummaryMetricsDescription = Nie obliczaj metryk na stronie podsumowania (przyspiesza \u0142adowanie strony)
-gb.accessLevel = Poziom dost\u0119pu
-gb.default = Domy\u015Blny
-gb.setDefault = Ustaw domy\u015Blny
-gb.since = Od
-gb.status = Status
-gb.bootDate = Data uruchomienia
-gb.servletContainer = Kontener serwlet\u00F3w
-gb.heapMaximum = Maksymalny stos
-gb.heapAllocated = Przydzielony stos
-gb.heapUsed = U\u017Cywany stos
-gb.free = Wolne
-gb.version = Wersja
-gb.releaseDate = Data wydania
-gb.date = Data
-gb.activity = Aktywno\u015B\u0107
-gb.subscribe = Subskrybuj
-gb.branch = Rozga\u0142\u0119zienie
-gb.maxHits = Maks. ilo\u015B\u0107 dost\u0119pu
-gb.recentActivity = Ostatnia aktywno\u015B\u0107
-gb.recentActivityStats = Ostatnich {0} dni / {1} zmian przez {2} autor\u00F3w
-gb.recentActivityNone = Ostatnich {0} dni / brak
-gb.dailyActivity = Dzienna aktywno\u015B\u0107
-gb.activeRepositories = Aktywne repozytoria
-gb.activeAuthors = Aktywni u\u017Cytkownicy
-gb.commits = Zmiany
-gb.teams = Zespo\u0142y
-gb.teamName = Nazwa zespo\u0142u
-gb.teamMembers = Cz\u0142onkowie zespo\u0142u
-gb.teamMemberships = Cz\u0142onkostwo zespo\u0142u
-gb.newTeam = Nowy zesp\u00F3\u0142
-gb.permittedTeams = Uprawnione zespo\u0142y
-gb.emptyRepository = Puste repozytorium
-gb.repositoryUrl = Url repozytorium
-gb.mailingLists = Lista mailingowa
-gb.preReceiveScripts = Skrypty przed odbiorem zmian
-gb.postReceiveScripts = Skrypty po odbiorze zmian
-gb.hookScripts = Wpi\u0119te haki
-gb.customFields = Niestandardowe pola
-gb.customFieldsDescription = Niestandardowe pola dost\u0119pne dla hak\u00F3w Groovy
-gb.accessPermissions = Uprawnienia dot\u0119pu
-gb.filters = Filtry
-gb.generalDescription = Wsp\u00F3lne ustawienia
-gb.accessPermissionsDescription = Ogranicz dost\u0119p u\u017Cytkownikom i zespo\u0142om
-gb.accessPermissionsForUserDescription = Ustal cz\u0142onkostwo w zespo\u0142ach lub udziel dost\u0119p do specyficzynch repozytori\u00F3w
-gb.accessPermissionsForTeamDescription = Ustal cz\u0142onk\u00F3w zespo\u0142u i udziel dost\u0119p do specyficzynch repozytori\u00F3w
-gb.federationRepositoryDescription = Udost\u0119pnij to repozytorium innym serwerom Gitblit
-gb.hookScriptsDescription = Uruchamiaj skrypty Groovy w momencie wgrania zmian na ten serwer
-gb.reset = Resetuj
-gb.pages = Strony
-gb.workingCopy = Kopia robocza
-gb.workingCopyWarning = To repozytorium ma kopi\u0119 robocz\u0105 i nie mo\u017Ce otrzymywa\u0107 zmian
-gb.query = Szukaj
-gb.queryHelp = Standardowa sk\u0142adnia wyszukiwa\u0144 jest wspierana.<p/><p/>Na stronie <a target="_new" href="http://lucene.apache.org/core/old_versioned_docs/versions/3_5_0/queryparsersyntax.html">Lucene Query Parser Syntax</a> dost\u0119pne s\u0105 dalsze szczeg\u00F3\u0142y.
-gb.queryResults = Wyniki {0} - {1} ({2} wynik\u00F3w)
-gb.noHits = Brak wynik\u00F3w
-gb.authored = utworzy\u0142
-gb.committed = wgra\u0142
-gb.indexedBranches = Indeksowane rozga\u0142\u0119zienia
-gb.indexedBranchesDescription = Wybierz rozga\u0142\u0119zienia do w\u0142\u0105czenia do indeksu Lucene
-gb.noIndexedRepositoriesWarning = \u017Badne z rozga\u0142\u0119zie\u0144 w repozytorium nie jest dost\u0119pne dla Lucene
-gb.undefinedQueryWarning = Wyszukanie nie zdefiniowane!
-gb.noSelectedRepositoriesWarning = Wska\u017C jedno lub wi\u0119cej repozytori\u00F3w!
-gb.luceneDisabled = Indeksowanie Lucene jest wy\u0142\u0105czone.
-gb.failedtoRead = B\u0142\u0105d podczas odczytu
-gb.isNotValidFile = nie jest poprawnym plikiem.
-gb.failedToReadMessage = B\u0142\u0105d podczas pdczytu domy\u015Blnego komunikatu z {0}!
-gb.passwordsDoNotMatch = Brak zgodno\u015Bci has\u0142a!
-gb.passwordTooShort = Has\u0142o za kr\u00F3tkie. Minimalna d\u0142ugo\u015B\u0107 to {0} znak\u00F3w.
-gb.passwordChanged = Pomy\u015Blnie zmieniono has\u0142o.
-gb.passwordChangeAborted = Zmiania has\u0142a porzucona.
-gb.pleaseSetRepositoryName = Wska\u017C nazw\u0119 repozytorium!
-gb.illegalLeadingSlash = Poprzedzaj\u0105cy g\u0142\u00F3wny folder znaki (/) s\u0105 niedozwolone.
-gb.illegalRelativeSlash = Wzgl\u0119dne odwo\u0142ania do folder\u00F3w (../) s\u0105 niedozwolone.
-gb.illegalCharacterRepositoryName = Zabronionu znak ''{0}'' w nazwie repozytorium!
-gb.selectAccessRestriction = Wska\u017C ograniczenia dost\u0119pu!
-gb.selectFederationStrategy = Wska\u017C strategi\u0119 federacji!
-gb.pleaseSetTeamName = Wpisz nazw\u0119 zespo\u0142u!
-gb.teamNameUnavailable = Nazwa zespo\u0142u ''{0}'' jest niedost\u0119pna.
-gb.teamMustSpecifyRepository = Zesp\u00F3\u0142 musi posiada\u0107 conajmniej jedno repozytorium.
-gb.teamCreated = Zesp\u00F3\u0142 ''{0}'' zosta\u0142 utworzony.
-gb.pleaseSetUsername = Wpisz nazw\u0119 u\u017Cytkownika!
-gb.usernameUnavailable = Nazwa u\u017Cytkownika''{0}'' jest niedost\u0119pna.
-gb.combinedMd5Rename = Gitblit jest skonfigurowany na po\u0142\u0105czone haszowanie hase\u0142 md5. Musisz wpisa\u0107 nowe has\u0142o przy zmianie nazwy konta.
-gb.userCreated = U\u017Cytkownik ''{0}'' zosta\u0142 utworzony.
-gb.couldNotFindFederationRegistration = Nie mo\u017Cna znale\u017A\u0107 rejestracji federacji!
-gb.failedToFindGravatarProfile = B\u0142\u0105d podczas dopasowania profilu Gravatar dla {0}
-gb.branchStats = {0} zmian oraz {1} tag\u00F3w w {2}
-gb.repositoryNotSpecified = Repozytorium nie wskazane!
-gb.repositoryNotSpecifiedFor = Repozytorium nie wskazane dla {0}!
-gb.canNotLoadRepository = Nie mo\u017Cna za\u0142adowa\u0107 repozytorium
-gb.commitIsNull = Zmiana jest nullem
-gb.unauthorizedAccessForRepository = Nieuwierzytelniony dost\u0119p do repozytorium
-gb.failedToFindCommit = B\u0142\u0105d podczas wyszukania zmiany \"{0}\" w {1} dla {2} strony!
-gb.couldNotFindFederationProposal = Nie mo\u017Cna odnale\u017A\u0107 propozycji federacji!
-gb.invalidUsernameOrPassword = B\u0142\u0119dny u\u017Cytkownik lub has\u0142o!
-gb.OneProposalToReview = Jest 1 oczekuj\u0105ca propozycja federacji.
-gb.nFederationProposalsToReview = Jest {0} oczekuj\u0105cych propozycji federacji.
-gb.couldNotFindTag = Nie mo\u017Cna odnale\u017A\u0107 taga {0}
-gb.couldNotCreateFederationProposal = Nie mo\u017Cna utworzy\u0107 propozycji federacji!
-gb.pleaseSetGitblitUrl = Wpisz url serwera Gitblit!
-gb.pleaseSetDestinationUrl = Wpisz url, gdzie ma trafi\u0107 propozycja!
-gb.proposalReceived = Odebrano propozycj\u0119 od {0}.
-gb.noGitblitFound = Przepraszamy, {0} nie mo\u017Cna odnale\u017A\u0107 instancji Gitblit pod {1}.
-gb.noProposals = Przepraszamy, {0} nie akceptuje obecnie propozycji.
-gb.noFederation = Przepraszamy, {0} nie jest skonfigurowany do federacji z jakimikolwiek instancjami Gitblit.
-gb.proposalFailed = Przepraszamy, {0} nie otrzyma\u0142 \u017Cadnych danych propozycji!
-gb.proposalError = Przepraszamy, {0} informuje o nieoczekiwanym b\u0142\u0119dzie!
-gb.failedToSendProposal = B\u0142\u0105d podczas wysy\u0142ania propozycji!
-gb.userServiceDoesNotPermitAddUser = {0} nie zezwala na utworzenie nowego u\u017Cytkownika!
-gb.userServiceDoesNotPermitPasswordChanges = {0} nie zezwala na zmian\u0119 has\u0142a!
-gb.displayName = Wy\u015Bwietlana nazwa
-gb.emailAddress = Adres email
-gb.errorAdminLoginRequired = Administracja wymaga zalogowania
-gb.errorOnlyAdminMayCreateRepository = Tylko administrator mo\u017Ce utworzy\u0107 repozytorium
-gb.errorOnlyAdminOrOwnerMayEditRepository = Tylko administrator lub w\u0142a\u015Bciciel mo\u017Ce edytowa\u0107 repozytorium.
-gb.errorAdministrationDisabled = Administracja jest wy\u0142\u0105czona
-gb.lastNDays = Ostatnich {0} dni
-gb.completeGravatarProfile = Pe\u0142ny profil na Gravatar.com
-gb.none = Brak
-gb.line = Linia
-gb.content = Zawarto\u015B\u0107
-gb.empty = Pusty
-gb.inherited = Odziedziczony
-gb.deleteRepository = Usun\u0105\u0107 repozytorium \"{0}\"?
-gb.repositoryDeleted = Repozytorium ''{0}'' usuni\u0119te.
-gb.repositoryDeleteFailed = B\u0142\u0105d podczas usuwania repozytorium ''{0}''!
-gb.deleteUser = Usun\u0105\u0107 u\u017Cytkownika \"{0}\"?
-gb.userDeleted = U\u017Cytkownik ''{0}'' usuni\u0119ty.
-gb.userDeleteFailed = B\u0142\u0105d podczas usuwania u\u017Cytkownika ''{0}''!
-gb.time.justNow = Przed chwil\u0105
-gb.time.today = Dzisiaj
-gb.time.yesterday = Wczoraj
-gb.time.minsAgo = {0} minut temu
-gb.time.hoursAgo = {0} godzin temu
-gb.time.daysAgo = {0} dni temu
-gb.time.weeksAgo = {0} tygodni temu
-gb.time.monthsAgo = {0} miesi\u0119cy temu
-gb.time.oneYearAgo = Rok temu
-gb.time.yearsAgo = {0} lat temu
-gb.duration.oneDay = Dzie\u0144 temu
-gb.duration.days = {0} dni
-gb.duration.oneMonth = Miesi\u0105c
-gb.duration.months = {0} miesi\u0119cy
-gb.duration.oneYear = rok
-gb.duration.years = {0} lat
-gb.authorizationControl = kontrola autoryzacji
-gb.allowAuthenticatedDescription = udziel ograniczonego dost\u0119pu wszystkim uwierzytelnionym u\u017Cytkownikom
-gb.allowNamedDescription = udziel ograniczonego dost\u0119pu nazwanym u\u017Cytkownikom lub zespo\u0142om
-gb.markdownFailure = Nieudane parsowanie znacznik\u00F3w Markdown!
-gb.clearCache = Wyczy\u015B\u0107 cache
-gb.projects = Projekty
-gb.project = Projekt
-gb.allProjects = Wszystkie projekty
-gb.copyToClipboard = Kopiuj do schowka
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/GitBlitWebApp_pt_BR.properties b/src/com/gitblit/wicket/GitBlitWebApp_pt_BR.properties
deleted file mode 100644
index efe286c..0000000
--- a/src/com/gitblit/wicket/GitBlitWebApp_pt_BR.properties
+++ /dev/null
@@ -1,445 +0,0 @@
-gb.repository = reposit�rio
-gb.owner = propriet�rio
-gb.description = descri��o
-gb.lastChange = �ltima altera��o
-gb.refs = refs
-gb.tag = tag
-gb.tags = tags
-gb.author = autor
-gb.committer = committer
-gb.commit = commit
-gb.tree = �rvore
-gb.parent = parent
-gb.url = URL
-gb.history = hist�rico
-gb.raw = raw
-gb.object = object
-gb.ticketId = ticket id
-gb.ticketAssigned = atribu�do
-gb.ticketOpenDate = data da abertura
-gb.ticketState = estado
-gb.ticketComments = coment�rios
-gb.view = visualizar
-gb.local = local
-gb.remote = remote
-gb.branches = branches
-gb.patch = patch
-gb.diff = diff
-gb.log = log
-gb.moreLogs = mais commits...
-gb.allTags = todas as tags...
-gb.allBranches = todos os branches...
-gb.summary = resumo
-gb.ticket = ticket
-gb.newRepository = novo reposit�rio
-gb.newUser = novo usu�rio
-gb.commitdiff = commitdiff
-gb.tickets = tickets
-gb.pageFirst = primeira
-gb.pagePrevious anterior
-gb.pageNext = pr�xima
-gb.head = HEAD
-gb.blame = blame
-gb.login = login
-gb.logout = logout
-gb.username = username
-gb.password = password
-gb.tagger = tagger
-gb.moreHistory = mais hist�rico...
-gb.difftocurrent = diff para a atual
-gb.search = pesquisar
-gb.searchForAuthor = Procurar por commits cujo autor �
-gb.searchForCommitter = Procurar por commits commitados por
-gb.addition = adicionados
-gb.modification = modificados
-gb.deletion = apagados
-gb.rename = renomear
-gb.metrics = m�tricas
-gb.stats = estat�sticas
-gb.markdown = markdown
-gb.changedFiles = arquivos alterados 
-gb.filesAdded = {0} arquivos adicionados
-gb.filesModified = {0} arquivos modificados
-gb.filesDeleted = {0} arquivos deletados
-gb.filesCopied = {0} arquivos copiados
-gb.filesRenamed = {0} arquivos renomeados
-gb.missingUsername = Faltando username
-gb.edit = editar
-gb.searchTypeTooltip = Selecione o Tipo de Pesquisa
-gb.searchTooltip = Pesquisar {0}
-gb.delete = deletar
-gb.docs = docs
-gb.accessRestriction = restri��o de acesso
-gb.name = nome
-gb.enableTickets = ativar tickets
-gb.enableDocs = ativar documenta��o
-gb.save = salvar
-gb.showRemoteBranches = mostrar branches remotos
-gb.editUsers = editar usu�rios
-gb.confirmPassword = confirmar password
-gb.restrictedRepositories = reposit�rios restritos
-gb.canAdmin = pode administrar
-gb.notRestricted = visualiza��o an�nima, clone, & push
-gb.pushRestricted = push aut�nticado
-gb.cloneRestricted = clone & push aut�nticados
-gb.viewRestricted = view, clone, & push aut�nticados
-gb.useTicketsDescription = somente leitura, issues do Ticgit distribu�dos
-gb.useDocsDescription = enumerar documenta��o Markdown no reposit�rio
-gb.showRemoteBranchesDescription = mostrar branches remotos
-gb.canAdminDescription = pode administrar o server Gitblit
-gb.permittedUsers = usu�rios autorizados
-gb.isFrozen = est� congelado
-gb.isFrozenDescription = proibir fazer push
-gb.zip = zip
-gb.showReadme = mostrar readme
-gb.showReadmeDescription = mostrar um arquivo \"leia-me\" na p�gina de resumo
-gb.nameDescription = usar '/' para agrupar reposit�rios.  e.g. libraries/mycoollib.git
-gb.ownerDescription = o propriet�rio pode editar configura��es do reposit�rio
-gb.blob = blob
-gb.commitActivityTrend = tend�ncia dos commits
-gb.commitActivityDOW = commits por dia da semana
-gb.commitActivityAuthors = principais committers
-gb.feed = feed
-gb.cancel = cancelar
-gb.changePassword = alterar password
-gb.isFederated = est� federado
-gb.federateThis = federar este reposit�rio
-gb.federateOrigin = federar o origin
-gb.excludeFromFederation = excluir da federa��o
-gb.excludeFromFederationDescription = bloquear inst�ncias federadas do GitBlit de fazer pull desta conta
-gb.tokens = tokens de federa��o
-gb.tokenAllDescription = todos reposit�rios, usu�rios & configura��es
-gb.tokenUnrDescription = todos reposit�rios & usu�rios
-gb.tokenJurDescription = todos reposit�rios
-gb.federatedRepositoryDefinitions = defini��es de reposit�rio
-gb.federatedUserDefinitions = defini��es de usu�rios
-gb.federatedSettingDefinitions = defini��es de configura��es
-gb.proposals = propostas de federa��es
-gb.received = recebidos
-gb.type = tipo
-gb.token = token
-gb.repositories = reposit�rios
-gb.proposal = propostas
-gb.frequency = frequ�ncia
-gb.folder = pasta
-gb.lastPull = �ltimo pull
-gb.nextPull = pr�ximo pull
-gb.inclusions = inclus�es
-gb.exclusions = exclu�es
-gb.registration = cadastro
-gb.registrations = cadastro de federa��es
-gb.sendProposal = enviar proposta
-gb.status = status
-gb.origin = origin
-gb.headRef = default branch (HEAD)
-gb.headRefDescription = alterar a ref o qual a HEAD aponta. e.g. refs/heads/master
-gb.federationStrategy = estrat�gia de federa��o 
-gb.federationRegistration = cadastro de federa��es
-gb.federationResults = resultados dos pulls de federa��es
-gb.federationSets = ajustes de federa��es
-gb.message = mensagem
-gb.myUrlDescription = a url de acesso p�blico para a inst�ncia Gitblit
-gb.destinationUrl = enviar para
-gb.destinationUrlDescription = a url da int�ncia do Gitblit para enviar sua proposta
-gb.users = usu�rios
-gb.federation = federa��o
-gb.error = erro
-gb.refresh = atualizar
-gb.browse = navegar
-gb.clone = clonar
-gb.filter = filtrar
-gb.create = criar
-gb.servers = servidores
-gb.recent = recente
-gb.available = dispon�vel
-gb.selected = selecionado
-gb.size = tamanho
-gb.downloading = downloading
-gb.loading = loading
-gb.starting = inciando
-gb.general = geral
-gb.settings = configura��es
-gb.manage = administrar
-gb.lastLogin = �ltimo login
-gb.skipSizeCalculation = ignorar c�lculo do tamanho
-gb.skipSizeCalculationDescription = n�o calcular o tamanho do reposit�rio (reduz o tempo de load da p�gina)
-gb.skipSummaryMetrics = ignorar resumo das m�tricas
-gb.skipSummaryMetricsDescription = n�o calcular m�tricas na p�gina de resumo
-gb.accessLevel = acesso
-gb.default = default
-gb.setDefault = tornar default
-gb.since = desde
-gb.status = status
-gb.bootDate = data do boot
-gb.servletContainer = servlet container
-gb.heapMaximum = heap m�ximo
-gb.heapAllocated = alocar heap
-gb.heapUsed = usar heap
-gb.free = free
-gb.version = vers�o
-gb.releaseDate = data de release
-gb.date = data
-gb.activity = atividade
-gb.subscribe = inscrever
-gb.branch = branch
-gb.maxHits = hits m�ximos
-gb.recentActivity = atividade recente
-gb.recentActivityStats = �ltimos {0} dias / {1} commits por {2} autores
-gb.recentActivityNone = �ltimos {0} dias / nenhum
-gb.dailyActivity = atividade di�ria
-gb.activeRepositories = reposit�rios ativos
-gb.activeAuthors = autores ativos
-gb.commits = commits
-gb.teams = equipes
-gb.teamName = nome da equipe
-gb.teamMembers = membros
-gb.teamMemberships = filia��es em equipes
-gb.newTeam = nova equipe
-gb.permittedTeams = equipes permitidas
-gb.emptyRepository = reposit�rio vazio
-gb.repositoryUrl = url do reposit�rio
-gb.mailingLists = listas de e-mails
-gb.preReceiveScripts = pre-receive scripts
-gb.postReceiveScripts = post-receive scripts
-gb.hookScripts = hook scripts
-gb.customFields = campos customizados
-gb.customFieldsDescription = campos customizados dispon�veis para Groovy hooks
-gb.accessPermissions = permiss�es de acesso
-gb.filters = filtros
-gb.generalDescription = configura��es comuns
-gb.accessPermissionsDescription = restringir acesso por usu�rios e equipes
-gb.accessPermissionsForUserDescription = ajustar filia��es em equipes ou garantir acesso a reposit�rios espec�ficos
-gb.accessPermissionsForTeamDescription = ajustar membros da equipe e garantir acesso a reposit�rios espec�ficos
-gb.federationRepositoryDescription = compartilhar este reposit�rio com outros servidores Gitblit
-gb.hookScriptsDescription = rodar scripts Groovy nos pushes para este servidor Gitblit
-gb.reset = reset
-gb.pages = p�ginas
-gb.workingCopy = working copy
-gb.workingCopyWarning = este reposit�rio tem uma working copy e n�o pode receber pushes
-gb.query = query
-gb.queryHelp =  Standard query syntax � suportada.<p/><p/>Por favor veja <a target="_new" href="http://lucene.apache.org/core/old_versioned_docs/versions/3_5_0/queryparsersyntax.html">Lucene Query Parser Syntax</a> para mais detalhes.
-gb.queryResults = resultados {0} - {1} ({2} hits)
-gb.noHits = sem hits
-gb.authored = foi autor de
-gb.committed = committed
-gb.indexedBranches = branches indexados
-gb.indexedBranchesDescription = selecione os branches para incluir nos seus �ndices Lucene
-gb.noIndexedRepositoriesWarning = nenhum dos seus reposit�rios foram configurados para indexa��o do Lucene
-gb.undefinedQueryWarning = a query n�o foi definida!
-gb.noSelectedRepositoriesWarning = por favor selecione um ou mais reposit�rios!
-gb.luceneDisabled = indexa��o do Lucene est� desabilitada
-gb.failedtoRead = leitura falhou
-gb.isNotValidFile = n�o � um arquivo v�lido
-gb.failedToReadMessage = Falhou em ler mensagens default de {0}!
-gb.passwordsDoNotMatch = Passwords n�o conferem!
-gb.passwordTooShort = Password � muito curto. Tamanho m�nimo s�o {0} caracteres.
-gb.passwordChanged = Password alterado com sucesso.
-gb.passwordChangeAborted = altera��o do password foi abortada.
-gb.pleaseSetRepositoryName = Por favor ajuste o nome do reposit�rio!
-gb.illegalLeadingSlash = Refer�ncias a diret�rios raiz come�ando com (/) s�o proibidas.
-gb.illegalRelativeSlash = Refer�ncias a diret�rios relativos (../) s�o proibidas.
-gb.illegalCharacterRepositoryName = Caractere ilegal ''{0}'' no nome do reposit�rio!
-gb.selectAccessRestriction = Por favor selecione a restri��o de acesso!
-gb.selectFederationStrategy = Por favor selecione a estrat�gia de federa��o!
-gb.pleaseSetTeamName = Por favor insira um nome de equipe!
-gb.teamNameUnavailable = O nome de equipe ''{0}'' est� indispon�vel.
-gb.teamMustSpecifyRepository = Uma equipe deve especificar pelo menos um reposit�rio.
-gb.teamCreated = Nova equipe ''{0}'' criada com sucesso.
-gb.pleaseSetUsername = Por favor entre com um username!
-gb.usernameUnavailable = Username ''{0}'' est� indispon�vel.
-gb.combinedMd5Rename = Gitblit est� configurado para usar um hash combinado-md5. Voc� deve inserir um novo password ao renamear a conta.
-gb.userCreated = Novo usu�rio ''{0}'' criado com sucesso.
-gb.couldNotFindFederationRegistration = N�o foi poss�vel localizar o registro da federa��o!
-gb.failedToFindGravatarProfile = Falhou em localizar um perfil Gravatar para {0}
-gb.branchStats = {0} commits e {1} tags em {2}
-gb.repositoryNotSpecified = Reposit�rio n�o espec�ficado!
-gb.repositoryNotSpecifiedFor = Reposit�rio n�o espec�ficado para {0}!
-gb.canNotLoadRepository = N�o foi poss�vel carregar o reposit�rio
-gb.commitIsNull = Commit est� nulo
-gb.unauthorizedAccessForRepository = Acesso n�o autorizado para o reposit�rio
-gb.failedToFindCommit = N�o foi poss�vel achar o commit \"{0}\" em {1} para {2} p�gina!
-gb.couldNotFindFederationProposal = N�o foi poss�vel localizar propostas de federa��o!
-gb.invalidUsernameOrPassword = username ou password inv�lido!
-gb.OneProposalToReview = Existe uma proposta de federa��o aguardando revis�o. 
-gb.nFederationProposalsToReview = Existem {0} propostas de federa��o aguardando revis�o.
-gb.couldNotFindTag = N�o foi poss�vel localizar a tag {0}
-gb.couldNotCreateFederationProposal = N�o foi poss�vel criar uma proposta de federation!
-gb.pleaseSetGitblitUrl = Por favor insira sua url do Gitblit!
-gb.pleaseSetDestinationUrl = Por favor insira a url de destino para sua proposta!
-gb.proposalReceived = Proposta recebida com sucesso por {0}.
-gb.noGitblitFound = Desculpe, {0} n�o localizou uma inst�ncia do Gitblit em {1}.
-gb.noProposals = Desculpe, {0} n�o est� aceitando propostas agora.
-gb.noFederation = Desculpe, {0} n�o est� configurado com nenhuma int�ncia do Gitblit.
-gb.proposalFailed = Desculpe, {0} n�o recebeu nenhum dado de proposta!
-gb.proposalError = Desculpe, {0} reportou que um erro inesperado ocorreu!
-gb.failedToSendProposal = N�o foi poss�vel enviar a proposta!
-gb.userServiceDoesNotPermitAddUser = {0} n�o permite adicionar uma conta de usu�rio!
-gb.userServiceDoesNotPermitPasswordChanges = {0} n�o permite altera��es no password!
-gb.displayName = nome
-gb.emailAddress = e-mail
-gb.errorAdminLoginRequired = Administra��o requer um login
-gb.errorOnlyAdminMayCreateRepository = Somente umadministrador pode criar um reposit�rio
-gb.errorOnlyAdminOrOwnerMayEditRepository = Somente umadministrador pode editar um reposit�rio
-gb.errorAdministrationDisabled = Administra��o est� desabilitada
-gb.lastNDays = �ltimos {0} dias
-gb.completeGravatarProfile = Profile completo em Gravatar.com
-gb.none = nenhum
-gb.line = linha
-gb.content = conte�do
-gb.empty = vazio
-gb.inherited = herdado
-gb.deleteRepository = Deletar reposit�rio \"{0}\"?
-gb.repositoryDeleted = Reposit�rio ''{0}'' deletado.
-gb.repositoryDeleteFailed = N�o foi poss�vel apagar o reposit�rio ''{0}''!
-gb.deleteUser = Deletar usu�rio \"{0}\"?
-gb.userDeleted = Usu�rio ''{0}'' deletado.
-gb.userDeleteFailed = N�o foi poss�vel apagar o usu�rio ''{0}''!
-gb.time.justNow = agora mesmo
-gb.time.today = hoje
-gb.time.yesterday = ontem
-gb.time.minsAgo = h� {0} minutos
-gb.time.hoursAgo = h� {0} horas
-gb.time.daysAgo = h� {0} dias
-gb.time.weeksAgo = h� {0} semanas
-gb.time.monthsAgo = h� {0} meses
-gb.time.oneYearAgo = h� 1 ano
-gb.time.yearsAgo = h� {0} anos 
-gb.duration.oneDay = 1 dia
-gb.duration.days = {0} dias
-gb.duration.oneMonth = 1 m�s
-gb.duration.months = {0} meses
-gb.duration.oneYear = 1 ano
-gb.duration.years = {0} anos
-gb.authorizationControl = controle de autoriza��o
-gb.allowAuthenticatedDescription = conceder permiss�o RW+ para todos os usu�rios aut�nticados
-gb.allowNamedDescription = conceder permiss�es refinadas para usu�rios escolhidos ou equipes
-gb.markdownFailure = N�o foi poss�vel converter conte�do Markdown!
-gb.clearCache = limpar o cache
-gb.projects = projetos
-gb.project = projeto
-gb.allProjects = todos projetos
-gb.copyToClipboard = copiar para o clipboard
-gb.fork = fork
-gb.forks = forks
-gb.forkRepository = fork {0}?
-gb.repositoryForked = fork feito em {0}
-gb.repositoryForkFailed= n�o foi poss�vel fazer fork
-gb.personalRepositories = reposit�rios pessoais
-gb.allowForks = permitir forks
-gb.allowForksDescription = permitir usu�rios autorizados a fazer fork deste reposit�rio
-gb.forkedFrom = forked de
-gb.canFork = pode fazer fork
-gb.canForkDescription = pode fazer fork de reposit�rios autorizados para reposit�rios pessoais
-gb.myFork = visualizar meu fork
-gb.forksProhibited = forks proibidos
-gb.forksProhibitedWarning = este reposit�rio pro�be forks
-gb.noForks = {0} n�o possui forks
-gb.forkNotAuthorized = desculpe, voc� n�o est� autorizado a fazer fork de {0}
-gb.forkInProgress = fork em progresso
-gb.preparingFork = preparando seu fork...
-gb.isFork = � fork
-gb.canCreate = pode criar
-gb.canCreateDescription = pode criar reposit�rios pessoais
-gb.illegalPersonalRepositoryLocation = seu reposit�rio pessoal deve estar localizado em \"{0}\"
-gb.verifyCommitter = verificar committer
-gb.verifyCommitterDescription = requer a identidade do committer para combinar com uma conta do Gitblt
-gb.verifyCommitterNote = todos os merges requerem "--no-ff" para impor a identidade do committer
-gb.repositoryPermissions = permiss�es de reposit�rio
-gb.userPermissions = permiss�es de usu�rio
-gb.teamPermissions = permiss�es de equipe
-gb.add = add
-gb.noPermission = APAGAR ESTA PERMISS�O
-gb.excludePermission = {0} (excluir)
-gb.viewPermission = {0} (visualizar)
-gb.clonePermission = {0} (clonar)
-gb.pushPermission = {0} (push)
-gb.createPermission = {0} (push, ref creation)
-gb.deletePermission = {0} (push, ref creation+deletion)
-gb.rewindPermission = {0} (push, ref creation+deletion+rewind)
-gb.permission = permiss�o
-gb.regexPermission = esta permiss�o foi configurada atrav�s da express�o regular \"{0}\"
-gb.accessDenied = acesso negado
-gb.busyCollectingGarbage = desculpe, o Gitblit est� ocupado coletando lixo em {0}
-gb.gcPeriod = per�odo do GC
-gb.gcPeriodDescription = dura��o entre as coletas de lixo
-gb.gcThreshold = limite do GC 
-gb.gcThresholdDescription = tamanho total m�nimo de objetos \"soltos\" que ativam a coleta de lixo
-gb.ownerPermission = propriet�rio do reposit�rio
-gb.administrator = administrador
-gb.administratorPermission = administrador do Gitblit
-gb.team = equipe
-gb.teamPermission = permiss�o concedida pela filia��o a equipe \"{0}\"
-gb.missing = faltando!
-gb.missingPermission = o reposit�rio para esta permiss�o est� faltando!
-gb.mutable = mut�vel
-gb.specified = espec�fico
-gb.effective = efetivo
-gb.organizationalUnit = unidade organizacional
-gb.organization = organiza��o
-gb.locality = localidade
-gb.stateProvince = estado ou prov�ncia
-gb.countryCode = c�digo do pa�s
-gb.properties = propriedades
-gb.issued = emitido
-gb.expires = expira
-gb.expired = expirado
-gb.expiring = expirando
-gb.revoked = revogado
-gb.serialNumber = n�mero serial
-gb.certificates = certificados
-gb.newCertificate = novo certificado
-gb.revokeCertificate = revogar certificado
-gb.sendEmail = enviar email
-gb.passwordHint = dica de password
-gb.ok = ok
-gb.invalidExpirationDate = data de expira��o inv�lida!
-gb.passwordHintRequired = dica de password requerida!
-gb.viewCertificate = visualizar certificado
-gb.subject = assunto
-gb.issuer = emissor
-gb.validFrom = v�lido a partir de
-gb.validUntil = v�lido at�
-gb.publicKey = chave p�blica
-gb.signatureAlgorithm = algoritmo de assinatura
-gb.sha1FingerPrint = digital SHA-1 
-gb.md5FingerPrint = digital MD5
-gb.reason = raz�o
-gb.revokeCertificateReason = Por selecione a raz�o da revoga��o do certificado
-gb.unspecified = n�o espec�fico
-gb.keyCompromise = comprometimento de chave
-gb.caCompromise = compromisso CA
-gb.affiliationChanged = afilia��o foi alterada
-gb.superseded = substitu�das
-gb.cessationOfOperation = cessa��o de funcionamento
-gb.privilegeWithdrawn = privil�gio retirado
-gb.time.inMinutes = em {0} minutos
-gb.time.inHours = em {0} horas
-gb.time.inDays = em {0} dias
-gb.hostname = hostname
-gb.hostnameRequired = Por favor insira um hostname
-gb.newSSLCertificate = novo servidor de certificado SSL
-gb.newCertificateDefaults = novos padr�es de certifica��o
-gb.duration = dura��o
-gb.certificateRevoked = Certificado {0, n�mero, 0} foi revogado
-gb.clientCertificateGenerated = Novo certificado cliente para {0} foi gerado com sucesso
-gb.sslCertificateGenerated = Novo servidor de certificado SSL gerado com sucesso para {0}
-gb.newClientCertificateMessage = OBSERVA��O:\nO 'password' n�o � o password do usu�rio mas sim o password usado para proteger a keystore.  Este password n�o ser� salvo ent�o voc� tamb�m inserir uma dica que ser� inclu�da nas instru��es de LEIA-ME do usu�rio.
-gb.certificate = certificado
-gb.emailCertificateBundle = pacote certificado de cliente de email
-gb.pleaseGenerateClientCertificate = Por favor gere um certificado cliente para {0}
-gb.clientCertificateBundleSent = Pacote de certificado de cliente para {0} enviada
-gb.enterKeystorePassword = Por favor insira uma chave para keystore do Gitblit
-gb.warning = warning
-gb.jceWarning = Seu Java Runtime Environment n�o tem os arquivos \"JCE Unlimited Strength Jurisdiction Policy\".\nIsto ir� limitar o tamanho dos passwords que voc� usar� para encriptar suas keystores para 7 caracteres.\nEstes arquivos de pol�ticas s�o um download opcional da Oracle.\n\nVoc� gostaria de continuar e gerar os certificados de infraestrutura de qualquer forma?\n\nRespondendo "N�o" ir� redirecionar o seu browser para a p�gina de downloads da Oracle, de onde voc� poder� fazer download desses arquivos.
-gb.maxActivityCommits = limitar exibi��o de commits
-gb.maxActivityCommitsDescription = quantidade m�xima de commits para contribuir para a p�gina de atividade
-gb.noMaximum = ilimitado
-gb.attributes = atributos
-gb.serveCertificate = servir https com este certificado
-gb.sslCertificateGeneratedRestart = Novo certificado SSL de servidor gerado com sucesso para {0}.\nVoc� deve reiniciar o Gitblit para usar o novo certificado.\n\nSe voc� estiver executando com o par�metro '--alias', voc� precisar� alter�-lo para ''--alias {0}''.
-gb.validity = validade
-gb.siteName = nome do site
-gb.siteNameDescription = breve, mas ainda assim um nome descritivo para seu servidor
-gb.excludeFromActivity = excluir da p�gina de atividades
-gb.isSparkleshared = reposit�rio � Sparkleshared
-gb.owners = propriet�rios
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/GitBlitWebApp_zh_CN.properties b/src/com/gitblit/wicket/GitBlitWebApp_zh_CN.properties
deleted file mode 100644
index b571585..0000000
--- a/src/com/gitblit/wicket/GitBlitWebApp_zh_CN.properties
+++ /dev/null
@@ -1,444 +0,0 @@
-gb.repository = \u7248\u672c\u5e93
-gb.owner = \u7ba1\u7406\u5458
-gb.description = \u63cf\u8ff0
-gb.lastChange = \u6700\u8fd1\u4fee\u6539
-gb.refs = refs
-gb.tag = \u6807\u7b7e
-gb.tags = \u6807\u7b7e
-gb.author = \u7528\u6237
-gb.committer = \u63d0\u4ea4\u8005
-gb.commit = \u63d0\u4ea4
-gb.tree = \u76ee\u5f55
-gb.parent = parent
-gb.url = URL
-gb.history = \u5386\u53f2\u4fe1\u606f
-gb.raw = raw
-gb.object = object
-gb.ticketId = ticket id
-gb.ticketAssigned = assigned
-gb.ticketOpenDate = \u5f00\u542f\u65e5\u671f
-gb.ticketState = \u72b6\u6001
-gb.ticketComments = \u8bc4\u8bba
-gb.view = \u67e5\u770b
-gb.local = \u672c\u5730
-gb.remote = \u8fdc\u7a0b
-gb.branches = \u5206\u652f
-gb.patch = patch
-gb.diff = \u5bf9\u6bd4
-gb.log = \u65e5\u5fd7
-gb.moreLogs = \u66f4\u591a\u63d0\u4ea4...
-gb.allTags = \u6240\u6709\u6807\u7b7e...
-gb.allBranches = \u6240\u6709\u5206\u652f...
-gb.summary = \u6982\u51b5
-gb.ticket = ticket
-gb.newRepository = \u521b\u5efa\u7248\u672c\u5e93
-gb.newUser = \u6dfb\u52a0\u7528\u6237
-gb.commitdiff = \u5bf9\u6bd4\u63d0\u4ea4\u7684\u5185\u5bb9
-gb.tickets = tickets
-gb.pageFirst = \u9996\u9875
-gb.pagePrevious = \u524d\u4e00\u9875
-gb.pageNext = \u4e0b\u4e00\u9875
-gb.head = HEAD
-gb.blame = blame
-gb.login = \u767b\u5f55
-gb.logout = \u6ce8\u9500
-gb.username = \u7528\u6237\u540d
-gb.password = \u5bc6\u7801
-gb.tagger = \u6807\u8bb0\u8005
-gb.moreHistory = \u66f4\u591a\u7684\u5386\u53f2\u4fe1\u606f...
-gb.difftocurrent = \u5bf9\u6bd4\u5f53\u524d
-gb.search = \u641c\u7d22
-gb.searchForAuthor = \u6309\u4f5c\u8005\u641c\u7d22 commits
-gb.searchForCommitter = \u6309\u63d0\u4ea4\u8005\u641c\u7d22 commits
-gb.addition = \u6dfb\u52a0
-gb.modification = \u4fee\u6539
-gb.deletion = \u5220\u9664
-gb.rename = \u91cd\u547d\u540d
-gb.metrics = metrics
-gb.stats = \u7edf\u8ba1
-gb.markdown = markdown
-gb.changedFiles = \u5df2\u4fee\u6539\u6587\u4ef6
-gb.filesAdded = {0}\u4e2a\u6587\u4ef6\u5df2\u6dfb\u52a0
-gb.filesModified = {0}\u4e2a\u6587\u4ef6\u5df2\u4fee\u6539
-gb.filesDeleted = {0}\u4e2a\u6587\u4ef6\u5df2\u5220\u9664
-gb.filesCopied = {0} \u6587\u4ef6\u5df2\u590d\u5236
-gb.filesRenamed = {0} \u6587\u4ef6\u5df2\u91cd\u547d\u540d
-gb.missingUsername = \u7528\u6237\u540d\u4e0d\u5b58\u5728
-gb.edit = \u7f16\u8f91
-gb.searchTypeTooltip = \u9009\u62e9\u641c\u7d22\u7c7b\u578b
-gb.searchTooltip = \u641c\u7d22 {0}
-gb.delete = \u5220\u9664
-gb.docs = \u6587\u6863
-gb.accessRestriction = \u8bbf\u95ee\u9650\u5236
-gb.name = \u540d\u79f0
-gb.enableTickets = \u5141\u8bb8 tickets
-gb.enableDocs = \u5141\u8bb8\u6587\u6863
-gb.save = \u4fdd\u5b58
-gb.showRemoteBranches = \u663e\u793a\u8fdc\u7a0b\u5206\u652f
-gb.editUsers = \u7f16\u8f91\u7528\u6237
-gb.confirmPassword = \u786e\u8ba4\u5bc6\u7801
-gb.restrictedRepositories = \u7248\u672c\u5e93\u8bbe\u7f6e
-gb.canAdmin = \u7ba1\u7406\u6743\u9650
-gb.notRestricted = anonymous view, clone, & push
-gb.pushRestricted = authenticated push
-gb.cloneRestricted = authenticated clone & push
-gb.viewRestricted = authenticated view, clone, & push
-gb.useTicketsDescription = distributed Ticgit issues
-gb.useDocsDescription = \u5217\u51fa\u7248\u672c\u5e93\u5185\u6240\u6709 Markdown \u6587\u6863
-gb.showRemoteBranchesDescription = \u663e\u793a\u8fdc\u7a0b\u5206\u652f
-gb.canAdminDescription = Gitblit \u670d\u52a1\u5668\u7ba1\u7406\u5458
-gb.permittedUsers = \u5141\u8bb8\u7528\u6237
-gb.isFrozen = \u88ab\u51bb\u7ed3
-gb.isFrozenDescription = \u7981\u6b62\u63a8\u9001\u64cd\u4f5c
-gb.zip = zip
-gb.showReadme = \u663e\u793areadme
-gb.showReadmeDescription = \u5728\u6982\u51b5\u9875\u9762\u663e\u793a \\"readme\\" Markdown \u6587\u4ef6
-gb.nameDescription = \u4f7f\u7528 '/' \u5bf9\u7248\u672c\u5e93\u8fdb\u884c\u5206\u7ec4  \u4f8b\u5982. libraries/mycoollib.git
-gb.ownerDescription = \u521b\u5efa\u8005\u53ef\u4ee5\u7f16\u8f91\u7248\u672c\u5e93\u5c5e\u6027
-gb.blob = blob
-gb.commitActivityTrend = commit \u6d3b\u52a8\u8d8b\u52bf
-gb.commitActivityDOW = \u6bcf\u5468 commit \u6d3b\u52a8
-gb.commitActivityAuthors = commit \u6d3b\u52a8\u4e3b\u8981\u7528\u6237
-gb.feed = feed
-gb.cancel = \u53d6\u6d88
-gb.changePassword = \u4fee\u6539\u5bc6\u7801
-gb.isFederated = is federated
-gb.federateThis = federate this repository
-gb.federateOrigin = federate the origin
-gb.excludeFromFederation = exclude from federation
-gb.excludeFromFederationDescription = \u7981\u6b62\u5df2 federated \u7684 Gitblit \u5b9e\u4f8b\u4ece\u672c\u8d26\u6237\u62c9\u53d6
-gb.tokens = federation tokens
-gb.tokenAllDescription = all repositories, users, & settings
-gb.tokenUnrDescription = all repositories & users
-gb.tokenJurDescription = all repositories
-gb.federatedRepositoryDefinitions = \u7248\u672c\u5e93\u5b9a\u4e49
-gb.federatedUserDefinitions = \u7528\u6237\u5b9a\u4e49
-gb.federatedSettingDefinitions = \u8bbe\u7f6e\u5b9a\u4e49
-gb.proposals = federation proposals
-gb.received = \u5df2\u63a5\u53d7
-gb.type = type
-gb.token = token
-gb.repositories = \u7248\u672c\u5e93
-gb.proposal = proposal
-gb.frequency = \u9891\u7387
-gb.folder = \u6587\u4ef6\u5939
-gb.lastPull = \u4e0a\u4e00\u6b21\u62c9\u53d6
-gb.nextPull = \u4e0b\u4e00\u6b21\u62c9\u53d6
-gb.inclusions = \u5305\u542b\u5185\u5bb9
-gb.exclusions = \u4f8b\u5916
-gb.registration = \u6ce8\u518c
-gb.registrations = federation \u6ce8\u518c
-gb.sendProposal = propose
-gb.status = \u72b6\u6001
-gb.origin = origin
-gb.headRef = \u9ed8\u8ba4\u5206\u652f (HEAD)
-gb.headRefDescription = \u4fee\u6539 HEAD \u6240\u6307\u5411\u7684 ref\u3002 \u4f8b\u5982: refs/heads/master
-gb.federationStrategy = federation \u7b56\u7565
-gb.federationRegistration = federation \u6ce8\u518c
-gb.federationResults = federation \u62c9\u53d6\u7ed3\u679c
-gb.federationSets = federation \u96c6
-gb.message = \u6d88\u606f
-gb.myUrlDescription = \u60a8\u7684 Gitblit \u5b9e\u4f8b\u7684\u516c\u5171\u8bbf\u95ee\u7f51\u5740
-gb.destinationUrl = \u53d1\u9001\u81f3
-gb.destinationUrlDescription = \u4f60\u6240\u8981\u53d1\u9001proposal\u7684 Gitblit \u5b9e\u4f8b\u7f51\u5740
-gb.users = \u7528\u6237
-gb.federation = federation
-gb.error = \u9519\u8bef
-gb.refresh = \u5237\u65b0
-gb.browse = \u6d4f\u89c8
-gb.clone = \u514b\u9686
-gb.filter = \u8fc7\u6ee4
-gb.create = \u521b\u5efa
-gb.servers = \u670d\u52a1\u5668
-gb.recent = \u6700\u8fd1
-gb.available = \u53ef\u7528
-gb.selected = \u5df2\u9009\u4e2d
-gb.size = \u5927\u5c0f
-gb.downloading = \u4e0b\u8f7d\u4e2d
-gb.loading = \u8f7d\u5165\u4e2d
-gb.starting = \u542f\u52a8\u4e2d
-gb.general = \u5e38\u89c4
-gb.settings = \u8bbe\u7f6e
-gb.manage = \u7ba1\u7406
-gb.lastLogin = \u4e0a\u6b21\u767b\u5f55
-gb.skipSizeCalculation = \u5ffd\u7565\u5927\u5c0f\u4f30\u8ba1
-gb.skipSizeCalculationDescription = \u4e0d\u8ba1\u7b97\u7248\u672c\u5e93\u5927\u5c0f\uff08\u8282\u7701\u9875\u9762\u8f7d\u5165\u65f6\u95f4\uff09
-gb.skipSummaryMetrics = \u5ffd\u7565\u6982\u51b5\u5904 metrics
-gb.skipSummaryMetricsDescription = \u6982\u51b5\u9875\u9762\u4e0d\u8ba1\u7b97metrics\uff08\u8282\u7701\u9875\u9762\u8f7d\u5165\u65f6\u95f4\uff09
-gb.accessLevel = \u8bbf\u95ee\u7ea7\u522b
-gb.default = \u9ed8\u8ba4
-gb.setDefault = \u9ed8\u8ba4\u8bbe\u7f6e
-gb.since = \u81ea\u4ece
-gb.status = \u72b6\u6001
-gb.bootDate = \u542f\u52a8\u65e5\u671f
-gb.servletContainer = servlet container
-gb.heapMaximum = \u6700\u5927\u5806
-gb.heapAllocated = \u5df2\u5206\u914d\u5806
-gb.heapUsed = \u5df2\u4f7f\u7528\u5806
-gb.free = \u7a7a\u95f2
-gb.version = \u7248\u672c
-gb.releaseDate = \u53d1\u884c\u65e5\u671f
-gb.date = \u65e5\u671f
-gb.activity = \u6d3b\u52a8
-gb.subscribe = \u8ba2\u9605
-gb.branch = \u5206\u652f
-gb.maxHits = \u6700\u5927\u547d\u4e2d\u6570
-gb.recentActivity = \u6700\u8fd1\u6d3b\u52a8
-gb.recentActivityStats = \u6700\u8fd1{0}\u5929 / {2}\u4f4d\u7528\u6237\u505a\u4e86{1}\u6b21\u63d0\u4ea4
-gb.recentActivityNone = \u6700\u8fd1{0}\u5929 / \u6ca1\u6709\u6d3b\u52a8
-gb.dailyActivity = \u65e5\u5e38\u6d3b\u52a8
-gb.activeRepositories = \u6d3b\u8dc3\u7684\u7248\u672c\u5e93
-gb.activeAuthors = \u6d3b\u8dc3\u7528\u6237
-gb.commits = \u63d0\u4ea4\u6b21\u6570
-gb.teams = \u56e2\u961f
-gb.teamName = \u56e2\u961f\u540d\u79f0
-gb.teamMembers = \u56e2\u961f\u6210\u5458
-gb.teamMemberships = \u56e2\u961f\u6210\u5458
-gb.newTeam = \u6dfb\u52a0\u56e2\u961f
-gb.permittedTeams = \u5141\u8bb8\u56e2\u961f
-gb.emptyRepository = \u7a7a\u7248\u672c\u5e93
-gb.repositoryUrl = \u7248\u672c\u5e93\u5730\u5740
-gb.mailingLists = \u90ae\u4ef6\u5217\u8868
-gb.preReceiveScripts = pre-receive \u811a\u672c
-gb.postReceiveScripts = post-receive \u811a\u672c
-gb.hookScripts = hook \u811a\u672c
-gb.customFields = \u81ea\u5b9a\u4e49\u57df
-gb.customFieldsDescription = Groovy\u811a\u672c\u652f\u6301\u7684\u81ea\u5b9a\u4e49\u57df
-gb.accessPermissions = \u8bbf\u95ee\u6743\u9650
-gb.filters = \u8fc7\u6ee4
-gb.generalDescription = \u4e00\u822c\u8bbe\u7f6e
-gb.accessPermissionsDescription = \u6309\u7167\u7528\u6237\u548c\u56e2\u961f\u9650\u5236\u8bbf\u95ee
-gb.accessPermissionsForUserDescription = \u8bbe\u7f6e\u56e2\u961f\u6210\u5458\u6216\u8005\u6388\u4e88\u6307\u5b9a\u7248\u672c\u5e93\u6743\u9650
-gb.accessPermissionsForTeamDescription = \u8bbe\u7f6e\u56e2\u961f\u6210\u5458\u5e76\u6388\u4e88\u6307\u5b9a\u7248\u672c\u5e93\u6743\u9650
-gb.federationRepositoryDescription = \u4e0e\u5176\u4ed6Gitblit\u670d\u52a1\u5668\u5206\u4eab\u7248\u672c\u5e93
-gb.hookScriptsDescription = \u5728\u670d\u52a1\u5668\u4e0a\u8fd0\u884cGroovy\u811a\u672c
-gb.reset = \u91cd\u7f6e
-gb.pages = \u9875\u9762
-gb.workingCopy = \u5de5\u4f5c\u526f\u672c
-gb.workingCopyWarning = \u6b64\u7248\u672c\u5e93\u5b58\u5728\u4e00\u4efd\u5de5\u4f5c\u526f\u672c\uff0c\u65e0\u6cd5\u8fdb\u884c\u63a8\u9001
-gb.query = \u67e5\u8be2
-gb.queryHelp = \u652f\u6301\u6807\u51c6\u67e5\u8be2\u683c\u5f0f.<p/><p/>\u8bf7\u67e5\u770b <a target="_new" href="http://lucene.apache.org/core/old_versioned_docs/versions/3_5_0/queryparsersyntax.html">Lucene \u67e5\u8be2\u5904\u7406\u5668\u683c\u5f0f</a> \u4ee5\u83b7\u53d6\u8be6\u7ec6\u5185\u5bb9\u3002
-gb.queryResults = \u7ed3\u679c {0} - {1} ({2} \u6b21\u547d\u4e2d)
-gb.noHits = \u672a\u547d\u4e2d
-gb.authored = authored
-gb.committed = committed
-gb.indexedBranches = \u5df2\u7d22\u5f15\u5206\u652f
-gb.indexedBranchesDescription = \u9009\u62e9\u8981\u653e\u5165\u4f60\u7684 Lucene \u7d22\u5f15\u7684\u5206\u652f
-gb.noIndexedRepositoriesWarning = \u60a8\u7684\u6240\u6709\u7248\u672c\u5e93\u90fd\u6ca1\u6709\u7ecf\u8fc7Lucene\u7d22\u5f15
-gb.undefinedQueryWarning = \u67e5\u8be2\u672a\u5b9a\u4e49!
-gb.noSelectedRepositoriesWarning = \u8bf7\u81f3\u5c11\u9009\u62e9\u4e00\u4e2a\u7248\u672c\u5e93!
-gb.luceneDisabled = Lucene\u7d22\u5f15\u5df2\u88ab\u7981\u6b62
-gb.failedtoRead = \u8bfb\u53d6\u5931\u8d25
-gb.isNotValidFile = \u4e0d\u662f\u5408\u6cd5\u6587\u4ef6
-gb.failedToReadMessage = \u5728 {0} \u4e2d\u8bfb\u53d6\u9ed8\u8ba4\u6d88\u606f\u5931\u8d25!
-gb.passwordsDoNotMatch = \u5bc6\u7801\u4e0d\u5339\u914d!
-gb.passwordTooShort = \u5bc6\u7801\u957f\u5ea6\u592a\u77ed\u3002\u6700\u77ed\u957f\u5ea6 {0} \u4e2a\u5b57\u7b26\u3002
-gb.passwordChanged = \u5bc6\u7801\u4fee\u6539\u6210\u529f\u3002
-gb.passwordChangeAborted = \u5bc6\u7801\u4fee\u6539\u7ec8\u6b62
-gb.pleaseSetRepositoryName = \u8bf7\u8bbe\u7f6e\u4e00\u4e2a\u7248\u672c\u5e93\u540d\u79f0!
-gb.illegalLeadingSlash = \u7981\u6b62\u4f7f\u7528\u6839\u76ee\u5f55\u5f15\u7528 (/) \u3002
-gb.illegalRelativeSlash = \u76f8\u5bf9\u6587\u4ef6\u5939\u8def\u5f84(../)\u7981\u6b62\u4f7f\u7528
-gb.illegalCharacterRepositoryName = \u7248\u672c\u5e93\u4e2d\u542b\u6709\u4e0d\u5408\u6cd5\u5b57\u7b26 ''{0}'' !
-gb.selectAccessRestriction = \u8bf7\u9009\u62e9\u8bbf\u95ee\u6743\u9650\uff01
-gb.selectFederationStrategy = \u8bf7\u9009\u62e9federation\u7b56\u7565!
-gb.pleaseSetTeamName = \u8bf7\u8f93\u5165\u4e00\u4e2a\u56e2\u961f\u540d\u79f0\uff01
-gb.teamNameUnavailable = \u56e2\u961f\u540d ''{0}'' \u4e0d\u5408\u6cd5.
-gb.teamMustSpecifyRepository = \u56e2\u961f\u5fc5\u987b\u62e5\u6709\u81f3\u5c11\u4e00\u4e2a\u7248\u672c\u5e93\u3002
-gb.teamCreated = \u6210\u529f\u521b\u5efa\u65b0\u56e2\u961f ''{0}'' .
-gb.pleaseSetUsername = \u8bf7\u8f93\u5165\u7528\u6237\u540d\uff01
-gb.usernameUnavailable = \u7528\u6237\u540d ''{0}'' \u4e0d\u53ef\u7528..
-gb.combinedMd5Rename = Gitblit\u91c7\u7528\u6df7\u5408md5\u5bc6\u7801\u54c8\u5e0c\u3002\u56e0\u6b64\u5fc5\u987b\u5728\u4fee\u6539\u7528\u6237\u540d\u540e\u4fee\u6539\u5bc6\u7801\u3002
-gb.userCreated = \u6210\u529f\u521b\u5efa\u65b0\u7528\u6237 \\"{0}\\"\u3002
-gb.couldNotFindFederationRegistration = \u65e0\u6cd5\u627e\u5230federation registration!
-gb.failedToFindGravatarProfile = \u52a0\u8f7d {0} \u7684Gravatar\u4fe1\u606f\u5931\u8d25
-gb.branchStats = {0} \u4e2a\u63d0\u4ea4\u548c {1} \u4e2a\u6807\u7b7e\u5728 {2} \u5185
-gb.repositoryNotSpecified = \u672a\u6307\u5b9a\u7248\u672c\u5e93!
-gb.repositoryNotSpecifiedFor = \u6ca1\u6709\u4e3a {0} \u8bbe\u7f6e\u7248\u672c\u5e93!
-gb.canNotLoadRepository = \u65e0\u6cd5\u8f7d\u5165\u7248\u672c\u5e93
-gb.commitIsNull = \u63d0\u4ea4\u5185\u5bb9\u4e3a\u7a7a
-gb.unauthorizedAccessForRepository = \u672a\u6388\u6743\u8bbf\u95ee\u7248\u672c\u5e93
-gb.failedToFindCommit = \u5728 {1} \u4e2d {2} \u4e2a\u9875\u9762\u5185\u67e5\u627e\u63d0\u4ea4 \\"{0}\\"\u5931\u8d25!
-gb.couldNotFindFederationProposal = \u65e0\u6cd5\u627e\u5230federation proposal!
-gb.invalidUsernameOrPassword = \u7528\u6237\u540d\u6216\u8005\u5bc6\u7801\u9519\u8bef\uff01
-gb.OneProposalToReview = 1\u4e2afederation proposals\u7b49\u5f85\u68c0\u67e5\u3002
-gb.nFederationProposalsToReview = {0} \u4e2afederation proposals\u7b49\u5f85\u68c0\u67e5
-gb.couldNotFindTag = \u65e0\u6cd5\u627e\u5230\u6807\u7b7e {0}
-gb.couldNotCreateFederationProposal = \u65e0\u6cd5\u521b\u5efafederation proposal!
-gb.pleaseSetGitblitUrl = \u8bf7\u8f93\u5165\u4f60\u7684Gitblit\u7f51\u5740!
-gb.pleaseSetDestinationUrl = \u8bf7\u4e3a\u4f60\u7684proposal\u8f93\u5165\u4e00\u4e2a\u76ee\u6807\u5730\u5740!
-gb.proposalReceived = \u6210\u529f\u4ece {0} \u63a5\u6536Proposal.
-gb.noGitblitFound = \u62b1\u6b49, {0} \u65e0\u6cd5\u5728{1} \u4e2d\u627e\u5230Gitblit\u5b9e\u4f8b\u3002
-gb.noProposals = \u62b1\u6b49, {0} \u5f53\u524d\u4e0d\u63a5\u53d7proposals\u3002
-gb.noFederation = \u62b1\u6b49, {0} \u6ca1\u6709\u4e0e\u4efb\u4f55Gitblit\u5b9e\u4f8b\u8bbe\u7f6efederate\u3002.
-gb.proposalFailed = \u62b1\u6b49, {0} \u65e0\u6cd5\u63a5\u53d7\u4efb\u4f55proposal\u6570\u636e!
-gb.proposalError = \u62b1\u6b49\uff0c{0} \u62a5\u544a\u4e2d\u53d1\u73b0\u672a\u9884\u671f\u7684\u9519\u8bef\uff01
-gb.failedToSendProposal = \u53d1\u9001proposal\u5931\u8d25!
-gb.userServiceDoesNotPermitAddUser = {0} \u4e0d\u5141\u8bb8\u6dfb\u52a0\u7528\u6237!
-gb.userServiceDoesNotPermitPasswordChanges = {0} \u4e0d\u5141\u8bb8\u8fdb\u884c\u5bc6\u7801\u4fee\u6539!
-gb.displayName = \u663e\u793a\u540d\u79f0
-gb.emailAddress = \u90ae\u7bb1
-gb.errorAdminLoginRequired = \u9700\u8981\u7ba1\u7406\u5458\u767b\u9646
-gb.errorOnlyAdminMayCreateRepository = \u53ea\u6709\u7ba1\u7406\u5458\u624d\u53ef\u4ee5\u521b\u5efa\u7248\u672c\u5e93
-gb.errorOnlyAdminOrOwnerMayEditRepository = \u53ea\u6709\u7ba1\u7406\u5458\u6216\u8005\u6240\u6709\u8005\u624d\u53ef\u4ee5\u7f16\u8f91\u4ee3\u7801\u5e93
-gb.errorAdministrationDisabled = \u7ba1\u7406\u6743\u9650\u88ab\u7981\u6b62\u3002
-gb.lastNDays = \u6700\u8fd1 {0} \u5929
-gb.completeGravatarProfile = \u5728Gravatar.com\u4e0a\u5b8c\u6210\u4e2a\u4eba\u8bbe\u5b9a
-gb.none = \u65e0
-gb.line = \u884c
-gb.content = \u5185\u5bb9
-gb.empty = \u7a7a\u767d\u7248\u672c\u5e93
-gb.inherited = \u7ee7\u627f
-gb.deleteRepository = \u5220\u9664\u7248\u672c\u5e93 \\"{0}\\" \uff1f
-gb.repositoryDeleted = \u7248\u672c\u5e93 ''{0}'' \u5df2\u5220\u9664\u3002
-gb.repositoryDeleteFailed = \u5220\u9664\u7248\u672c\u5e93 \\"{0}\\" \u5931\u8d25\uff01
-gb.deleteUser = \u5220\u9664\u7528\u6237 \\"{0}\\" \uff1f
-gb.userDeleted = \u7528\u6237 ''{0}'' \u5df2\u5220\u9664\uff01
-gb.userDeleteFailed = \u5220\u9664\u7528\u6237''{0}''\u5931\u8d25\uff01
-gb.time.justNow = \u521a\u521a
-gb.time.today = \u4eca\u5929
-gb.time.yesterday = \u6628\u5929
-gb.time.minsAgo = {0} \u5206\u949f\u4ee5\u524d
-gb.time.hoursAgo = {0} \u5c0f\u65f6\u4ee5\u524d
-gb.time.daysAgo = {0} \u5929\u4ee5\u524d
-gb.time.weeksAgo = {0} \u5468\u4ee5\u524d
-gb.time.monthsAgo = {0} \u4e2a\u6708\u4ee5\u524d
-gb.time.oneYearAgo = 1 \u5e74\u4ee5\u524d
-gb.time.yearsAgo = {0} \u5e74\u4ee5\u524d
-gb.duration.oneDay = 1 \u5929
-gb.duration.days = {0} \u5929
-gb.duration.oneMonth = 1 \u6708
-gb.duration.months = {0} \u6708
-gb.duration.oneYear = 1 \u5e74
-gb.duration.years = {0} \u5e74
-gb.authorizationControl = \u6388\u6743\u63a7\u5236
-gb.allowAuthenticatedDescription = \u6388\u4e88\u6240\u6709\u8ba4\u8bc1\u7528\u6237\u53d7\u9650\u5236\u7684\u8bbf\u95ee\u6743\u9650
-gb.allowNamedDescription = \u6388\u4e88\u6307\u5b9a\u540d\u79f0\u7684\u7528\u6237\u6216\u56e2\u961f\u53d7\u9650\u5236\u7684\u8bbf\u95ee\u6743\u9650
-gb.markdownFailure = \u8bfb\u53d6 Markdown \u5185\u5bb9\u5931\u8d25\uff01
-gb.clearCache = \u6e05\u9664\u7f13\u5b58
-gb.projects = \u9879\u76ee
-gb.project = \u9879\u76ee
-gb.allProjects = \u6240\u6709\u9879\u76ee
-gb.copyToClipboard = \u590d\u5236\u5230\u526a\u8d34\u677f
-gb.fork = \u6d3e\u751f
-gb.forks = \u6d3e\u751f
-gb.forkRepository = \u6d3e\u751f {0} ?
-gb.repositoryForked = {0} \u5df2\u88ab\u6d3e\u751f
-gb.repositoryForkFailed = \u6d3e\u751f\u5931\u8d25
-gb.personalRepositories = \u79c1\u4eba\u7248\u672c\u5e93
-gb.allowForks = \u5141\u8bb8\u6d3e\u751f
-gb.allowForksDescription = \u5141\u8bb8\u8ba4\u8bc1\u7528\u6237\u6d3e\u751f\u6b64\u7248\u672c\u5e93
-gb.forkedFrom = \u6d3e\u751f\u81ea
-gb.canFork = \u5141\u8bb8\u6d3e\u751f
-gb.canForkDescription = \u5141\u8bb8\u6d3e\u751f\u8ba4\u8bc1\u7248\u672c\u5e93\u5230\u79c1\u4eba\u7248\u672c\u5e93
-gb.myFork = \u67e5\u770b\u6211\u7684\u6d3e\u751f
-gb.forksProhibited = \u7981\u6b62\u6d3e\u751f
-gb.forksProhibitedWarning = \u5f53\u524d\u7248\u672c\u5e93\u7981\u6b62\u6d3e\u751f
-gb.noForks = {0} \u6ca1\u6709\u6d3e\u751f
-gb.forkNotAuthorized = \u62b1\u6b49\uff0c\u4f60\u65e0\u6743\u6d3e\u751f {0}
-gb.forkInProgress = \u6b63\u5728\u6d3e\u751f
-gb.preparingFork = \u6b63\u5728\u4e3a\u60a8\u51c6\u5907\u6d3e\u751f...
-gb.isFork = \u5df2\u6d3e\u751f
-gb.canCreate = \u5141\u8bb8\u521b\u5efa
-gb.canCreateDescription = \u5141\u8bb8\u521b\u5efa\u79c1\u4eba\u7248\u672c\u5e93
-gb.illegalPersonalRepositoryLocation = \u60a8\u7684\u79c1\u4eba\u7248\u672c\u5e93\u5fc5\u987b\u4f4d\u4e8e \\"{0}\\"
-gb.verifyCommitter = \u9a8c\u8bc1\u63d0\u4ea4\u8005
-gb.verifyCommitterDescription = \u9700\u8981\u63d0\u4ea4\u8005\u7684\u8eab\u4efd\u4e0e Gitblit \u7528\u6237\u8eab\u4efd\u76f8\u7b26
-gb.verifyCommitterNote = \u6240\u6709\u5408\u5e76\u9009\u9879\u9700\u8981\u4f7f\u7528 \\"--no-ff\\" \u6765\u6267\u884c\u63d0\u4ea4\u8005\u9a8c\u8bc1
-gb.repositoryPermissions = \u7248\u672c\u5e93\u6743\u9650
-gb.userPermissions = \u7528\u6237\u6743\u9650
-gb.teamPermissions = \u56e2\u961f\u6743\u9650
-gb.add = \u6dfb\u52a0
-gb.noPermission = \u5220\u9664\u6b64\u6743\u9650
-gb.excludePermission = {0} (exclude)
-gb.viewPermission = {0} (view)
-gb.clonePermission = {0} (clone)
-gb.pushPermission = {0} (push)
-gb.createPermission = {0} (push, ref creation)
-gb.deletePermission = {0} (push, ref creation+deletion)
-gb.rewindPermission = {0} (push, ref creation+deletion+rewind)
-gb.permission = \u6743\u9650
-gb.regexPermission = \u6b64\u6743\u9650\u662f\u901a\u8fc7\u6b63\u5219\u8868\u8fbe\u5f0f \\"{0}\\" \u8bbe\u7f6e
-gb.accessDenied = \u8bbf\u95ee\u88ab\u62d2\u7edd
-gb.busyCollectingGarbage = \u62b1\u6b49\uff0cGitblit\u6b63\u5728 {0} \u5185\u6e05\u7406\u5783\u573e
-gb.gcPeriod = GC \u65f6\u95f4
-gb.gcPeriodDescription = \u5783\u573e\u6e05\u7406\u7684\u6301\u7eed\u65f6\u95f4
-gb.gcThreshold = GC \u9600\u503c
-gb.gcThresholdDescription = \u6fc0\u53d1\u5783\u573e\u6e05\u7406\u7684\u6700\u5c0f objects \u5927\u5c0f
-gb.ownerPermission = \u7248\u672c\u5e93\u521b\u5efa\u8005
-gb.administrator = \u7ba1\u7406\u5458
-gb.administratorPermission = Gitblit \u7ba1\u7406\u5458
-gb.team = \u56e2\u961f
-gb.teamPermission = \u901a\u8fc7 \\"{0}\\" \u56e2\u961f\u6210\u5458\u8bbe\u7f6e\u6743\u9650
-gb.missing = \u4e0d\u5b58\u5728!
-gb.missingPermission = \u6b64\u6743\u9650\u7684\u7248\u672c\u5e93\u4e0d\u5b58\u5728!
-gb.mutable = mutable
-gb.specified = specified
-gb.effective = effective
-gb.organizationalUnit = \u7ec4\u7ec7\u90e8\u5206
-gb.organization = \u7ec4\u7ec7
-gb.locality = \u5730\u533a
-gb.stateProvince = \u5dde\u6216\u7701
-gb.countryCode = \u56fd\u5bb6\u4ee3\u7801
-gb.properties = \u5c5e\u6027
-gb.issued = issued
-gb.expires = \u5230\u671f
-gb.expired = \u5df2\u5230\u671f
-gb.expiring = \u5373\u5c06\u8fc7\u671f
-gb.revoked = \u5df2\u64a4\u9500
-gb.serialNumber = \u5e8f\u5217\u53f7
-gb.certificates = \u8bc1\u4e66
-gb.newCertificate = \u521b\u5efa\u8bc1\u4e66
-gb.revokeCertificate = \u64a4\u9500\u8bc1\u4e66
-gb.sendEmail = \u53d1\u9001\u90ae\u4ef6
-gb.passwordHint = \u5bc6\u7801\u63d0\u793a
-gb.ok = \u786e\u5b9a
-gb.invalidExpirationDate = \u65e0\u6548\u7684\u8fc7\u671f\u65f6\u95f4!
-gb.passwordHintRequired = \u9700\u8981\u586b\u5199\u5bc6\u7801\u63d0\u793a!
-gb.viewCertificate = \u67e5\u770b\u8bc1\u4e66
-gb.subject = \u4e3b\u9898
-gb.issuer = \u63d0\u4ea4\u8005
-gb.validFrom = \u6709\u6548\u671f\u5f00\u59cb\u81ea
-gb.validUntil = \u6709\u6548\u671f\u622a\u6b62\u4e8e
-gb.publicKey = \u516c\u94a5
-gb.signatureAlgorithm = \u7b7e\u540d\u7b97\u6cd5
-gb.sha1FingerPrint = SHA-1 \u6307\u7eb9\u7b97\u6cd5
-gb.md5FingerPrint = MD5 \u6307\u7eb9\u7b97\u6cd5
-gb.reason = \u7406\u7531
-gb.revokeCertificateReason = \u8bf7\u9009\u62e9\u64a4\u9500\u8bc1\u4e66\u7684\u7406\u7531
-gb.unspecified = \u672a\u6307\u5b9a
-gb.keyCompromise = key compromise
-gb.caCompromise = CA compromise
-gb.affiliationChanged = \u96b6\u5c5e\u5173\u7cfb\u5df2\u4fee\u6539
-gb.superseded = \u5df2\u53d6\u4ee3
-gb.cessationOfOperation = \u505c\u6b62\u64cd\u4f5c
-gb.privilegeWithdrawn = \u7279\u6743\u5df2\u64a4\u56de
-gb.time.inMinutes = {0} \u5206\u949f\u4e4b\u5185
-gb.time.inHours = {0} \u5c0f\u65f6\u4e4b\u5185
-gb.time.inDays = {0} \u5929\u4e4b\u5185
-gb.hostname = hostname
-gb.hostnameRequired = \u8bf7\u8f93\u5165 hostname
-gb.newSSLCertificate = \u521b\u5efa\u670d\u52a1\u5668 SSL \u8bc1\u4e66
-gb.newCertificateDefaults = \u521b\u5efa\u8bc1\u4e66\u9ed8\u8ba4\u8bbe\u7f6e
-gb.duration = \u6301\u7eed\u65f6\u95f4
-gb.certificateRevoked = \u8bc1\u4e66 {0,number,0} \u5df2\u88ab\u64a4\u9500
-gb.clientCertificateGenerated = \u6210\u529f\u4e3a {0} \u751f\u6210\u65b0\u7684\u5ba2\u6237\u7aef\u8bc1\u4e66
-gb.sslCertificateGenerated = \u6210\u529f\u4e3a {0} \u751f\u6210\u65b0\u7684\u670d\u52a1\u5668 SSL \u8bc1\u4e66
-gb.newClientCertificateMessage = \u6ce8\u610f:\\n\u6b64\u5bc6\u7801\u5e76\u975e\u7528\u6237\u5bc6\u7801, \u8fd9\u662f\u4fdd\u5b58\u7528\u6237 keystore \u7684\u5bc6\u7801\u3002  \u7531\u4e8e\u672c\u5bc6\u7801\u672a\u5b58\u50a8\uff0c\u56e0\u6b64\u4f60\u5fc5\u987b\u4e00\u4e2a\u5bc6\u7801\u63d0\u793a\uff0c\u8fd9\u4e2a\u63d0\u793a\u4f1a\u8bb0\u5f55\u5728\u7528\u6237\u7684 README \u6587\u6863\u5185\u3002
-gb.certificate = \u8bc1\u4e66
-gb.emailCertificateBundle = \u53d1\u9001\u5ba2\u6237\u7aef\u8bc1\u4e66
-gb.pleaseGenerateClientCertificate = \u8bf7\u4e3a {0} \u751f\u6210\u4e00\u4e2a\u5ba2\u6237\u7aef\u8bc1\u4e66
-gb.clientCertificateBundleSent = {0} \u7684\u5ba2\u6237\u7aef\u8bc1\u4e66\u5df2\u53d1\u9001
-gb.enterKeystorePassword = \u8bf7\u8f93\u5165 Gitblit keystore \u5bc6\u7801
-gb.warning = \u8b66\u544a
-gb.jceWarning = \u60a8\u7684 JAVA \u8fd0\u884c\u73af\u5883\u4e0d\u5305\u542b \\"JCE Unlimited Strength Jurisdiction Policy\\" \u6587\u4ef6\u3002\\n\u8fd9\u5c06\u5bfc\u81f4\u60a8\u6700\u591a\u53ea\u80fd\u75287\u4e2a\u5b57\u7b26\u7684\u5bc6\u7801\u4fdd\u62a4\u60a8\u7684 keystore\u3002 \\n\u8fd9\u4e9b\u662f\u4e00\u4e9b\u53ef\u9009\u4e0b\u8f7d\u7684\u653f\u7b56\u6587\u4ef6\u3002\\n\\n\u4f60\u662f\u5426\u8981\u7ee7\u7eed\u751f\u6210\u8bc1\u4e66\uff1f\\n\\n\u9009\u62e9\u5426\u7684\u8bdd\uff0c\u5c06\u4f1a\u6253\u5f00\u4e00\u4e2a\u6d4f\u89c8\u5668\u754c\u9762\u4f9b\u60a8\u4e0b\u8f7d\u76f8\u5173\u6587\u4ef6\u3002
-gb.maxActivityCommits = \u6700\u5927\u6d3b\u52a8\u63d0\u4ea4\u6570
-gb.maxActivityCommitsDescription = \u6d3b\u52a8\u9875\u9762\u663e\u793a\u7684\u6700\u5927\u63d0\u4ea4\u6570
-gb.noMaximum = \u65e0\u4e0a\u9650
-gb.attributes = \u5c5e\u6027
-gb.serveCertificate = \u4f7f\u7528\u6b64\u8bc1\u4e66\u63d0\u4f9b https \u652f\u6301
-gb.sslCertificateGeneratedRestart = \u6210\u529f\u4e3a {0} \u751f\u6210\u65b0\u7684 SSL \u8bc1\u4e66.\\n\u4f60\u5fc5\u987b\u91cd\u65b0\u542f\u52a8 Gitblit \u4ee5\u4f7f\u7528\u6b64\u8bc1\u4e66\u3002\\n\\n\u5982\u679c\u60a8\u4f7f\u7528 '--alias' \u53c2\u6570\u542f\u52a8\uff0c\u4f60\u5fc5\u987b\u4e5f\u8981\u8bbe\u7f6e ''--alias {0}''\u3002
-gb.validity = \u5408\u6cd5\u6027
-gb.siteName = \u7f51\u7ad9\u540d\u79f0
-gb.siteNameDescription = \u60a8\u7684\u670d\u52a1\u5668\u7684\u7b80\u8981\u63cf\u8ff0
-gb.excludeFromActivity = \u4ece\u6d3b\u52a8\u9875\u9762\u6392\u9664
-gb.isSparkleshared = repository is Sparkleshared
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/GitBlitWebSession.java b/src/com/gitblit/wicket/GitBlitWebSession.java
deleted file mode 100644
index 5195a1f..0000000
--- a/src/com/gitblit/wicket/GitBlitWebSession.java
+++ /dev/null
@@ -1,157 +0,0 @@
-/*
- * 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.wicket;
-
-import java.util.Map;
-import java.util.TimeZone;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-import org.apache.wicket.Page;
-import org.apache.wicket.PageParameters;
-import org.apache.wicket.RedirectToUrlException;
-import org.apache.wicket.Request;
-import org.apache.wicket.Session;
-import org.apache.wicket.protocol.http.RequestUtils;
-import org.apache.wicket.protocol.http.WebRequestCycle;
-import org.apache.wicket.protocol.http.WebSession;
-import org.apache.wicket.protocol.http.request.WebClientInfo;
-
-import com.gitblit.Constants.AuthenticationType;
-import com.gitblit.models.UserModel;
-
-public final class GitBlitWebSession extends WebSession {
-
-	private static final long serialVersionUID = 1L;
-
-	protected TimeZone timezone;
-
-	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;
-	}
-
-	public void invalidate() {
-		super.invalidate();
-		user = null;
-	}
-	
-	/**
-	 * Cache the requested protected resource pending successful authentication.
-	 * 
-	 * @param pageClass
-	 */
-	public void cacheRequest(Class<? extends Page> pageClass) {
-		// build absolute url with correctly encoded parameters?!
-		Request req = WebRequestCycle.get().getRequest();
-		Map<String, ?> params = req.getRequestParameters().getParameters();
-		PageParameters pageParams = new PageParameters(params);
-		String relativeUrl = WebRequestCycle.get().urlFor(pageClass, pageParams).toString();
-		requestUrl = RequestUtils.toAbsolutePath(relativeUrl);
-		if (isTemporary())
-		{
-			// we must bind the temporary session into the session store
-			// so that we can re-use this session for reporting an error message
-			// on the redirected page and continuing the request after
-			// authentication.
-			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() {
-		if (requestUrl != null) {
-			String url = requestUrl;
-			requestUrl = null;
-			throw new RedirectToUrlException(url);
-		}
-		return false;
-	}
-
-	public boolean isLoggedIn() {
-		return user != null;
-	}
-
-	public boolean canAdmin() {
-		if (user == null) {
-			return false;
-		}
-		return user.canAdmin();
-	}
-	
-	public String getUsername() {
-		return user == null ? "anonymous" : user.username;
-	}
-
-	public UserModel getUser() {
-		return user;
-	}
-
-	public void setUser(UserModel user) {
-		this.user = user;
-	}
-
-	public TimeZone getTimezone() {
-		if (timezone == null) {
-			timezone = ((WebClientInfo) getClientInfo()).getProperties().getTimeZone();
-		}
-		// use server timezone if we can't determine the client timezone
-		if (timezone == null) {
-			timezone = TimeZone.getDefault();
-		}
-		return timezone;
-	}
-
-	public void cacheErrorMessage(String message) {
-		this.errorMessage = message;
-	}
-
-	public String clearErrorMessage() {
-		String msg = errorMessage;
-		errorMessage = null;
-		return msg;
-	}
-	
-	public boolean isForking() {
-		return isForking.get();
-	}
-	
-	public void isForking(boolean val) {
-		isForking.set(val);
-	}
-
-	public static GitBlitWebSession get() {
-		return (GitBlitWebSession) Session.get();
-	}
-}
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/PageRegistration.java b/src/com/gitblit/wicket/PageRegistration.java
deleted file mode 100644
index e8eeaba..0000000
--- a/src/com/gitblit/wicket/PageRegistration.java
+++ /dev/null
@@ -1,215 +0,0 @@
-/*
- * 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.wicket;
-
-import java.io.Serializable;
-import java.util.ArrayList;
-import java.util.List;
-
-import org.apache.wicket.PageParameters;
-import org.apache.wicket.markup.html.WebPage;
-
-import com.gitblit.utils.StringUtils;
-
-/**
- * Represents a page link registration for the topbar.
- * 
- * @author James Moger
- * 
- */
-public class PageRegistration implements Serializable {
-	private static final long serialVersionUID = 1L;
-
-	public final String translationKey;
-	public final Class<? extends WebPage> pageClass;
-	public final PageParameters params;
-
-	public PageRegistration(String translationKey, Class<? extends WebPage> pageClass) {
-		this(translationKey, pageClass, null);
-	}
-
-	public PageRegistration(String translationKey, Class<? extends WebPage> pageClass,
-			PageParameters params) {
-		this.translationKey = translationKey;
-		this.pageClass = pageClass;
-		this.params = params;
-	}
-
-	/**
-	 * Represents a page link to a non-Wicket page. Might be external.
-	 * 
-	 * @author James Moger
-	 * 
-	 */
-	public static class OtherPageLink extends PageRegistration {
-
-		private static final long serialVersionUID = 1L;
-
-		public final String url;
-
-		public OtherPageLink(String translationKey, String url) {
-			super(translationKey, null);
-			this.url = url;
-		}
-	}
-
-	/**
-	 * Represents a DropDownMenu for the topbar
-	 * 
-	 * @author James Moger
-	 * 
-	 */
-	public static class DropDownMenuRegistration extends PageRegistration {
-
-		private static final long serialVersionUID = 1L;
-
-		public final List<DropDownMenuItem> menuItems;
-
-		public DropDownMenuRegistration(String translationKey, Class<? extends WebPage> pageClass) {
-			super(translationKey, pageClass);
-			menuItems = new ArrayList<DropDownMenuItem>();
-		}
-	}
-
-	/**
-	 * A MenuItem for the DropDownMenu.
-	 * 
-	 * @author James Moger
-	 * 
-	 */
-	public static class DropDownMenuItem implements Serializable {
-
-		private static final long serialVersionUID = 1L;
-
-		final PageParameters parameters;
-		final String displayText;
-		final String parameter;
-		final String value;
-		final boolean isSelected;
-
-		/**
-		 * Divider constructor.
-		 */
-		public DropDownMenuItem() {
-			this(null, null, null, null);
-		}
-
-		/**
-		 * Standard Menu Item constructor.
-		 * 
-		 * @param displayText
-		 * @param parameter
-		 * @param value
-		 */
-		public DropDownMenuItem(String displayText, String parameter, String value) {
-			this(displayText, parameter, value, null);
-		}
-
-		/**
-		 * Standard Menu Item constructor that preserves aggregate parameters.
-		 * 
-		 * @param displayText
-		 * @param parameter
-		 * @param value
-		 */
-		public DropDownMenuItem(String displayText, String parameter, String value,
-				PageParameters params) {
-			this.displayText = displayText;
-			this.parameter = parameter;
-			this.value = value;
-
-			if (params == null) {
-				// no parameters specified
-				parameters = new PageParameters();
-				setParameter(parameter, value);
-				isSelected = false;
-			} else {
-				parameters = new PageParameters(params);
-				if (parameters.containsKey(parameter)) {
-					isSelected = params.getString(parameter).equals(value);
-					if (isSelected) {
-						// already selected, so remove this enables toggling
-						parameters.remove(parameter);
-					} else {
-						// set the new selection value
-						setParameter(parameter, value);
-					}
-				} else {
-					// not currently selected
-					isSelected = false;
-					setParameter(parameter, value);
-				}
-			}
-		}
-
-		private void setParameter(String parameter, String value) {
-			if (!StringUtils.isEmpty(parameter)) {
-				if (StringUtils.isEmpty(value)) {
-					this.parameters.remove(parameter);
-				} else {
-					this.parameters.put(parameter, value);
-				}
-			}
-		}
-
-		public String formatParameter() {
-			if (StringUtils.isEmpty(parameter) || StringUtils.isEmpty(value)) {
-				return "";
-			}
-			return parameter + "=" + value;
-		}
-
-		public PageParameters getPageParameters() {
-			return parameters;
-		}
-
-		public boolean isDivider() {
-			return displayText == null && value == null && parameter == null;
-		}
-
-		public boolean isSelected() {
-			return isSelected;
-		}
-
-		@Override
-		public int hashCode() {
-			if (isDivider()) {
-				// divider menu item
-				return super.hashCode();
-			}
-			if (StringUtils.isEmpty(displayText)) {
-				return value.hashCode() + parameter.hashCode();
-			}
-			return displayText.hashCode();
-		}
-
-		@Override
-		public boolean equals(Object o) {
-			if (o instanceof DropDownMenuItem) {
-				return hashCode() == o.hashCode();
-			}
-			return false;
-		}
-
-		@Override
-		public String toString() {
-			if (StringUtils.isEmpty(displayText)) {
-				return formatParameter();
-			}
-			return displayText;
-		}
-	}
-}
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/WicketUtils.java b/src/com/gitblit/wicket/WicketUtils.java
deleted file mode 100644
index e4eb29f..0000000
--- a/src/com/gitblit/wicket/WicketUtils.java
+++ /dev/null
@@ -1,601 +0,0 @@
-/*
- * 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.wicket;
-
-import java.text.DateFormat;
-import java.text.MessageFormat;
-import java.text.SimpleDateFormat;
-import java.util.Collection;
-import java.util.Date;
-import java.util.List;
-import java.util.TimeZone;
-
-import javax.servlet.http.HttpServletRequest;
-
-import org.apache.wicket.Component;
-import org.apache.wicket.PageParameters;
-import org.apache.wicket.Request;
-import org.apache.wicket.behavior.HeaderContributor;
-import org.apache.wicket.behavior.SimpleAttributeModifier;
-import org.apache.wicket.markup.html.IHeaderContributor;
-import org.apache.wicket.markup.html.IHeaderResponse;
-import org.apache.wicket.markup.html.basic.Label;
-import org.apache.wicket.markup.html.image.ContextImage;
-import org.apache.wicket.protocol.http.WebRequest;
-import org.apache.wicket.resource.ContextRelativeResource;
-import org.eclipse.jgit.diff.DiffEntry.ChangeType;
-import org.wicketstuff.googlecharts.AbstractChartData;
-import org.wicketstuff.googlecharts.IChartData;
-
-import com.gitblit.Constants;
-import com.gitblit.Constants.FederationPullStatus;
-import com.gitblit.GitBlit;
-import com.gitblit.Keys;
-import com.gitblit.models.FederationModel;
-import com.gitblit.models.Metric;
-import com.gitblit.utils.HttpUtils;
-import com.gitblit.utils.StringUtils;
-import com.gitblit.utils.TimeUtils;
-
-public class WicketUtils {
-
-	public static void setCssClass(Component container, String value) {
-		container.add(new SimpleAttributeModifier("class", value));
-	}
-
-	public static void setCssStyle(Component container, String value) {
-		container.add(new SimpleAttributeModifier("style", value));
-	}
-
-	public static void setCssBackground(Component container, String value) {
-		String background = MessageFormat.format("background-color:{0};",
-				StringUtils.getColor(value));
-		container.add(new SimpleAttributeModifier("style", background));
-	}
-
-	public static void setHtmlTooltip(Component container, String value) {
-		container.add(new SimpleAttributeModifier("title", value));
-	}
-
-	public static void setInputPlaceholder(Component container, String value) {
-		container.add(new SimpleAttributeModifier("placeholder", value));
-	}
-
-	public static void setChangeTypeCssClass(Component container, ChangeType type) {
-		switch (type) {
-		case ADD:
-			setCssClass(container, "addition");
-			break;
-		case COPY:
-		case RENAME:
-			setCssClass(container, "rename");
-			break;
-		case DELETE:
-			setCssClass(container, "deletion");
-			break;
-		case MODIFY:
-			setCssClass(container, "modification");
-			break;
-		}
-	}
-
-	public static void setTicketCssClass(Component container, String state) {
-		String css = null;
-		if (state.equals("open")) {
-			css = "label label-important";
-		} else if (state.equals("hold")) {
-			css = "label label-warning";
-		} else if (state.equals("resolved")) {
-			css = "label label-success";
-		} else if (state.equals("invalid")) {
-			css = "label";
-		}
-		if (css != null) {
-			setCssClass(container, css);
-		}
-	}
-
-	public static void setAlternatingBackground(Component c, int i) {
-		String clazz = i % 2 == 0 ? "light" : "dark";
-		setCssClass(c, clazz);
-	}
-
-	public static Label createAuthorLabel(String wicketId, String author) {
-		Label label = new Label(wicketId, author);
-		WicketUtils.setHtmlTooltip(label, author);
-		return label;
-	}
-
-	public static ContextImage getPullStatusImage(String wicketId, FederationPullStatus status) {
-		String filename = null;
-		switch (status) {
-		case MIRRORED:
-		case PULLED:
-			filename = "bullet_green.png";
-			break;
-		case SKIPPED:
-			filename = "bullet_yellow.png";
-			break;
-		case FAILED:
-			filename = "bullet_red.png";
-			break;
-		case EXCLUDED:
-			filename = "bullet_white.png";
-			break;
-		case PENDING:
-		case NOCHANGE:
-		default:
-			filename = "bullet_black.png";
-		}
-		return WicketUtils.newImage(wicketId, filename, status.name());
-	}
-
-	public static ContextImage getFileImage(String wicketId, String filename) {
-		filename = filename.toLowerCase();
-		if (filename.endsWith(".java")) {
-			return newImage(wicketId, "file_java_16x16.png");
-		} else if (filename.endsWith(".rb")) {
-			return newImage(wicketId, "file_ruby_16x16.png");
-		} else if (filename.endsWith(".php")) {
-			return newImage(wicketId, "file_php_16x16.png");
-		} else if (filename.endsWith(".cs")) {
-			return newImage(wicketId, "file_cs_16x16.png");
-		} else if (filename.endsWith(".cpp")) {
-			return newImage(wicketId, "file_cpp_16x16.png");
-		} else if (filename.endsWith(".c")) {
-			return newImage(wicketId, "file_c_16x16.png");
-		} else if (filename.endsWith(".h")) {
-			return newImage(wicketId, "file_h_16x16.png");
-		} else if (filename.endsWith(".sln")) {
-			return newImage(wicketId, "file_vs_16x16.png");
-		} else if (filename.endsWith(".csv") || filename.endsWith(".xls")
-				|| filename.endsWith(".xlsx")) {
-			return newImage(wicketId, "file_excel_16x16.png");
-		} else if (filename.endsWith(".doc") || filename.endsWith(".docx")) {
-			return newImage(wicketId, "file_word_16x16.png");
-		} else if (filename.endsWith(".ppt")) {
-			return newImage(wicketId, "file_ppt_16x16.png");
-		} else if (filename.endsWith(".zip")) {
-			return newImage(wicketId, "file_zip_16x16.png");
-		} else if (filename.endsWith(".pdf")) {
-			return newImage(wicketId, "file_acrobat_16x16.png");
-		} else if (filename.endsWith(".htm") || filename.endsWith(".html")) {
-			return newImage(wicketId, "file_world_16x16.png");
-		} else if (filename.endsWith(".xml")) {
-			return newImage(wicketId, "file_code_16x16.png");
-		} else if (filename.endsWith(".properties")) {
-			return newImage(wicketId, "file_settings_16x16.png");
-		}
-
-		List<String> mdExtensions = GitBlit.getStrings(Keys.web.markdownExtensions);
-		for (String ext : mdExtensions) {
-			if (filename.endsWith('.' + ext.toLowerCase())) {
-				return newImage(wicketId, "file_world_16x16.png");
-			}
-		}
-		return newImage(wicketId, "file_16x16.png");
-	}
-
-	public static ContextImage getRegistrationImage(String wicketId, FederationModel registration,
-			Component c) {
-		if (registration.isResultData()) {
-			return WicketUtils.newImage(wicketId, "information_16x16.png",
-					c.getString("gb.federationResults"));
-		} else {
-			return WicketUtils.newImage(wicketId, "arrow_left.png",
-					c.getString("gb.federationRegistration"));
-		}
-	}
-
-	public static ContextImage newClearPixel(String wicketId) {
-		return newImage(wicketId, "pixel.png");
-	}
-
-	public static ContextImage newBlankImage(String wicketId) {
-		return newImage(wicketId, "blank.png");
-	}
-
-	public static ContextImage newImage(String wicketId, String file) {
-		return newImage(wicketId, file, null);
-	}
-
-	public static ContextImage newImage(String wicketId, String file, String tooltip) {
-		ContextImage img = new ContextImage(wicketId, file);
-		if (!StringUtils.isEmpty(tooltip)) {
-			setHtmlTooltip(img, tooltip);
-		}
-		return img;
-	}
-
-	public static Label newIcon(String wicketId, String css) {
-		Label lbl = new Label(wicketId);
-		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);
-	}
-
-	public static String getGitblitURL(Request request) {
-		HttpServletRequest req = ((WebRequest) request).getHttpServletRequest();
-		return HttpUtils.getGitblitURL(req);
-	}
-
-	public static HeaderContributor syndicationDiscoveryLink(final String feedTitle,
-			final String url) {
-		return new HeaderContributor(new IHeaderContributor() {
-			private static final long serialVersionUID = 1L;
-
-			public void renderHead(IHeaderResponse response) {
-				String contentType = "application/rss+xml";
-
-				StringBuilder buffer = new StringBuilder();
-				buffer.append("<link rel=\"alternate\" ");
-				buffer.append("type=\"").append(contentType).append("\" ");
-				buffer.append("title=\"").append(feedTitle).append("\" ");
-				buffer.append("href=\"").append(url).append("\" />");
-				response.renderString(buffer.toString());
-			}
-		});
-	}
-
-	public static PageParameters newTokenParameter(String token) {
-		return new PageParameters("t=" + token);
-	}
-
-	public static PageParameters newRegistrationParameter(String url, String name) {
-		return new PageParameters("u=" + url + ",n=" + name);
-	}
-
-	public static PageParameters newUsernameParameter(String username) {
-		return new PageParameters("user=" + username);
-	}
-
-	public static PageParameters newTeamnameParameter(String teamname) {
-		return new PageParameters("team=" + teamname);
-	}
-
-	public static PageParameters newProjectParameter(String projectName) {
-		return new PageParameters("p=" + projectName);
-	}
-
-	public static PageParameters newRepositoryParameter(String repositoryName) {
-		return new PageParameters("r=" + repositoryName);
-	}
-
-	public static PageParameters newObjectParameter(String objectId) {
-		return new PageParameters("h=" + objectId);
-	}
-
-	public static PageParameters newObjectParameter(String repositoryName, String objectId) {
-		if (StringUtils.isEmpty(objectId)) {
-			return newRepositoryParameter(repositoryName);
-		}
-		return new PageParameters("r=" + repositoryName + ",h=" + objectId);
-	}
-
-	public static PageParameters newPathParameter(String repositoryName, String objectId,
-			String path) {
-		if (StringUtils.isEmpty(path)) {
-			return newObjectParameter(repositoryName, objectId);
-		}
-		if (StringUtils.isEmpty(objectId)) {
-			return new PageParameters("r=" + repositoryName + ",f=" + path);
-		}
-		return new PageParameters("r=" + repositoryName + ",h=" + objectId + ",f=" + path);
-	}
-
-	public static PageParameters newLogPageParameter(String repositoryName, String objectId,
-			int pageNumber) {
-		if (pageNumber <= 1) {
-			return newObjectParameter(repositoryName, objectId);
-		}
-		if (StringUtils.isEmpty(objectId)) {
-			return new PageParameters("r=" + repositoryName + ",pg=" + pageNumber);
-		}
-		return new PageParameters("r=" + repositoryName + ",h=" + objectId + ",pg=" + pageNumber);
-	}
-
-	public static PageParameters newHistoryPageParameter(String repositoryName, String objectId,
-			String path, int pageNumber) {
-		if (pageNumber <= 1) {
-			return newObjectParameter(repositoryName, objectId);
-		}
-		if (StringUtils.isEmpty(objectId)) {
-			return new PageParameters("r=" + repositoryName + ",f=" + path + ",pg=" + pageNumber);
-		}
-		return new PageParameters("r=" + repositoryName + ",h=" + objectId + ",f=" + path + ",pg="
-				+ pageNumber);
-	}
-
-	public static PageParameters newBlobDiffParameter(String repositoryName, String baseCommitId,
-			String commitId, String path) {
-		if (StringUtils.isEmpty(commitId)) {
-			return new PageParameters("r=" + repositoryName + ",f=" + path + ",hb=" + baseCommitId);
-		}
-		return new PageParameters("r=" + repositoryName + ",h=" + commitId + ",f=" + path + ",hb="
-				+ baseCommitId);
-	}
-
-	public static PageParameters newSearchParameter(String repositoryName, String commitId,
-			String search, Constants.SearchType type) {
-		if (StringUtils.isEmpty(commitId)) {
-			return new PageParameters("r=" + repositoryName + ",s=" + search + ",st=" + type.name());
-		}
-		return new PageParameters("r=" + repositoryName + ",h=" + commitId + ",s=" + search
-				+ ",st=" + type.name());
-	}
-
-	public static PageParameters newSearchParameter(String repositoryName, String commitId,
-			String search, Constants.SearchType type, int pageNumber) {
-		if (StringUtils.isEmpty(commitId)) {
-			return new PageParameters("r=" + repositoryName + ",s=" + search + ",st=" + type.name()
-					+ ",pg=" + pageNumber);
-		}
-		return new PageParameters("r=" + repositoryName + ",h=" + commitId + ",s=" + search
-				+ ",st=" + type.name() + ",pg=" + pageNumber);
-	}
-
-	public static String getProjectName(PageParameters params) {
-		return params.getString("p", "");
-	}
-
-	public static String getRepositoryName(PageParameters params) {
-		return params.getString("r", "");
-	}
-
-	public static String getObject(PageParameters params) {
-		return params.getString("h", null);
-	}
-
-	public static String getPath(PageParameters params) {
-		return params.getString("f", null);
-	}
-
-	public static String getBaseObjectId(PageParameters params) {
-		return params.getString("hb", null);
-	}
-
-	public static String getSearchString(PageParameters params) {
-		return params.getString("s", null);
-	}
-
-	public static String getSearchType(PageParameters params) {
-		return params.getString("st", null);
-	}
-
-	public static int getPage(PageParameters params) {
-		// index from 1
-		return params.getInt("pg", 1);
-	}
-
-	public static String getRegEx(PageParameters params) {
-		return params.getString("x", "");
-	}
-
-	public static String getSet(PageParameters params) {
-		return params.getString("set", "");
-	}
-
-	public static String getTeam(PageParameters params) {
-		return params.getString("team", "");
-	}
-
-	public static int getDaysBack(PageParameters params) {
-		return params.getInt("db", 14);
-	}
-
-	public static String getUsername(PageParameters params) {
-		return params.getString("user", "");
-	}
-
-	public static String getTeamname(PageParameters params) {
-		return params.getString("team", "");
-	}
-
-	public static String getToken(PageParameters params) {
-		return params.getString("t", "");
-	}
-
-	public static String getUrlParameter(PageParameters params) {
-		return params.getString("u", "");
-	}
-
-	public static String getNameParameter(PageParameters params) {
-		return params.getString("n", "");
-	}
-
-	public static Label createDateLabel(String wicketId, Date date, TimeZone timeZone, TimeUtils timeUtils) {
-		String format = GitBlit.getString(Keys.web.datestampShortFormat, "MM/dd/yy");
-		DateFormat df = new SimpleDateFormat(format);
-		if (timeZone == null) {
-			timeZone = GitBlit.getTimezone();
-		}
-		df.setTimeZone(timeZone);
-		String dateString;
-		if (date.getTime() == 0) {
-			dateString = "--";
-		} else {
-			dateString = df.format(date);
-		}
-		String title = null;
-		if (date.getTime() <= System.currentTimeMillis()) {
-			// past
-			title = timeUtils.timeAgo(date);
-		}
-		if ((System.currentTimeMillis() - date.getTime()) < 10 * 24 * 60 * 60 * 1000L) {
-			String tmp = dateString;
-			dateString = title;
-			title = tmp;
-		}
-		Label label = new Label(wicketId, dateString);
-		WicketUtils.setCssClass(label, timeUtils.timeAgoCss(date));
-		if (!StringUtils.isEmpty(title)) {
-			WicketUtils.setHtmlTooltip(label, title);
-		}
-		return label;
-	}
-
-	public static Label createTimeLabel(String wicketId, Date date, TimeZone timeZone, TimeUtils timeUtils) {
-		String format = GitBlit.getString(Keys.web.timeFormat, "HH:mm");
-		DateFormat df = new SimpleDateFormat(format);
-		if (timeZone == null) {
-			timeZone = GitBlit.getTimezone();
-		}
-		df.setTimeZone(timeZone);
-		String timeString;
-		if (date.getTime() == 0) {
-			timeString = "--";
-		} else {
-			timeString = df.format(date);
-		}
-		String title = timeUtils.timeAgo(date);
-		Label label = new Label(wicketId, timeString);
-		if (!StringUtils.isEmpty(title)) {
-			WicketUtils.setHtmlTooltip(label, title);
-		}
-		return label;
-	}
-
-	public static Label createDatestampLabel(String wicketId, Date date, TimeZone timeZone, TimeUtils timeUtils) {
-		String format = GitBlit.getString(Keys.web.datestampLongFormat, "EEEE, MMMM d, yyyy");
-		DateFormat df = new SimpleDateFormat(format);
-		if (timeZone == null) {
-			timeZone = GitBlit.getTimezone();
-		}
-		df.setTimeZone(timeZone);
-		String dateString;
-		if (date.getTime() == 0) {
-			dateString = "--";
-		} else {
-			dateString = df.format(date);
-		}
-		String title = null;
-		if (TimeUtils.isToday(date)) {
-			title = timeUtils.today();
-		} else if (TimeUtils.isYesterday(date)) {
-				title = timeUtils.yesterday();
-		} else if (date.getTime() <= System.currentTimeMillis()) {
-			// past
-			title = timeUtils.timeAgo(date);
-		}
-		if ((System.currentTimeMillis() - date.getTime()) < 10 * 24 * 60 * 60 * 1000L) {
-			String tmp = dateString;
-			dateString = title;
-			title = tmp;
-		}
-		Label label = new Label(wicketId, dateString);
-		if (!StringUtils.isEmpty(title)) {
-			WicketUtils.setHtmlTooltip(label, title);
-		}
-		return label;
-	}
-
-	public static Label createTimestampLabel(String wicketId, Date date, TimeZone timeZone, TimeUtils timeUtils) {
-		String format = GitBlit.getString(Keys.web.datetimestampLongFormat,
-				"EEEE, MMMM d, yyyy HH:mm Z");
-		DateFormat df = new SimpleDateFormat(format);
-		if (timeZone == null) {
-			timeZone = GitBlit.getTimezone();
-		}
-		df.setTimeZone(timeZone);
-		String dateString;
-		if (date.getTime() == 0) {
-			dateString = "--";
-		} else {
-			dateString = df.format(date);
-		}
-		String title = null;
-		if (date.getTime() <= System.currentTimeMillis()) {
-			// past
-			title = timeUtils.timeAgo(date);
-		}
-		Label label = new Label(wicketId, dateString);
-		if (!StringUtils.isEmpty(title)) {
-			WicketUtils.setHtmlTooltip(label, title);
-		}
-		return label;
-	}
-
-	public static IChartData getChartData(Collection<Metric> metrics) {
-		final double[] commits = new double[metrics.size()];
-		final double[] tags = new double[metrics.size()];
-		int i = 0;
-		double max = 0;
-		for (Metric m : metrics) {
-			commits[i] = m.count;
-			if (m.tag > 0) {
-				tags[i] = m.count;
-			} else {
-				tags[i] = -1d;
-			}
-			max = Math.max(max, m.count);
-			i++;
-		}
-		IChartData data = new AbstractChartData(max) {
-			private static final long serialVersionUID = 1L;
-
-			public double[][] getData() {
-				return new double[][] { commits, tags };
-			}
-		};
-		return data;
-	}
-
-	public static double maxValue(Collection<Metric> metrics) {
-		double max = Double.MIN_VALUE;
-		for (Metric m : metrics) {
-			if (m.count > max) {
-				max = m.count;
-			}
-		}
-		return max;
-	}
-
-	public static IChartData getScatterData(Collection<Metric> metrics) {
-		final double[] y = new double[metrics.size()];
-		final double[] x = new double[metrics.size()];
-		int i = 0;
-		double max = 0;
-		for (Metric m : metrics) {
-			y[i] = m.count;
-			if (m.duration > 0) {
-				x[i] = m.duration;
-			} else {
-				x[i] = -1d;
-			}
-			max = Math.max(max, m.count);
-			i++;
-		}
-		IChartData data = new AbstractChartData(max) {
-			private static final long serialVersionUID = 1L;
-
-			public double[][] getData() {
-				return new double[][] { x, y };
-			}
-		};
-		return data;
-	}
-
-}
diff --git a/src/com/gitblit/wicket/charting/GoogleChart.java b/src/com/gitblit/wicket/charting/GoogleChart.java
deleted file mode 100644
index b6309ff..0000000
--- a/src/com/gitblit/wicket/charting/GoogleChart.java
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * 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.wicket.charting;
-
-import java.io.Serializable;
-import java.util.ArrayList;
-import java.util.List;
-
-import com.gitblit.utils.StringUtils;
-
-/**
- * Abstract parent class for Google Charts built with the Visualization API.
- * 
- * @author James Moger
- * 
- */
-public abstract class GoogleChart implements Serializable {
-
-	private static final long serialVersionUID = 1L;
-	final String tagId;
-	final String dataName;
-	final String title;
-	final String keyName;
-	final String valueName;
-	final List<ChartValue> values;
-	int width;
-	int height;
-
-	public GoogleChart(String tagId, String title, String keyName, String valueName) {
-		this.tagId = tagId;
-		this.dataName = StringUtils.getSHA1(title).substring(0, 8);
-		this.title = title;
-		this.keyName = keyName;
-		this.valueName = valueName;
-		values = new ArrayList<ChartValue>();
-	}
-
-	public void setWidth(int width) {
-		this.width = width;
-	}
-
-	public void setHeight(int height) {
-		this.height = height;
-	}
-
-	public void addValue(String name, int value) {
-		values.add(new ChartValue(name, value));
-	}
-
-	public void addValue(String name, float value) {
-		values.add(new ChartValue(name, value));
-	}
-
-	public void addValue(String name, double value) {
-		values.add(new ChartValue(name, (float) value));
-	}
-
-	protected abstract void appendChart(StringBuilder sb);
-
-	protected void line(StringBuilder sb, String line) {
-		sb.append(line);
-		sb.append('\n');
-	}
-
-	protected class ChartValue implements Serializable, Comparable<ChartValue> {
-
-		private static final long serialVersionUID = 1L;
-
-		final String name;
-		final float value;
-
-		ChartValue(String name, float value) {
-			this.name = name;
-			this.value = value;
-		}
-
-		@Override
-		public int compareTo(ChartValue o) {
-			// sorts the dataset by largest value first
-			if (value > o.value) {
-				return -1;
-			} else if (value < o.value) {
-				return 1;
-			}
-			return 0;
-		}
-	}
-}
diff --git a/src/com/gitblit/wicket/charting/GooglePieChart.java b/src/com/gitblit/wicket/charting/GooglePieChart.java
deleted file mode 100644
index 119a824..0000000
--- a/src/com/gitblit/wicket/charting/GooglePieChart.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * 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.wicket.charting;
-
-import java.text.MessageFormat;
-import java.util.Collections;
-
-import com.gitblit.utils.StringUtils;
-
-/**
- * Builds an interactive pie chart using the Visualization API.
- * 
- * @author James Moger
- * 
- */
-public class GooglePieChart extends GoogleChart {
-
-	private static final long serialVersionUID = 1L;
-
-	public GooglePieChart(String tagId, String title, String keyName, String valueName) {
-		super(tagId, title, keyName, valueName);
-	}
-
-	@Override
-	protected void appendChart(StringBuilder sb) {
-		// create dataset
-		String dName = "data_" + dataName;
-		line(sb, MessageFormat.format("var {0} = new google.visualization.DataTable();", dName));
-		line(sb, MessageFormat.format("{0}.addColumn(''string'', ''{1}'');", dName, keyName));
-		line(sb, MessageFormat.format("{0}.addColumn(''number'', ''{1}'');", dName, valueName));
-		line(sb, MessageFormat.format("{0}.addRows({1,number,0});", dName, values.size()));
-
-		Collections.sort(values);
-
-		StringBuilder colors = new StringBuilder("colors:[");
-		for (int i = 0; i < values.size(); i++) {
-			ChartValue value = values.get(i);
-			colors.append('\'');
-			colors.append(StringUtils.getColor(value.name));
-			colors.append('\'');
-			if (i < values.size() - 1) {
-				colors.append(',');
-			}
-			line(sb, MessageFormat.format("{0}.setValue({1,number,0}, 0, ''{2}'');", dName, i,
-					value.name));
-			line(sb, MessageFormat.format("{0}.setValue({1,number,0}, 1, {2,number,0.0});", dName,
-					i, value.value));
-		}
-		colors.append(']');
-
-		// instantiate chart
-		String cName = "chart_" + dataName;
-		line(sb, MessageFormat.format(
-				"var {0} = new google.visualization.PieChart(document.getElementById(''{1}''));",
-				cName, tagId));
-		line(sb,
-				MessageFormat
-						.format("{0}.draw({1}, '{'width: {2,number,0}, height: {3,number,0}, chartArea:'{'left:20,top:20'}', title: ''{4}'', {5} '}');",
-								cName, dName, width, height, title, colors.toString()));
-		line(sb, "");
-	}
-}
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/pages/ActivityPage.html b/src/com/gitblit/wicket/pages/ActivityPage.html
deleted file mode 100644
index 4b10c2c..0000000
--- a/src/com/gitblit/wicket/pages/ActivityPage.html
+++ /dev/null
@@ -1,23 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml"  
-      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"  
-      xml:lang="en"  
-      lang="en"> 
-<body>
-<wicket:extend>
-	<div class="pageTitle">
-		<h2><wicket:message key="gb.recentActivity"></wicket:message><small> <span class="hidden-phone">/ <span wicket:id="subheader">[days back]</span></span></small></h2>
-	</div>
-	<div class="hidden-phone" style="height: 155px;text-align: center;">
-		<table>
-		<tr>
-		<td><span class="hidden-tablet" id="chartDaily"></span></td>
-		<td><span id="chartRepositories"></span></td>
-		<td><span id="chartAuthors"></span></td>
-		</tr>
-		</table>
-	</div>
-	<div wicket:id="activityPanel" style="padding-top:5px;" >[activity panel]</div>
-</wicket:extend>
-</body>
-</html>
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/pages/ActivityPage.java b/src/com/gitblit/wicket/pages/ActivityPage.java
deleted file mode 100644
index bceac8f..0000000
--- a/src/com/gitblit/wicket/pages/ActivityPage.java
+++ /dev/null
@@ -1,207 +0,0 @@
-/*
- * 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.wicket.pages;
-
-import java.text.MessageFormat;
-import java.text.SimpleDateFormat;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import org.apache.wicket.PageParameters;
-import org.apache.wicket.behavior.HeaderContributor;
-import org.apache.wicket.markup.html.basic.Label;
-
-import com.gitblit.GitBlit;
-import com.gitblit.Keys;
-import com.gitblit.models.Activity;
-import com.gitblit.models.Metric;
-import com.gitblit.models.RepositoryModel;
-import com.gitblit.utils.ActivityUtils;
-import com.gitblit.utils.StringUtils;
-import com.gitblit.wicket.PageRegistration;
-import com.gitblit.wicket.PageRegistration.DropDownMenuItem;
-import com.gitblit.wicket.PageRegistration.DropDownMenuRegistration;
-import com.gitblit.wicket.WicketUtils;
-import com.gitblit.wicket.charting.GoogleChart;
-import com.gitblit.wicket.charting.GoogleCharts;
-import com.gitblit.wicket.charting.GoogleLineChart;
-import com.gitblit.wicket.charting.GooglePieChart;
-import com.gitblit.wicket.panels.ActivityPanel;
-
-/**
- * Activity Page shows a list of recent commits across all visible Gitblit
- * repositories.
- * 
- * @author James Moger
- * 
- */
-public class ActivityPage extends RootPage {
-
-	public ActivityPage(PageParameters params) {
-		super(params);
-		setupPage("", "");
-
-		// parameters
-		int daysBack = WicketUtils.getDaysBack(params);
-		if (daysBack < 1) {
-			daysBack = 14;
-		}
-		String objectId = WicketUtils.getObject(params);
-
-		// determine repositories to view and retrieve the activity
-		List<RepositoryModel> models = getRepositories(params);
-		List<Activity> recentActivity = ActivityUtils.getRecentActivity(models, 
-				daysBack, objectId, getTimeZone());
-
-		if (recentActivity.size() == 0) {
-			// no activity, skip graphs and activity panel
-			add(new Label("subheader", MessageFormat.format(getString("gb.recentActivityNone"),
-					daysBack)));
-			add(new Label("activityPanel"));
-		} else {
-			// calculate total commits and total authors
-			int totalCommits = 0;
-			Set<String> uniqueAuthors = new HashSet<String>();
-			for (Activity activity : recentActivity) {
-				totalCommits += activity.getCommitCount();
-				uniqueAuthors.addAll(activity.getAuthorMetrics().keySet());
-			}
-			int totalAuthors = uniqueAuthors.size();
-
-			// add the subheader with stat numbers
-			add(new Label("subheader", MessageFormat.format(getString("gb.recentActivityStats"),
-					daysBack, totalCommits, totalAuthors)));
-
-			// create the activity charts
-			GoogleCharts charts = createCharts(recentActivity);
-			add(new HeaderContributor(charts));
-
-			// add activity panel
-			add(new ActivityPanel("activityPanel", recentActivity));
-		}
-	}
-
-	@Override
-	protected boolean reusePageParameters() {
-		return true;
-	}
-
-	@Override
-	protected void addDropDownMenus(List<PageRegistration> pages) {
-		DropDownMenuRegistration filters = new DropDownMenuRegistration("gb.filters",
-				ActivityPage.class);
-
-		PageParameters currentParameters = getPageParameters();
-		int daysBack = GitBlit.getInteger(Keys.web.activityDuration, 14);
-		if (currentParameters != null && !currentParameters.containsKey("db")) {
-			currentParameters.put("db", daysBack);
-		}
-
-		// preserve time filter options on repository choices
-		filters.menuItems.addAll(getRepositoryFilterItems(currentParameters));
-
-		// preserve repository filter options on time choices
-		filters.menuItems.addAll(getTimeFilterItems(currentParameters));
-
-		if (filters.menuItems.size() > 0) {
-			// Reset Filter
-			filters.menuItems.add(new DropDownMenuItem(getString("gb.reset"), null, null));
-		}
-		pages.add(filters);
-	}
-
-	/**
-	 * Creates the daily activity line chart, the active repositories pie chart,
-	 * and the active authors pie chart
-	 * 
-	 * @param recentActivity
-	 * @return
-	 */
-	private GoogleCharts createCharts(List<Activity> recentActivity) {
-		// activity metrics
-		Map<String, Metric> repositoryMetrics = new HashMap<String, Metric>();
-		Map<String, Metric> authorMetrics = new HashMap<String, Metric>();
-
-		// aggregate repository and author metrics
-		for (Activity activity : recentActivity) {
-
-			// aggregate author metrics
-			for (Map.Entry<String, Metric> entry : activity.getAuthorMetrics().entrySet()) {
-				String author = entry.getKey();
-				if (!authorMetrics.containsKey(author)) {
-					authorMetrics.put(author, new Metric(author));
-				}
-				authorMetrics.get(author).count += entry.getValue().count;
-			}
-
-			// aggregate repository metrics
-			for (Map.Entry<String, Metric> entry : activity.getRepositoryMetrics().entrySet()) {
-				String repository = StringUtils.stripDotGit(entry.getKey());
-				if (!repositoryMetrics.containsKey(repository)) {
-					repositoryMetrics.put(repository, new Metric(repository));
-				}
-				repositoryMetrics.get(repository).count += entry.getValue().count;
-			}
-		}
-
-		// build google charts
-		int w = 310;
-		int h = 150;
-		GoogleCharts charts = new GoogleCharts();
-
-		// sort in reverse-chronological order and then reverse that
-		Collections.sort(recentActivity);
-		Collections.reverse(recentActivity);
-
-		// daily line chart
-		GoogleChart chart = new GoogleLineChart("chartDaily", getString("gb.dailyActivity"), "day",
-				getString("gb.commits"));
-		SimpleDateFormat df = new SimpleDateFormat("MMM dd");
-		df.setTimeZone(getTimeZone());
-		for (Activity metric : recentActivity) {
-			chart.addValue(df.format(metric.startDate), metric.getCommitCount());
-		}
-		chart.setWidth(w);
-		chart.setHeight(h);
-		charts.addChart(chart);
-
-		// active repositories pie chart
-		chart = new GooglePieChart("chartRepositories", getString("gb.activeRepositories"),
-				getString("gb.repository"), getString("gb.commits"));
-		for (Metric metric : repositoryMetrics.values()) {
-			chart.addValue(metric.name, metric.count);
-		}
-		chart.setWidth(w);
-		chart.setHeight(h);
-		charts.addChart(chart);
-
-		// active authors pie chart
-		chart = new GooglePieChart("chartAuthors", getString("gb.activeAuthors"),
-				getString("gb.author"), getString("gb.commits"));
-		for (Metric metric : authorMetrics.values()) {
-			chart.addValue(metric.name, metric.count);
-		}
-		chart.setWidth(w);
-		chart.setHeight(h);
-		charts.addChart(chart);
-
-		return charts;
-	}
-}
diff --git a/src/com/gitblit/wicket/pages/BasePage.html b/src/com/gitblit/wicket/pages/BasePage.html
deleted file mode 100644
index 4a642e7..0000000
--- a/src/com/gitblit/wicket/pages/BasePage.html
+++ /dev/null
@@ -1,62 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml"  
-      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"  
-      xml:lang="en"  
-      lang="en"> 
-
-	<!-- Head -->
-	<wicket:head>
-		<meta name="viewport" content="width=device-width, initial-scale=1.0">
-   		<title wicket:id="title">[page title]</title>
-		<link rel="icon" href="gitblt-favicon.png" type="image/png" />
-		
-		<link rel="stylesheet" href="bootstrap/css/bootstrap.css"/>
-		<link rel="stylesheet" type="text/css" href="gitblit.css"/>
-	</wicket:head>
-
-	<body>
-
-		<!-- page content -->
-		<wicket:child />
-		
-		<!-- page footer -->
-		<div class="container">
-			<footer class="footer">
-				<p class="pull-right">
-					<a title="gitblit homepage" href="http://gitblit.com/">
-						<span wicket:id="gbVersion"></span>
-					</a> 
-				</p>
-				<div wicket:id="userPanel">[user panel]</div>
-			</footer>
-		</div>
-
-		<!-- Override Bootstrap's responsive menu background highlighting -->
-		<style>
-		@media (max-width: 979px) {
-			.nav-collapse .nav > li > a:hover, .nav-collapse .dropdown-menu a:hover {
-				background-color: #000070;
-			}
-			
-			.navbar div > ul .dropdown-menu li a {
-				color: #ccc;
-			}
-		}
-		</style>
-		
-		<!-- Include scripts at end for faster page loading -->
-		<script type="text/javascript" src="bootstrap/js/jquery.js"></script>
-		<script type="text/javascript" src="bootstrap/js/bootstrap.js"></script>		
-	</body>
-	
-	<!-- user fragment -->
-	<wicket:fragment wicket:id="userFragment">
-		<span class="userPanel" wicket:id="username"></span>
-		<span class="userPanel" wicket:id="loginLink"></span>
-		<span class="hidden-phone">
-			<span class="userPanel" wicket:id="separator"></span>
-			<span class="userPanel"><a wicket:id="changePasswordLink"><wicket:message key="gb.changePassword"></wicket:message></a></span>
-		</span>
-	</wicket:fragment>
-	
-</html>
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/pages/BasePage.java b/src/com/gitblit/wicket/pages/BasePage.java
deleted file mode 100644
index c733c99..0000000
--- a/src/com/gitblit/wicket/pages/BasePage.java
+++ /dev/null
@@ -1,460 +0,0 @@
-/*
- * 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.wicket.pages;
-
-import java.text.MessageFormat;
-import java.util.ArrayList;
-import java.util.Calendar;
-import java.util.Collections;
-import java.util.Date;
-import java.util.HashSet;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.ResourceBundle;
-import java.util.Set;
-import java.util.TimeZone;
-import java.util.regex.Pattern;
-
-import javax.servlet.http.HttpServletRequest;
-
-import org.apache.wicket.Application;
-import org.apache.wicket.MarkupContainer;
-import org.apache.wicket.PageParameters;
-import org.apache.wicket.RedirectToUrlException;
-import org.apache.wicket.RequestCycle;
-import org.apache.wicket.RestartResponseException;
-import org.apache.wicket.markup.html.CSSPackageResource;
-import org.apache.wicket.markup.html.WebPage;
-import org.apache.wicket.markup.html.basic.Label;
-import org.apache.wicket.markup.html.link.BookmarkablePageLink;
-import org.apache.wicket.markup.html.link.ExternalLink;
-import org.apache.wicket.markup.html.panel.FeedbackPanel;
-import org.apache.wicket.markup.html.panel.Fragment;
-import org.apache.wicket.protocol.http.RequestUtils;
-import org.apache.wicket.protocol.http.WebRequest;
-import org.apache.wicket.protocol.http.WebResponse;
-import org.apache.wicket.protocol.http.servlet.ServletWebRequest;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.gitblit.Constants;
-import com.gitblit.Constants.AccessPermission;
-import com.gitblit.Constants.AccessRestrictionType;
-import com.gitblit.Constants.AuthorizationControl;
-import com.gitblit.Constants.FederationStrategy;
-import com.gitblit.GitBlit;
-import com.gitblit.Keys;
-import com.gitblit.models.ProjectModel;
-import com.gitblit.models.RepositoryModel;
-import com.gitblit.models.TeamModel;
-import com.gitblit.models.UserModel;
-import com.gitblit.utils.StringUtils;
-import com.gitblit.utils.TimeUtils;
-import com.gitblit.wicket.GitBlitWebSession;
-import com.gitblit.wicket.WicketUtils;
-import com.gitblit.wicket.panels.LinkPanel;
-
-public abstract class BasePage extends WebPage {
-
-	private final Logger logger;
-	
-	private transient TimeUtils timeUtils;
-
-	public BasePage() {
-		super();
-		logger = LoggerFactory.getLogger(getClass());
-		customizeHeader();
-		login();
-	}
-
-	public BasePage(PageParameters params) {
-		super(params);
-		logger = LoggerFactory.getLogger(getClass());
-		customizeHeader();
-		login();
-	}
-	
-	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;		
-			try {
-				bundle = ResourceBundle.getBundle("com.gitblit.wicket.GitBlitWebApp", GitBlitWebSession.get().getLocale());
-			} catch (Throwable t) {
-				bundle = ResourceBundle.getBundle("com.gitblit.wicket.GitBlitWebApp");
-			}
-			timeUtils = new TimeUtils(bundle);
-		}
-		return timeUtils;
-	}
-	
-	@Override
-	protected void onBeforeRender() {
-		if (GitBlit.isDebugMode()) {
-			// strip Wicket tags in debug mode for jQuery DOM traversal
-			Application.get().getMarkupSettings().setStripWicketTags(true);
-		}
-		super.onBeforeRender();
-	}
-
-	@Override
-	protected void onAfterRender() {
-		if (GitBlit.isDebugMode()) {
-			// restore Wicket debug tags
-			Application.get().getMarkupSettings().setStripWicketTags(false);
-		}
-		super.onAfterRender();
-	}	
-
-	private void login() {
-		GitBlitWebSession session = GitBlitWebSession.get();
-		if (session.isLoggedIn() && !session.isSessionInvalidated()) {
-			// already have a session, refresh usermodel to pick up
-			// any changes to permissions or roles (issue-186)
-			UserModel user = GitBlit.self().getUserModel(session.getUser().username);
-			session.setUser(user);
-			return;
-		}
-		
-		// try to authenticate by servlet request
-		HttpServletRequest httpRequest = ((WebRequest) getRequestCycle().getRequest()).getHttpServletRequest();
-		UserModel user = GitBlit.self().authenticate(httpRequest);
-
-		// Login the user
-		if (user != null) {
-			// issue 62: fix session fixation vulnerability
-			session.replaceSession();
-			session.setUser(user);
-
-			// Set Cookie
-			WebResponse response = (WebResponse) getRequestCycle().getResponse();
-			GitBlit.self().setCookie(response, user);
-			
-			session.continueRequest();
-		}
-	}
-
-	protected void setupPage(String repositoryName, String pageName) {
-		if (repositoryName != null && repositoryName.trim().length() > 0) {
-			add(new Label("title", getServerName() + " - " + repositoryName));
-		} else {
-			add(new Label("title", getServerName()));
-		}
-
-		ExternalLink rootLink = new ExternalLink("rootLink", urlFor(RepositoriesPage.class, null).toString());
-		WicketUtils.setHtmlTooltip(rootLink, GitBlit.getString(Keys.web.siteName, Constants.NAME));
-		add(rootLink);
-
-		// Feedback panel for info, warning, and non-fatal error messages
-		add(new FeedbackPanel("feedback"));
-
-		// footer
-		if (GitBlit.getBoolean(Keys.web.authenticateViewPages, true)
-				|| GitBlit.getBoolean(Keys.web.authenticateAdminPages, true)) {
-			UserFragment userFragment = new UserFragment("userPanel", "userFragment", BasePage.this);
-			add(userFragment);
-		} else {
-			add(new Label("userPanel", ""));
-		}
-
-		add(new Label("gbVersion", "v" + Constants.VERSION));
-		if (GitBlit.getBoolean(Keys.web.aggressiveHeapManagement, false)) {
-			System.gc();
-		}
-	}
-
-	protected Map<AccessRestrictionType, String> getAccessRestrictions() {
-		Map<AccessRestrictionType, String> map = new LinkedHashMap<AccessRestrictionType, String>();
-		for (AccessRestrictionType type : AccessRestrictionType.values()) {
-			switch (type) {
-			case NONE:
-				map.put(type, getString("gb.notRestricted"));
-				break;
-			case PUSH:
-				map.put(type, getString("gb.pushRestricted"));
-				break;
-			case CLONE:
-				map.put(type, getString("gb.cloneRestricted"));
-				break;
-			case VIEW:
-				map.put(type, getString("gb.viewRestricted"));
-				break;
-			}
-		}
-		return map;
-	}
-	
-	protected Map<AccessPermission, String> getAccessPermissions() {
-		Map<AccessPermission, String> map = new LinkedHashMap<AccessPermission, String>();
-		for (AccessPermission type : AccessPermission.values()) {
-			switch (type) {
-			case NONE:
-				map.put(type, MessageFormat.format(getString("gb.noPermission"), type.code));
-				break;
-			case EXCLUDE:
-				map.put(type, MessageFormat.format(getString("gb.excludePermission"), type.code));
-				break;
-			case VIEW:
-				map.put(type, MessageFormat.format(getString("gb.viewPermission"), type.code));
-				break;
-			case CLONE:
-				map.put(type, MessageFormat.format(getString("gb.clonePermission"), type.code));
-				break;
-			case PUSH:
-				map.put(type, MessageFormat.format(getString("gb.pushPermission"), type.code));
-				break;
-			case CREATE:
-				map.put(type, MessageFormat.format(getString("gb.createPermission"), type.code));
-				break;
-			case DELETE:
-				map.put(type, MessageFormat.format(getString("gb.deletePermission"), type.code));
-				break;
-			case REWIND:
-				map.put(type, MessageFormat.format(getString("gb.rewindPermission"), type.code));
-				break;
-			}
-		}
-		return map;
-	}
-	
-	protected Map<FederationStrategy, String> getFederationTypes() {
-		Map<FederationStrategy, String> map = new LinkedHashMap<FederationStrategy, String>();
-		for (FederationStrategy type : FederationStrategy.values()) {
-			switch (type) {
-			case EXCLUDE:
-				map.put(type, getString("gb.excludeFromFederation"));
-				break;
-			case FEDERATE_THIS:
-				map.put(type, getString("gb.federateThis"));
-				break;
-			case FEDERATE_ORIGIN:
-				map.put(type, getString("gb.federateOrigin"));
-				break;
-			}
-		}
-		return map;
-	}
-	
-	protected Map<AuthorizationControl, String> getAuthorizationControls() {
-		Map<AuthorizationControl, String> map = new LinkedHashMap<AuthorizationControl, String>();
-		for (AuthorizationControl type : AuthorizationControl.values()) {
-			switch (type) {
-			case AUTHENTICATED:
-				map.put(type, getString("gb.allowAuthenticatedDescription"));
-				break;
-			case NAMED:
-				map.put(type, getString("gb.allowNamedDescription"));
-				break;
-			}
-		}
-		return map;
-	}
-
-	protected TimeZone getTimeZone() {
-		return GitBlit.getBoolean(Keys.web.useClientTimezone, false) ? GitBlitWebSession.get()
-				.getTimezone() : GitBlit.getTimezone();
-	}
-
-	protected String getServerName() {
-		ServletWebRequest servletWebRequest = (ServletWebRequest) getRequest();
-		HttpServletRequest req = servletWebRequest.getHttpServletRequest();
-		return req.getServerName();
-	}
-	
-	public static String getRepositoryUrl(RepositoryModel repository) {
-		StringBuilder sb = new StringBuilder();
-		sb.append(WicketUtils.getGitblitURL(RequestCycle.get().getRequest()));
-		sb.append(Constants.GIT_PATH);
-		sb.append(repository.name);
-		
-		// inject username into repository url if authentication is required
-		if (repository.accessRestriction.exceeds(AccessRestrictionType.NONE)
-				&& GitBlitWebSession.get().isLoggedIn()) {
-			String username = GitBlitWebSession.get().getUsername();
-			sb.insert(sb.indexOf("://") + 3, username + "@");
-		}
-		return sb.toString();
-	}
-	
-	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();
-		}
-
-		boolean hasParameter = false;
-		String regex = WicketUtils.getRegEx(params);
-		String team = WicketUtils.getTeam(params);
-		int daysBack = params.getInt("db", 0);
-
-		List<ProjectModel> availableModels = getProjectModels();
-		Set<ProjectModel> models = new HashSet<ProjectModel>();
-
-		if (!StringUtils.isEmpty(regex)) {
-			// filter the projects by the regex
-			hasParameter = true;
-			Pattern pattern = Pattern.compile(regex);
-			for (ProjectModel model : availableModels) {
-				if (pattern.matcher(model.name).find()) {
-					models.add(model);
-				}
-			}
-		}
-
-		if (!StringUtils.isEmpty(team)) {
-			// filter the projects by the specified teams
-			hasParameter = true;
-			List<String> teams = StringUtils.getStringsFromValue(team, ",");
-
-			// need TeamModels first
-			List<TeamModel> teamModels = new ArrayList<TeamModel>();
-			for (String name : teams) {
-				TeamModel teamModel = GitBlit.self().getTeamModel(name);
-				if (teamModel != null) {
-					teamModels.add(teamModel);
-				}
-			}
-
-			// brute-force our way through finding the matching models
-			for (ProjectModel projectModel : availableModels) {
-				for (String repositoryName : projectModel.repositories) {
-					for (TeamModel teamModel : teamModels) {
-						if (teamModel.hasRepositoryPermission(repositoryName)) {
-							models.add(projectModel);
-						}
-					}
-				}
-			}
-		}
-
-		if (!hasParameter) {
-			models.addAll(availableModels);
-		}
-
-		// time-filter the list
-		if (daysBack > 0) {
-			Calendar cal = Calendar.getInstance();
-			cal.set(Calendar.HOUR_OF_DAY, 0);
-			cal.set(Calendar.MINUTE, 0);
-			cal.set(Calendar.SECOND, 0);
-			cal.set(Calendar.MILLISECOND, 0);
-			cal.add(Calendar.DATE, -1 * daysBack);
-			Date threshold = cal.getTime();
-			Set<ProjectModel> timeFiltered = new HashSet<ProjectModel>();
-			for (ProjectModel model : models) {
-				if (model.lastChange.after(threshold)) {
-					timeFiltered.add(model);
-				}
-			}
-			models = timeFiltered;
-		}
-
-		List<ProjectModel> list = new ArrayList<ProjectModel>(models);
-		Collections.sort(list);
-		return list;
-	}
-
-	public void warn(String message, Throwable t) {
-		logger.warn(message, t);
-	}
-	
-	public void error(String message, boolean redirect) {
-		logger.error(message  + " for " + GitBlitWebSession.get().getUsername());
-		if (redirect) {
-			GitBlitWebSession.get().cacheErrorMessage(message);
-			String relativeUrl = urlFor(RepositoriesPage.class, null).toString();
-			String absoluteUrl = RequestUtils.toAbsolutePath(relativeUrl);
-			throw new RedirectToUrlException(absoluteUrl);
-		} else {
-			super.error(message);
-		}
-	}
-
-	public void error(String message, Throwable t, boolean redirect) {
-		logger.error(message, t);
-		if (redirect) {
-			GitBlitWebSession.get().cacheErrorMessage(message);
-			throw new RestartResponseException(getApplication().getHomePage());
-		} else {
-			super.error(message);
-		}
-	}
-
-	public void authenticationError(String message) {
-		logger.error(getRequest().getURL() + " for " + GitBlitWebSession.get().getUsername());
-		if (!GitBlitWebSession.get().isLoggedIn()) {
-			// cache the request if we have not authenticated.
-			// the request will continue after authentication.
-			GitBlitWebSession.get().cacheRequest(getClass());
-		}
-		error(message, true);
-	}
-
-	/**
-	 * Panel fragment for displaying login or logout/change_password links.
-	 * 
-	 */
-	static class UserFragment extends Fragment {
-
-		private static final long serialVersionUID = 1L;
-
-		public UserFragment(String id, String markupId, MarkupContainer markupProvider) {
-			super(id, markupId, markupProvider);
-
-			GitBlitWebSession session = GitBlitWebSession.get();
-			if (session.isLoggedIn()) {				
-				UserModel user = session.getUser();
-				boolean editCredentials = GitBlit.self().supportsCredentialChanges(user);
-				boolean standardLogin = session.authenticationType.isStandard();
-
-				// username, logout, and change password
-				add(new Label("username", user.getDisplayName() + ":"));
-				add(new LinkPanel("loginLink", null, markupProvider.getString("gb.logout"),
-						LogoutPage.class).setVisible(standardLogin));
-				
-				// quick and dirty hack for showing a separator
-				add(new Label("separator", "|").setVisible(standardLogin && editCredentials));
-				add(new BookmarkablePageLink<Void>("changePasswordLink", 
-						ChangePasswordPage.class).setVisible(editCredentials));
-			} else {
-				// login
-				add(new Label("username").setVisible(false));
-				add(new Label("loginLink").setVisible(false));
-				add(new Label("separator").setVisible(false));
-				add(new Label("changePasswordLink").setVisible(false));
-			}
-		}
-	}
-}
diff --git a/src/com/gitblit/wicket/pages/BlamePage.html b/src/com/gitblit/wicket/pages/BlamePage.html
deleted file mode 100644
index 9391eaf..0000000
--- a/src/com/gitblit/wicket/pages/BlamePage.html
+++ /dev/null
@@ -1,39 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml"  
-      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"  
-      xml:lang="en"  
-      lang="en"> 
-
-<body>
-<wicket:extend>
-	
-	<!-- blame nav links -->	
-	<div class="page_nav2">
-		<a wicket:id="blobLink"><wicket:message key="gb.view"></wicket:message></a> | <a wicket:id="historyLink"><wicket:message key="gb.history"></wicket:message></a> | <a wicket:id="headLink"><wicket:message key="gb.head"></wicket:message></a> | <a wicket:id="commitLink"><wicket:message key="gb.commit"></wicket:message></a> | <a wicket:id="commitDiffLink"><wicket:message key="gb.commitdiff"></wicket:message></a>
-	</div>	
-	
-	<!-- commit header -->
-	<div wicket:id="commitHeader">[commit header]</div>
-
-	<!-- breadcrumbs -->
-	<div wicket:id="breadcrumbs">[breadcrumbs]</div>
-		
-	<!--  blame content -->
-	<table class="annotated" style="margin-bottom:5px;">
-		<tbody>
-			<tr>
-				<th><wicket:message key="gb.commit">[commit]</wicket:message></th>
-				<th><wicket:message key="gb.line">[line]</wicket:message></th>
-				<th><wicket:message key="gb.content">[content]</wicket:message></th>
-			</tr>
-			<tr wicket:id="annotation">
-				<td><span class="sha1" wicket:id="commit"></span></td>
-				<td><span class="sha1" wicket:id="line"></span></td>
-				<td><span class="sha1" wicket:id="data"></span></td>
-			</tr>
-		</tbody>
-	</table>
-	
-</wicket:extend>
-</body>
-</html>
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/pages/BlamePage.java b/src/com/gitblit/wicket/pages/BlamePage.java
deleted file mode 100644
index d76181d..0000000
--- a/src/com/gitblit/wicket/pages/BlamePage.java
+++ /dev/null
@@ -1,129 +0,0 @@
-/*
- * 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.wicket.pages;
-
-import java.text.DateFormat;
-import java.text.MessageFormat;
-import java.text.SimpleDateFormat;
-import java.util.List;
-
-import org.apache.wicket.PageParameters;
-import org.apache.wicket.markup.html.basic.Label;
-import org.apache.wicket.markup.html.link.BookmarkablePageLink;
-import org.apache.wicket.markup.repeater.Item;
-import org.apache.wicket.markup.repeater.data.DataView;
-import org.apache.wicket.markup.repeater.data.ListDataProvider;
-import org.eclipse.jgit.lib.Constants;
-import org.eclipse.jgit.revwalk.RevCommit;
-
-import com.gitblit.GitBlit;
-import com.gitblit.Keys;
-import com.gitblit.models.AnnotatedLine;
-import com.gitblit.utils.DiffUtils;
-import com.gitblit.utils.StringUtils;
-import com.gitblit.wicket.WicketUtils;
-import com.gitblit.wicket.panels.CommitHeaderPanel;
-import com.gitblit.wicket.panels.LinkPanel;
-import com.gitblit.wicket.panels.PathBreadcrumbsPanel;
-
-public class BlamePage extends RepositoryPage {
-
-	public BlamePage(PageParameters params) {
-		super(params);
-
-		final String blobPath = WicketUtils.getPath(params);
-
-		RevCommit commit = getCommit();
-
-		add(new BookmarkablePageLink<Void>("blobLink", BlobPage.class,
-				WicketUtils.newPathParameter(repositoryName, objectId, blobPath)));
-		add(new BookmarkablePageLink<Void>("commitLink", CommitPage.class,
-				WicketUtils.newObjectParameter(repositoryName, objectId)));
-		add(new BookmarkablePageLink<Void>("commitDiffLink", CommitDiffPage.class,
-				WicketUtils.newObjectParameter(repositoryName, objectId)));
-
-		// blame page links
-		add(new BookmarkablePageLink<Void>("headLink", BlamePage.class,
-				WicketUtils.newPathParameter(repositoryName, Constants.HEAD, blobPath)));
-		add(new BookmarkablePageLink<Void>("historyLink", HistoryPage.class,
-				WicketUtils.newPathParameter(repositoryName, objectId, blobPath)));
-
-		add(new CommitHeaderPanel("commitHeader", repositoryName, commit));
-
-		add(new PathBreadcrumbsPanel("breadcrumbs", repositoryName, blobPath, objectId));
-
-		String format = GitBlit.getString(Keys.web.datetimestampLongFormat,
-				"EEEE, MMMM d, yyyy HH:mm Z");
-		final DateFormat df = new SimpleDateFormat(format);
-		df.setTimeZone(getTimeZone());
-		List<AnnotatedLine> lines = DiffUtils.blame(getRepository(), blobPath, objectId);
-		ListDataProvider<AnnotatedLine> blameDp = new ListDataProvider<AnnotatedLine>(lines);
-		DataView<AnnotatedLine> blameView = new DataView<AnnotatedLine>("annotation", blameDp) {
-			private static final long serialVersionUID = 1L;
-			private int count;
-			private String lastCommitId = "";
-			private boolean showInitials = true;
-
-			public void populateItem(final Item<AnnotatedLine> item) {
-				AnnotatedLine entry = item.getModelObject();
-				item.add(new Label("line", "" + entry.lineNumber));
-				item.add(new Label("data", StringUtils.escapeForHtml(entry.data, true))
-						.setEscapeModelStrings(false));
-				if (!lastCommitId.equals(entry.commitId)) {
-					lastCommitId = entry.commitId;
-					count++;
-					// show the link for first line
-					LinkPanel commitLink = new LinkPanel("commit", null,
-							getShortObjectId(entry.commitId), CommitPage.class,
-							newCommitParameter(entry.commitId));
-					WicketUtils.setHtmlTooltip(commitLink,
-							MessageFormat.format("{0}, {1}", entry.author, df.format(entry.when)));
-					item.add(commitLink);
-					showInitials = true;
-				} else {
-					if (showInitials) {
-						showInitials = false;
-						// show author initials
-						item.add(new Label("commit", getInitials(entry.author)));
-					} else {
-						// hide the commit link until the next block
-						item.add(new Label("commit").setVisible(false));
-					}
-				}
-				if (count % 2 == 0) {
-					WicketUtils.setCssClass(item, "even");
-				} else {
-					WicketUtils.setCssClass(item, "odd");
-				}
-			}
-		};
-		add(blameView);
-	}
-
-	private String getInitials(String author) {
-		StringBuilder sb = new StringBuilder();
-		String[] chunks = author.split(" ");
-		for (String chunk : chunks) {
-			sb.append(chunk.charAt(0));
-		}
-		return sb.toString().toUpperCase();
-	}
-
-	@Override
-	protected String getPageName() {
-		return getString("gb.blame");
-	}
-}
diff --git a/src/com/gitblit/wicket/pages/BlobDiffPage.java b/src/com/gitblit/wicket/pages/BlobDiffPage.java
deleted file mode 100644
index d86d2e6..0000000
--- a/src/com/gitblit/wicket/pages/BlobDiffPage.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * 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.wicket.pages;
-
-import org.apache.wicket.PageParameters;
-import org.apache.wicket.markup.html.basic.Label;
-import org.apache.wicket.markup.html.link.BookmarkablePageLink;
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.revwalk.RevCommit;
-
-import com.gitblit.GitBlit;
-import com.gitblit.Keys;
-import com.gitblit.utils.DiffUtils;
-import com.gitblit.utils.DiffUtils.DiffOutputType;
-import com.gitblit.utils.JGitUtils;
-import com.gitblit.utils.StringUtils;
-import com.gitblit.wicket.WicketUtils;
-import com.gitblit.wicket.panels.CommitHeaderPanel;
-import com.gitblit.wicket.panels.PathBreadcrumbsPanel;
-
-public class BlobDiffPage extends RepositoryPage {
-
-	public BlobDiffPage(PageParameters params) {
-		super(params);
-
-		final String blobPath = WicketUtils.getPath(params);
-		final String baseObjectId = WicketUtils.getBaseObjectId(params);
-
-		Repository r = getRepository();
-		RevCommit commit = getCommit();
-
-		DiffOutputType diffType = DiffOutputType.forName(GitBlit.getString(Keys.web.diffStyle,
-				DiffOutputType.GITBLIT.name()));
-
-		String diff;
-		if (StringUtils.isEmpty(baseObjectId)) {
-			// use first parent
-			diff = DiffUtils.getDiff(r, commit, blobPath, diffType);
-			add(new BookmarkablePageLink<Void>("patchLink", PatchPage.class,
-					WicketUtils.newPathParameter(repositoryName, objectId, blobPath)));
-		} else {
-			// base commit specified
-			RevCommit baseCommit = JGitUtils.getCommit(r, baseObjectId);
-			diff = DiffUtils.getDiff(r, baseCommit, commit, blobPath, diffType);
-			add(new BookmarkablePageLink<Void>("patchLink", PatchPage.class,
-					WicketUtils.newBlobDiffParameter(repositoryName, baseObjectId, objectId,
-							blobPath)));
-		}
-
-		add(new BookmarkablePageLink<Void>("commitLink", CommitPage.class,
-				WicketUtils.newObjectParameter(repositoryName, objectId)));
-		add(new BookmarkablePageLink<Void>("commitDiffLink", CommitDiffPage.class,
-				WicketUtils.newObjectParameter(repositoryName, objectId)));
-
-		// diff page links
-		add(new BookmarkablePageLink<Void>("blameLink", BlamePage.class,
-				WicketUtils.newPathParameter(repositoryName, objectId, blobPath)));
-		add(new BookmarkablePageLink<Void>("historyLink", HistoryPage.class,
-				WicketUtils.newPathParameter(repositoryName, objectId, blobPath)));
-
-		add(new CommitHeaderPanel("commitHeader", repositoryName, commit));
-
-		add(new PathBreadcrumbsPanel("breadcrumbs", repositoryName, blobPath, objectId));
-
-		add(new Label("diffText", diff).setEscapeModelStrings(false));
-	}
-
-	@Override
-	protected String getPageName() {
-		return getString("gb.diff");
-	}
-}
diff --git a/src/com/gitblit/wicket/pages/BlobPage.html b/src/com/gitblit/wicket/pages/BlobPage.html
deleted file mode 100644
index 80f061f..0000000
--- a/src/com/gitblit/wicket/pages/BlobPage.html
+++ /dev/null
@@ -1,38 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml"  
-      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"  
-      xml:lang="en"  
-      lang="en">
-      
-<!-- contribute google-code-prettify resources to the page header -->
-<wicket:head>
-  <wicket:link>
-   	<link href="prettify/prettify.css" type="text/css" rel="stylesheet" />
-	<script type="text/javascript" src="prettify/prettify.js"></script>
-  </wicket:link>
-</wicket:head>
-
-<wicket:extend>
-<!-- need to specify body.onload -->
-<body onload="prettyPrint()">
-
-		<!-- blob nav links -->	
-		<div class="page_nav2">
-			<a wicket:id="blameLink"><wicket:message key="gb.blame"></wicket:message></a> | <a wicket:id="historyLink"><wicket:message key="gb.history"></wicket:message></a> | <a wicket:id="rawLink"><wicket:message key="gb.raw"></wicket:message></a> | <a wicket:id="headLink"><wicket:message key="gb.head"></wicket:message></a>
-		</div>	
-	
-		<!-- commit header -->
-		<div wicket:id="commitHeader">[commit header]</div>
-
-		<!-- breadcrumbs -->
-		<div wicket:id="breadcrumbs">[breadcrumbs]</div>
-		
-		<!--  blob content -->
-		<pre style="border:0px;" wicket:id="blobText">[blob content]</pre>
-
-		<!--  blob image -->
-		<img wicket:id="blobImage" style="padding-top:5px;"></img>
-	
-</body>
-</wicket:extend>
-</html>
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/pages/BlobPage.java b/src/com/gitblit/wicket/pages/BlobPage.java
deleted file mode 100644
index e2b8546..0000000
--- a/src/com/gitblit/wicket/pages/BlobPage.java
+++ /dev/null
@@ -1,194 +0,0 @@
-/*
- * 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.wicket.pages;
-
-import java.text.MessageFormat;
-import java.util.HashMap;
-import java.util.Map;
-
-import org.apache.wicket.Component;
-import org.apache.wicket.PageParameters;
-import org.apache.wicket.markup.html.basic.Label;
-import org.apache.wicket.markup.html.image.Image;
-import org.apache.wicket.markup.html.link.BookmarkablePageLink;
-import org.eclipse.jgit.lib.Constants;
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.revwalk.RevCommit;
-
-import com.gitblit.GitBlit;
-import com.gitblit.Keys;
-import com.gitblit.utils.JGitUtils;
-import com.gitblit.utils.StringUtils;
-import com.gitblit.wicket.ExternalImage;
-import com.gitblit.wicket.WicketUtils;
-import com.gitblit.wicket.panels.CommitHeaderPanel;
-import com.gitblit.wicket.panels.PathBreadcrumbsPanel;
-
-public class BlobPage extends RepositoryPage {
-
-	public BlobPage(PageParameters params) {
-		super(params);
-
-		Repository r = getRepository();
-		final String blobPath = WicketUtils.getPath(params);
-		String [] encodings = GitBlit.getEncodings();
-		
-		if (StringUtils.isEmpty(blobPath)) {
-			// blob by objectid
-
-			add(new BookmarkablePageLink<Void>("blameLink", BlamePage.class,
-					WicketUtils.newPathParameter(repositoryName, objectId, blobPath))
-					.setEnabled(false));
-			add(new BookmarkablePageLink<Void>("historyLink", HistoryPage.class).setEnabled(false));
-			add(new BookmarkablePageLink<Void>("rawLink", RawPage.class,
-					WicketUtils.newPathParameter(repositoryName, objectId, blobPath)));
-			add(new BookmarkablePageLink<Void>("headLink", BlobPage.class).setEnabled(false));
-			add(new CommitHeaderPanel("commitHeader", objectId));
-			add(new PathBreadcrumbsPanel("breadcrumbs", repositoryName, blobPath, objectId));
-			Component c = new Label("blobText", JGitUtils.getStringContent(r, objectId, encodings));
-			WicketUtils.setCssClass(c, "plainprint");
-			add(c);
-		} else {
-			// standard blob view
-			String extension = null;
-			if (blobPath.lastIndexOf('.') > -1) {
-				extension = blobPath.substring(blobPath.lastIndexOf('.') + 1).toLowerCase();
-			}
-
-			// see if we should redirect to the markdown page
-			for (String ext : GitBlit.getStrings(Keys.web.markdownExtensions)) {
-				if (ext.equals(extension)) {
-					setResponsePage(MarkdownPage.class, params);
-					return;
-				}
-			}
-
-			// manually get commit because it can be null
-			RevCommit commit = JGitUtils.getCommit(r, objectId);
-
-			// blob page links
-			add(new BookmarkablePageLink<Void>("blameLink", BlamePage.class,
-					WicketUtils.newPathParameter(repositoryName, objectId, blobPath)));
-			add(new BookmarkablePageLink<Void>("historyLink", HistoryPage.class,
-					WicketUtils.newPathParameter(repositoryName, objectId, blobPath)));
-			add(new BookmarkablePageLink<Void>("rawLink", RawPage.class,
-					WicketUtils.newPathParameter(repositoryName, objectId, blobPath)));
-			add(new BookmarkablePageLink<Void>("headLink", BlobPage.class,
-					WicketUtils.newPathParameter(repositoryName, Constants.HEAD, blobPath)));
-
-			add(new CommitHeaderPanel("commitHeader", repositoryName, commit));
-
-			add(new PathBreadcrumbsPanel("breadcrumbs", repositoryName, blobPath, objectId));
-
-			// Map the extensions to types
-			Map<String, Integer> map = new HashMap<String, Integer>();
-			for (String ext : GitBlit.getStrings(Keys.web.prettyPrintExtensions)) {
-				map.put(ext.toLowerCase(), 1);
-			}
-			for (String ext : GitBlit.getStrings(Keys.web.imageExtensions)) {
-				map.put(ext.toLowerCase(), 2);
-			}
-			for (String ext : GitBlit.getStrings(Keys.web.binaryExtensions)) {
-				map.put(ext.toLowerCase(), 3);
-			}
-
-			if (extension != null) {
-				int type = 0;
-				if (map.containsKey(extension)) {
-					type = map.get(extension);
-				}
-				switch (type) {
-				case 2:
-					// image blobs
-					add(new Label("blobText").setVisible(false));
-					add(new ExternalImage("blobImage", urlFor(RawPage.class, WicketUtils.newPathParameter(repositoryName, objectId, blobPath)).toString()));
-					break;
-				case 3:
-					// binary blobs
-					add(new Label("blobText", "Binary File"));
-					add(new Image("blobImage").setVisible(false));
-					break;
-				default:
-					// plain text
-					String source = JGitUtils.getStringContent(r, commit.getTree(), blobPath, encodings);
-					String table = generateSourceView(source, type == 1);
-					add(new Label("blobText", table).setEscapeModelStrings(false));
-					add(new Image("blobImage").setVisible(false));
-				}
-			} else {
-				// plain text
-				String source = JGitUtils.getStringContent(r, commit.getTree(), blobPath, encodings);
-				String table = generateSourceView(source, false);
-				add(new Label("blobText", table).setEscapeModelStrings(false));
-				add(new Image("blobImage").setVisible(false));
-			}
-		}
-	}
-	
-	protected String generateSourceView(String source, 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\">");
-		sb.append("<pre>");
-		String numPattern = "<span id=\"L{0}\" class=\"num\">{0}</span>\n";
-		for (int i = 0; i < lines.length; i++) {
-			sb.append(MessageFormat.format(numPattern, "" + (i + 1)));
-		}
-		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\">");
-		if (prettyPrint) {
-			sb.append("<pre class=\"prettyprint\">");
-		} else {
-			sb.append("<pre class=\"plainprint\">");
-		}
-		lines = StringUtils.escapeForHtml(source, true).split("\n");
-		
-		sb.append("<table width=\"100%\"><tbody>");
-		
-		String linePattern = "<tr class=\"{0}\"><td><a href=\"#L{2}\">{1}</a>\r</tr>";
-		for (int i = 0; i < lines.length; i++) {
-			String line = lines[i].replace('\r', ' ');
-			String cssClass = (i % 2 == 0) ? "even" : "odd";
-			sb.append(MessageFormat.format(linePattern, cssClass, line, "" + (i + 1)));
-		}
-		sb.append("</tbody></table></pre>");
-		sb.append("</pre>");
-		sb.append("</div>");
-		sb.append("</td>");
-		sb.append("<!-- end lines column -->");
-		
-		sb.append("</tr></tbody></table>");
-		sb.append("<!-- end blob table -->");
-		
-		return sb.toString();
-	}
-
-	@Override
-	protected String getPageName() {
-		return getString("gb.view");
-	}
-}
diff --git a/src/com/gitblit/wicket/pages/BranchesPage.java b/src/com/gitblit/wicket/pages/BranchesPage.java
deleted file mode 100644
index 8684fb3..0000000
--- a/src/com/gitblit/wicket/pages/BranchesPage.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * 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.wicket.pages;
-
-import org.apache.wicket.PageParameters;
-
-import com.gitblit.wicket.panels.BranchesPanel;
-
-public class BranchesPage extends RepositoryPage {
-
-	public BranchesPage(PageParameters params) {
-		super(params);
-
-		add(new BranchesPanel("branchesPanel", getRepositoryModel(), getRepository(), -1, isShowAdmin() || isOwner()));
-	}
-
-	@Override
-	protected String getPageName() {
-		return getString("gb.branches");
-	}
-}
diff --git a/src/com/gitblit/wicket/pages/CommitDiffPage.html b/src/com/gitblit/wicket/pages/CommitDiffPage.html
deleted file mode 100644
index de39f4c..0000000
--- a/src/com/gitblit/wicket/pages/CommitDiffPage.html
+++ /dev/null
@@ -1,43 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml"  
-      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"  
-      xml:lang="en"  
-      lang="en"> 
-
-<body>
-<wicket:extend>
-
-	<!-- commitdiff nav links -->	
-	<div class="page_nav2">
-		<wicket:message key="gb.parent"></wicket:message>: <span wicket:id="parentLink">[parent link]</span> | <a wicket:id="patchLink"><wicket:message key="gb.patch"></wicket:message></a> | <a wicket:id="commitLink"><wicket:message key="gb.commit"></wicket:message></a>
-	</div>	
-	
-	<!-- commit header -->
-	<div wicket:id="commitHeader">[commit header]</div>
-
-	<!-- changed paths -->
-	<div style="padding-top:15px;">
-		<!-- commit legend -->
-		<div class="hidden-phone" style="text-align:right;" wicket:id="commitLegend"></div>
-	
-		<div class="header"><i class="icon-file"></i> <wicket:message key="gb.changedFiles">[changed files]</wicket:message></div>
-	</div>
-	
-	<table class="pretty">
-		<tr wicket:id="changedPath">
-			<td class="changeType"><span wicket:id="changeType">[change type]</span></td>		
-			<td class="path"><span wicket:id="pathName">[commit path]</span></td>			
-			<td class="hidden-phone rightAlign">
-				<span class="link">
-					<a wicket:id="patch"><wicket:message key="gb.patch"></wicket:message></a> | <a wicket:id="view"><wicket:message key="gb.view"></wicket:message></a> | <a wicket:id="blame"><wicket:message key="gb.blame"></wicket:message></a> | <a wicket:id="history"><wicket:message key="gb.history"></wicket:message></a>
-				</span>
-			</td>
-		</tr>
-	</table>
-	
-	<!--  diff content -->
-	<pre style="padding-top:10px;" wicket:id="diffText">[diff text]</pre>
-	
-</wicket:extend>
-</body>
-</html>
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/pages/CommitDiffPage.java b/src/com/gitblit/wicket/pages/CommitDiffPage.java
deleted file mode 100644
index 3ad7074..0000000
--- a/src/com/gitblit/wicket/pages/CommitDiffPage.java
+++ /dev/null
@@ -1,192 +0,0 @@
-/*
- * 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.wicket.pages;
-
-import java.text.MessageFormat;
-import java.util.ArrayList;
-import java.util.List;
-
-import org.apache.wicket.PageParameters;
-import org.apache.wicket.markup.html.basic.Label;
-import org.apache.wicket.markup.html.link.BookmarkablePageLink;
-import org.apache.wicket.markup.html.link.ExternalLink;
-import org.apache.wicket.markup.repeater.Item;
-import org.apache.wicket.markup.repeater.data.DataView;
-import org.apache.wicket.markup.repeater.data.ListDataProvider;
-import org.eclipse.jgit.diff.DiffEntry.ChangeType;
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.revwalk.RevCommit;
-
-import com.gitblit.GitBlit;
-import com.gitblit.Keys;
-import com.gitblit.models.PathModel.PathChangeModel;
-import com.gitblit.models.SubmoduleModel;
-import com.gitblit.utils.DiffUtils;
-import com.gitblit.utils.DiffUtils.DiffOutputType;
-import com.gitblit.utils.JGitUtils;
-import com.gitblit.wicket.WicketUtils;
-import com.gitblit.wicket.panels.CommitHeaderPanel;
-import com.gitblit.wicket.panels.CommitLegendPanel;
-import com.gitblit.wicket.panels.LinkPanel;
-
-public class CommitDiffPage extends RepositoryPage {
-
-	public CommitDiffPage(PageParameters params) {
-		super(params);
-
-		Repository r = getRepository();
-
-		DiffOutputType diffType = DiffOutputType.forName(GitBlit.getString(Keys.web.diffStyle,
-				DiffOutputType.GITBLIT.name()));
-
-		RevCommit commit = null, otherCommit = null;
-
-		if( objectId.contains("..") )
-		{
-			String[] parts = objectId.split("\\.\\.");
-			commit = getCommit(r, parts[0]);
-			otherCommit = getCommit(r, parts[1]);
-		}
-		else
-		{
-			commit = getCommit();
-		}
-
-		String diff;
-
-		if(otherCommit == null)
-		{
-			diff = DiffUtils.getCommitDiff(r, commit, diffType);
-		}
-		else
-		{
-			diff = DiffUtils.getDiff(r, commit, otherCommit, diffType);
-		}
-
-		List<String> parents = new ArrayList<String>();
-		if (commit.getParentCount() > 0) {
-			for (RevCommit parent : commit.getParents()) {
-				parents.add(parent.name());
-			}
-		}
-
-		// commit page links
-		if (parents.size() == 0) {
-			add(new Label("parentLink", getString("gb.none")));
-		} else {
-			add(new LinkPanel("parentLink", null, parents.get(0).substring(0, 8),
-					CommitDiffPage.class, newCommitParameter(parents.get(0))));
-		}
-		add(new BookmarkablePageLink<Void>("patchLink", PatchPage.class,
-				WicketUtils.newObjectParameter(repositoryName, objectId)));
-		add(new BookmarkablePageLink<Void>("commitLink", CommitPage.class,
-				WicketUtils.newObjectParameter(repositoryName, objectId)));
-
-		add(new CommitHeaderPanel("commitHeader", repositoryName, commit));
-
-		// changed paths list
-		List<PathChangeModel> paths;
-
-		if( otherCommit == null )
-		{
-			paths = JGitUtils.getFilesInCommit(r, commit);
-		}
-		else
-		{
-			paths = JGitUtils.getFilesInCommit(r, otherCommit);
-		}
-
-		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;
-
-			public void populateItem(final Item<PathChangeModel> item) {
-				final PathChangeModel entry = item.getModelObject();
-				Label changeType = new Label("changeType", "");
-				WicketUtils.setChangeTypeCssClass(changeType, entry.changeType);
-				setChangeTypeTooltip(changeType, entry.changeType);
-				item.add(changeType);
-
-				boolean hasSubmodule = false;
-				String submodulePath = null;
-				if (entry.isTree()) {
-					// tree
-					item.add(new LinkPanel("pathName", null, entry.path, TreePage.class,
-							WicketUtils
-									.newPathParameter(repositoryName, entry.commitId, entry.path)));
-				} else if (entry.isSubmodule()) {
-					// submodule
-					String submoduleId = entry.objectId;
-					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));
-				} else {
-					// blob
-					item.add(new LinkPanel("pathName", "list", entry.path, BlobPage.class,
-							WicketUtils
-									.newPathParameter(repositoryName, entry.commitId, entry.path)));
-				}
-
-				// quick links
-				if (entry.isSubmodule()) {
-					// submodule
-					item.add(new ExternalLink("patch", "").setEnabled(false));
-					item.add(new BookmarkablePageLink<Void>("view", CommitPage.class, WicketUtils
-							.newObjectParameter(submodulePath, entry.objectId)).setEnabled(hasSubmodule));
-					item.add(new ExternalLink("blame", "").setEnabled(false));
-					item.add(new BookmarkablePageLink<Void>("history", HistoryPage.class, WicketUtils
-							.newPathParameter(repositoryName, entry.commitId, entry.path)));
-				} else {
-					// tree or blob
-					item.add(new BookmarkablePageLink<Void>("patch", PatchPage.class, WicketUtils
-							.newPathParameter(repositoryName, entry.commitId, entry.path)));
-					item.add(new BookmarkablePageLink<Void>("view", BlobPage.class, WicketUtils
-							.newPathParameter(repositoryName, entry.commitId, entry.path)));
-					item.add(new BookmarkablePageLink<Void>("blame", BlamePage.class, WicketUtils
-							.newPathParameter(repositoryName, entry.commitId, entry.path)));
-					item.add(new BookmarkablePageLink<Void>("history", HistoryPage.class, WicketUtils
-							.newPathParameter(repositoryName, entry.commitId, entry.path))
-							.setEnabled(!entry.changeType.equals(ChangeType.ADD)));
-				}
-				WicketUtils.setAlternatingBackground(item, counter);
-				counter++;
-			}
-		};
-		add(pathsView);
-		add(new Label("diffText", diff).setEscapeModelStrings(false));
-	}
-
-	@Override
-	protected String getPageName() {
-		return getString("gb.commitdiff");
-	}
-
-	private RevCommit getCommit(Repository r, String rev)
-	{
-		RevCommit otherCommit = JGitUtils.getCommit(r, rev);
-		if (otherCommit == null) {
-			error(MessageFormat.format(getString("gb.failedToFindCommit"), rev, repositoryName, getPageName()), true);
-		}
-		return otherCommit;
-	}
-}
diff --git a/src/com/gitblit/wicket/pages/CommitPage.html b/src/com/gitblit/wicket/pages/CommitPage.html
deleted file mode 100644
index 79a038c..0000000
--- a/src/com/gitblit/wicket/pages/CommitPage.html
+++ /dev/null
@@ -1,99 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml"  
-      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"  
-      xml:lang="en"  
-      lang="en"> 
-
-<body>
-<wicket:extend>
-
-	<!-- commit nav links -->	
-	<div class="page_nav2">
-		<wicket:message key="gb.parent"></wicket:message>: <span wicket:id="parentLink">[parent link]</span> | <a wicket:id="patchLink"><wicket:message key="gb.patch"></wicket:message></a> | <span wicket:id="commitdiffLink">[commitdiff link]</span>
-	</div>	
-	
-	<!-- commit header -->
-	<div wicket:id="commitHeader">[commit header]</div>
-	
-	<div class="row">
-	
-	
-	<div class="span10">
-	<!-- commit info -->
-	<table class="plain">
-		<tr><th><wicket:message key="gb.refs">refs</wicket:message></th><td><div wicket:id="refsPanel">[references]</div></td></tr>
-		<tr><th><wicket:message key="gb.author">author</wicket:message></th><td><span class="sha1" wicket:id="commitAuthor">[author</span></td></tr>
-		<tr><th></th><td><span class="sha1" wicket:id="commitAuthorDate">[author date]</span></td></tr>
-		<tr><th><wicket:message key="gb.committer">committer</wicket:message></th><td><span class="sha1" wicket:id="commitCommitter">[committer]</span></td></tr>
-		<tr><th></th><td><span class="sha1" wicket:id="commitCommitterDate">[commit date]</span></td></tr>
-		<tr class="hidden-phone"><th><wicket:message key="gb.commit">commit</wicket:message></th><td><span class="sha1" wicket:id="commitId">[commit id]</span></td></tr>
-		<tr class="hidden-phone"><th><wicket:message key="gb.tree">tree</wicket:message></th>
-			<td><span class="sha1" wicket:id="commitTree">[commit tree]</span>
-				<span class="link">
-					<a wicket:id="treeLink"><wicket:message key="gb.tree"></wicket:message></a> | <span wicket:id="compressedLinks"></span>
-				</span>
-			</td></tr>
-		<tr class="hidden-phone"><th valign="top"><wicket:message key="gb.parent">parent</wicket:message></th>
-			<td>
-				<span wicket:id="commitParents">
-					<span class="sha1" wicket:id="commitParent">[commit parents]</span>
-					<span class="link">
-						<a wicket:id="view"><wicket:message key="gb.view"></wicket:message></a> | <a wicket:id="diff"><wicket:message key="gb.diff"></wicket:message></a>
-					</span><br/>			
-				</span>
-			</td>
-		</tr>
-	</table>
-	</div>
-	
-	</div>
-	
-	<!-- full message -->
-	<div class="commit_message" wicket:id="fullMessage">[commit message]</div>
-
-	<!--  git notes -->
-	<table class="gitnotes">
-		<tr wicket:id="notes">
-			<td class="info">
-				<table>
-					<tr><td><span wicket:id="refName"></span></td></tr>
-					<tr><td><span class="sha1" wicket:id="authorName"></span></td></tr>
-					<tr><td><span class="sha1" wicket:id="authorDate"></span></td></tr>
-				</table>
-				<!--  Note Author Gravatar -->
-				<span style="vertical-align: top;" wicket:id="noteAuthorAvatar" />				
-			</td>
-			<td class="message"><span class="sha1" wicket:id="noteContent"></span></td>
-		</tr>
-	</table>
-	
-	<!--  commit legend -->
-	<div class="hidden-phone" style="text-align:right;" wicket:id="commitLegend"></div>
-	
-	<!-- header -->
-	<div class="header"><i class="icon-file"></i> <wicket:message key="gb.changedFiles">[changed files]</wicket:message></div>
-	
-	<!-- changed paths -->
-	<table class="pretty">
-		<tr wicket:id="changedPath">
-			<td class="changeType"><span wicket:id="changeType">[change type]</span></td>
-			<td class="path"><span wicket:id="pathName">[commit path]</span></td>			
-			<td class="hidden-phone rightAlign">
-				<span class="link">
-					<a wicket:id="diff"><wicket:message key="gb.diff"></wicket:message></a> | <a wicket:id="view"><wicket:message key="gb.view"></wicket:message></a> | <a wicket:id="blame"><wicket:message key="gb.blame"></wicket:message></a> | <a wicket:id="history"><wicket:message key="gb.history"></wicket:message></a>
-				</span>
-			</td>
-		</tr>
-	</table>
-	
-	<wicket:fragment wicket:id="fullPersonIdent">
-		<span wicket:id="personName"></span><span wicket:id="personAddress"></span>
-	</wicket:fragment>
-	
-	<wicket:fragment wicket:id="partialPersonIdent">
-		<span wicket:id="personName"></span>
-	</wicket:fragment>
-	
-</wicket:extend>
-</body>
-</html>
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/pages/CommitPage.java b/src/com/gitblit/wicket/pages/CommitPage.java
deleted file mode 100644
index c5a24c8..0000000
--- a/src/com/gitblit/wicket/pages/CommitPage.java
+++ /dev/null
@@ -1,223 +0,0 @@
-/*
- * 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.wicket.pages;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-import org.apache.wicket.PageParameters;
-import org.apache.wicket.markup.html.basic.Label;
-import org.apache.wicket.markup.html.link.BookmarkablePageLink;
-import org.apache.wicket.markup.html.link.ExternalLink;
-import org.apache.wicket.markup.repeater.Item;
-import org.apache.wicket.markup.repeater.data.DataView;
-import org.apache.wicket.markup.repeater.data.ListDataProvider;
-import org.apache.wicket.model.StringResourceModel;
-import org.eclipse.jgit.diff.DiffEntry.ChangeType;
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.revwalk.RevCommit;
-
-import com.gitblit.Constants;
-import com.gitblit.GitBlit;
-import com.gitblit.models.GitNote;
-import com.gitblit.models.PathModel.PathChangeModel;
-import com.gitblit.models.SubmoduleModel;
-import com.gitblit.utils.JGitUtils;
-import com.gitblit.wicket.WicketUtils;
-import com.gitblit.wicket.panels.CommitHeaderPanel;
-import com.gitblit.wicket.panels.CommitLegendPanel;
-import com.gitblit.wicket.panels.CompressedDownloadsPanel;
-import com.gitblit.wicket.panels.GravatarImage;
-import com.gitblit.wicket.panels.LinkPanel;
-import com.gitblit.wicket.panels.RefsPanel;
-
-public class CommitPage extends RepositoryPage {
-
-	public CommitPage(PageParameters params) {
-		super(params);
-
-		Repository r = getRepository();
-		RevCommit c = getCommit();
-		
-		List<String> parents = new ArrayList<String>();
-		if (c.getParentCount() > 0) {
-			for (RevCommit parent : c.getParents()) {
-				parents.add(parent.name());
-			}
-		}
-
-		// commit page links
-		if (parents.size() == 0) {
-			add(new Label("parentLink", "none"));
-			add(new Label("commitdiffLink", getString("gb.commitdiff")));
-		} else {
-			add(new LinkPanel("parentLink", null, getShortObjectId(parents.get(0)),
-					CommitPage.class, newCommitParameter(parents.get(0))));
-			add(new LinkPanel("commitdiffLink", null, new StringResourceModel("gb.commitdiff",
-					this, null), CommitDiffPage.class, WicketUtils.newObjectParameter(
-					repositoryName, objectId)));
-		}
-		add(new BookmarkablePageLink<Void>("patchLink", PatchPage.class,
-				WicketUtils.newObjectParameter(repositoryName, objectId)));
-
-		add(new CommitHeaderPanel("commitHeader", repositoryName, c));
-
-		addRefs(r, c);
-
-		// author
-		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",
-				c.getCommitterIdent().getWhen(), getTimeZone(), getTimeUtils()));
-
-		add(new Label("commitId", c.getName()));
-
-		add(new LinkPanel("commitTree", "list", c.getTree().getName(), TreePage.class,
-				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
-		ListDataProvider<String> parentsDp = new ListDataProvider<String>(parents);
-		DataView<String> parentsView = new DataView<String>("commitParents", parentsDp) {
-			private static final long serialVersionUID = 1L;
-
-			public void populateItem(final Item<String> item) {
-				String entry = item.getModelObject();
-				item.add(new LinkPanel("commitParent", "list", entry, CommitPage.class,
-						newCommitParameter(entry)));
-				item.add(new BookmarkablePageLink<Void>("view", CommitPage.class,
-						newCommitParameter(entry)));
-				item.add(new BookmarkablePageLink<Void>("diff", CommitDiffPage.class,
-						newCommitParameter(entry)));
-			}
-		};
-		add(parentsView);
-
-		addFullText("fullMessage", c.getFullMessage(), true);
-
-		// git notes
-		List<GitNote> notes = JGitUtils.getNotesOnCommit(r, c);
-		ListDataProvider<GitNote> notesDp = new ListDataProvider<GitNote>(notes);
-		DataView<GitNote> notesView = new DataView<GitNote>("notes", notesDp) {
-			private static final long serialVersionUID = 1L;
-
-			public void populateItem(final Item<GitNote> item) {
-				GitNote entry = item.getModelObject();
-				item.add(new RefsPanel("refName", repositoryName, Arrays.asList(entry.notesRef)));
-				item.add(createPersonPanel("authorName", entry.notesRef.getAuthorIdent(),
-						Constants.SearchType.AUTHOR));
-				item.add(new GravatarImage("noteAuthorAvatar", entry.notesRef.getAuthorIdent()));
-				item.add(WicketUtils.createTimestampLabel("authorDate", entry.notesRef
-						.getAuthorIdent().getWhen(), getTimeZone(), getTimeUtils()));
-				item.add(new Label("noteContent", GitBlit.self().processCommitMessage(
-						repositoryName, entry.content)).setEscapeModelStrings(false));
-			}
-		};
-		add(notesView.setVisible(notes.size() > 0));
-
-		// changed paths list
-		List<PathChangeModel> paths = JGitUtils.getFilesInCommit(r, c);
-		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;
-
-			public void populateItem(final Item<PathChangeModel> item) {
-				final PathChangeModel entry = item.getModelObject();
-				Label changeType = new Label("changeType", "");
-				WicketUtils.setChangeTypeCssClass(changeType, entry.changeType);
-				setChangeTypeTooltip(changeType, entry.changeType);
-				item.add(changeType);
-				
-				boolean hasSubmodule = false;
-				String submodulePath = null;
-				if (entry.isTree()) {
-					// tree
-					item.add(new LinkPanel("pathName", null, entry.path, TreePage.class,
-							WicketUtils
-									.newPathParameter(repositoryName, entry.commitId, entry.path)));
-				} else if (entry.isSubmodule()) {
-					// submodule
-					String submoduleId = entry.objectId;
-					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));
-				} else {
-					// blob
-					String displayPath = entry.path;
-					String path = entry.path;
-					if (entry.isSymlink()) {
-						path = JGitUtils.getStringContent(getRepository(), getCommit().getTree(), path);
-						displayPath = entry.path + " -> " + path;
-					}
-					item.add(new LinkPanel("pathName", "list", displayPath, BlobPage.class,
-							WicketUtils
-									.newPathParameter(repositoryName, entry.commitId, path)));
-				}
-				
-				// quick links
-				if (entry.isSubmodule()) {
-					// submodule					
-					item.add(new BookmarkablePageLink<Void>("diff", BlobDiffPage.class, WicketUtils
-							.newPathParameter(repositoryName, entry.commitId, entry.path))
-							.setEnabled(!entry.changeType.equals(ChangeType.ADD)));
-					item.add(new BookmarkablePageLink<Void>("view", CommitPage.class, WicketUtils
-							.newObjectParameter(submodulePath, entry.objectId)).setEnabled(hasSubmodule));
-					item.add(new ExternalLink("blame", "").setEnabled(false));
-					item.add(new BookmarkablePageLink<Void>("history", HistoryPage.class, WicketUtils
-							.newPathParameter(repositoryName, entry.commitId, entry.path))
-							.setEnabled(!entry.changeType.equals(ChangeType.ADD)));
-				} else {
-					// tree or blob
-					item.add(new BookmarkablePageLink<Void>("diff", BlobDiffPage.class, WicketUtils
-							.newPathParameter(repositoryName, entry.commitId, entry.path))
-							.setEnabled(!entry.changeType.equals(ChangeType.ADD)
-									&& !entry.changeType.equals(ChangeType.DELETE)));
-					item.add(new BookmarkablePageLink<Void>("view", BlobPage.class, WicketUtils
-							.newPathParameter(repositoryName, entry.commitId, entry.path)));
-					item.add(new BookmarkablePageLink<Void>("blame", BlamePage.class, WicketUtils
-							.newPathParameter(repositoryName, entry.commitId, entry.path))
-							.setEnabled(!entry.changeType.equals(ChangeType.ADD)));
-					item.add(new BookmarkablePageLink<Void>("history", HistoryPage.class, WicketUtils
-							.newPathParameter(repositoryName, entry.commitId, entry.path))
-							.setEnabled(!entry.changeType.equals(ChangeType.ADD)));
-				}
-
-				WicketUtils.setAlternatingBackground(item, counter);
-				counter++;
-			}
-		};
-		add(pathsView);
-	}
-
-	@Override
-	protected String getPageName() {
-		return getString("gb.commit");
-	}
-}
diff --git a/src/com/gitblit/wicket/pages/DocsPage.java b/src/com/gitblit/wicket/pages/DocsPage.java
deleted file mode 100644
index 9ddc98d..0000000
--- a/src/com/gitblit/wicket/pages/DocsPage.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * 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.wicket.pages;
-
-import java.util.List;
-
-import org.apache.wicket.PageParameters;
-import org.apache.wicket.markup.html.basic.Label;
-import org.apache.wicket.markup.html.link.BookmarkablePageLink;
-import org.apache.wicket.markup.repeater.Item;
-import org.apache.wicket.markup.repeater.data.DataView;
-import org.apache.wicket.markup.repeater.data.ListDataProvider;
-import org.eclipse.jgit.lib.Repository;
-
-import com.gitblit.GitBlit;
-import com.gitblit.Keys;
-import com.gitblit.models.PathModel;
-import com.gitblit.utils.ByteFormat;
-import com.gitblit.utils.JGitUtils;
-import com.gitblit.wicket.WicketUtils;
-import com.gitblit.wicket.panels.LinkPanel;
-
-public class DocsPage extends RepositoryPage {
-
-	public DocsPage(PageParameters params) {
-		super(params);
-
-		Repository r = getRepository();
-		List<String> extensions = GitBlit.getStrings(Keys.web.markdownExtensions);
-		List<PathModel> paths = JGitUtils.getDocuments(r, extensions);
-
-		final ByteFormat byteFormat = new ByteFormat();
-
-		add(new Label("header", getString("gb.docs")));
-
-		// documents list
-		ListDataProvider<PathModel> pathsDp = new ListDataProvider<PathModel>(paths);
-		DataView<PathModel> pathsView = new DataView<PathModel>("document", pathsDp) {
-			private static final long serialVersionUID = 1L;
-			int counter;
-
-			public void populateItem(final Item<PathModel> item) {
-				PathModel entry = item.getModelObject();
-				item.add(WicketUtils.newImage("docIcon", "file_world_16x16.png"));
-				item.add(new Label("docSize", byteFormat.format(entry.size)));
-				item.add(new LinkPanel("docName", "list", entry.name, BlobPage.class, WicketUtils
-						.newPathParameter(repositoryName, entry.commitId, entry.path)));
-
-				// links
-				item.add(new BookmarkablePageLink<Void>("view", BlobPage.class, WicketUtils
-						.newPathParameter(repositoryName, entry.commitId, entry.path)));
-				item.add(new BookmarkablePageLink<Void>("raw", RawPage.class, WicketUtils
-						.newPathParameter(repositoryName, entry.commitId, entry.path)));
-				item.add(new BookmarkablePageLink<Void>("blame", BlamePage.class, WicketUtils
-						.newPathParameter(repositoryName, entry.commitId, entry.path)));
-				item.add(new BookmarkablePageLink<Void>("history", HistoryPage.class, WicketUtils
-						.newPathParameter(repositoryName, entry.commitId, entry.path)));
-				WicketUtils.setAlternatingBackground(item, counter);
-				counter++;
-			}
-		};
-		add(pathsView);
-	}
-
-	@Override
-	protected String getPageName() {
-		return getString("gb.docs");
-	}
-}
diff --git a/src/com/gitblit/wicket/pages/EditRepositoryPage.html b/src/com/gitblit/wicket/pages/EditRepositoryPage.html
deleted file mode 100644
index 7fc0de2..0000000
--- a/src/com/gitblit/wicket/pages/EditRepositoryPage.html
+++ /dev/null
@@ -1,111 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml"  
-      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"  
-      xml:lang="en"  
-      lang="en"> 
-
-<wicket:extend>
-<body onload="document.getElementById('name').focus();">
-	<form style="padding-top:5px;" wicket:id="editForm">
-
-<div class="tabbable">
-	<!-- tab titles -->
-	<ul class="nav nav-tabs">
-		<li class="active"><a href="#general" data-toggle="tab"><wicket:message key="gb.general"></wicket:message></a></li>
-		<li><a href="#permissions" data-toggle="tab"><wicket:message key="gb.accessPermissions"></wicket:message></a></li>
-		<li><a href="#federation" data-toggle="tab"><wicket:message key="gb.federation"></wicket:message></a></li>
-		<li><a href="#search" data-toggle="tab"><wicket:message key="gb.search"></wicket:message></a></li>
-		<li><a href="#hooks" data-toggle="tab"><wicket:message key="gb.hookScripts"></wicket:message></a></li>
-	</ul>
-
-	<!-- tab content -->
-	<div class="tab-content">
-
-		<!-- general tab -->
-		<div class="tab-pane active" id="general">
-		<table class="plain">
-			<tbody class="settings">
-				<tr><th><wicket:message key="gb.name"></wicket:message></th><td class="edit"><input class="span4" type="text" wicket:id="name" id="name" size="40" tabindex="1" /> &nbsp;<span class="help-inline"><wicket:message key="gb.nameDescription"></wicket:message></span></td></tr>
-				<tr><th><wicket:message key="gb.description"></wicket:message></th><td class="edit"><input class="span4" type="text" wicket:id="description" size="40" tabindex="2" /></td></tr>
-				<tr><th colspan="2"><hr/></th></tr>
-				<tr><th><wicket:message key="gb.origin"></wicket:message></th><td class="edit"><input class="span5" type="text" wicket:id="origin" size="80" tabindex="3" /></td></tr>
-				<tr><th><wicket:message key="gb.headRef"></wicket:message></th><td class="edit"><select class="span3" wicket:id="HEAD" tabindex="4" /> &nbsp;<span class="help-inline"><wicket:message key="gb.headRefDescription"></wicket:message></span></td></tr>
-				<tr><th><wicket:message key="gb.gcPeriod"></wicket:message></th><td class="edit"><select class="span2" wicket:id="gcPeriod" tabindex="5" /> &nbsp;<span class="help-inline"><wicket:message key="gb.gcPeriodDescription"></wicket:message></span></td></tr>
-				<tr><th><wicket:message key="gb.gcThreshold"></wicket:message></th><td class="edit"><input class="span1" type="text" wicket:id="gcThreshold" tabindex="6" /> &nbsp;<span class="help-inline"><wicket:message key="gb.gcThresholdDescription"></wicket:message></span></td></tr>
-				<tr><th colspan="2"><hr/></th></tr>
-				<tr><th><wicket:message key="gb.enableTickets"></wicket:message></th><td class="edit"><label class="checkbox"><input type="checkbox" wicket:id="useTickets" tabindex="7" /> &nbsp;<span class="help-inline"><wicket:message key="gb.useTicketsDescription"></wicket:message></span></label></td></tr>
-				<tr><th><wicket:message key="gb.enableDocs"></wicket:message></th><td class="edit"><label class="checkbox"><input type="checkbox" wicket:id="useDocs" tabindex="8" /> &nbsp;<span class="help-inline"><wicket:message key="gb.useDocsDescription"></wicket:message></span></label></td></tr>
-				<tr><th><wicket:message key="gb.showRemoteBranches"></wicket:message></th><td class="edit"><label class="checkbox"><input type="checkbox" wicket:id="showRemoteBranches" tabindex="9" /> &nbsp;<span class="help-inline"><wicket:message key="gb.showRemoteBranchesDescription"></wicket:message></span></label></td></tr>
-				<tr><th><wicket:message key="gb.showReadme"></wicket:message></th><td class="edit"><label class="checkbox"><input type="checkbox" wicket:id="showReadme" tabindex="10" /> &nbsp;<span class="help-inline"><wicket:message key="gb.showReadmeDescription"></wicket:message></span></label></td></tr>
-				<tr><th><wicket:message key="gb.skipSizeCalculation"></wicket:message></th><td class="edit"><label class="checkbox"><input type="checkbox" wicket:id="skipSizeCalculation" tabindex="11" /> &nbsp;<span class="help-inline"><wicket:message key="gb.skipSizeCalculationDescription"></wicket:message></span></label></td></tr>
-				<tr><th><wicket:message key="gb.skipSummaryMetrics"></wicket:message></th><td class="edit"><label class="checkbox"><input type="checkbox" wicket:id="skipSummaryMetrics" tabindex="12" /> &nbsp;<span class="help-inline"><wicket:message key="gb.skipSummaryMetricsDescription"></wicket:message></span></label></td></tr>
-				<tr><th><wicket:message key="gb.maxActivityCommits"></wicket:message></th><td class="edit"><select class="span2" wicket:id="maxActivityCommits" tabindex="13" /> &nbsp;<span class="help-inline"><wicket:message key="gb.maxActivityCommitsDescription"></wicket:message></span></td></tr>
-				<tr><th colspan="2"><hr/></th></tr>
-				<tr><th><wicket:message key="gb.mailingLists"></wicket:message></th><td class="edit"><input class="span8" type="text" wicket:id="mailingLists" size="40" tabindex="14" /></td></tr>
-			</tbody>
-		</table>
-		</div>
-
-		<!-- access permissions -->
-		<div class="tab-pane" id="permissions">
-			<table class="plain">
-				<tbody class="settings">
-					<tr><th><wicket:message key="gb.owners"></wicket:message></th><td class="edit"><span wicket:id="owners" tabindex="15" /> </td></tr>
-					<tr><th colspan="2"><hr/></th></tr>
-					<tr><th><wicket:message key="gb.accessRestriction"></wicket:message></th><td class="edit"><select class="span4" wicket:id="accessRestriction" tabindex="16" /></td></tr>
-					<tr><th colspan="2"><hr/></th></tr>
-					<tr><th><wicket:message key="gb.authorizationControl"></wicket:message></th><td style="padding:2px;"><span class="authorizationControl" wicket:id="authorizationControl"></span></td></tr>
-					<tr><th colspan="2"><hr/></th></tr>
-					<tr><th><wicket:message key="gb.isFrozen"></wicket:message></th><td class="edit"><label class="checkbox"><input type="checkbox" wicket:id="isFrozen" tabindex="17" /> &nbsp;<span class="help-inline"><wicket:message key="gb.isFrozenDescription"></wicket:message></span></label></td></tr>
-					<tr><th><wicket:message key="gb.allowForks"></wicket:message></th><td class="edit"><label class="checkbox"><input type="checkbox" wicket:id="allowForks" tabindex="18" /> &nbsp;<span class="help-inline"><wicket:message key="gb.allowForksDescription"></wicket:message></span></label></td></tr>
-					<tr><th><wicket:message key="gb.verifyCommitter"></wicket:message></th><td class="edit"><label class="checkbox"><input type="checkbox" wicket:id="verifyCommitter" tabindex="19" /> &nbsp;<span class="help-inline"><wicket:message key="gb.verifyCommitterDescription"></wicket:message></span><br/><span class="help-inline" style="padding-left:10px;"><wicket:message key="gb.verifyCommitterNote"></wicket:message></span></label></td></tr>
-					<tr><th colspan="2"><hr/></th></tr>
-					<tr><th><wicket:message key="gb.userPermissions"></wicket:message></th><td style="padding:2px;"><span wicket:id="users"></span></td></tr>
-					<tr><th colspan="2"><hr/></th></tr>
-					<tr><th><wicket:message key="gb.teamPermissions"></wicket:message></th><td style="padding:2px;"><span wicket:id="teams"></span></td></tr>
-				</tbody>
-			</table>
-		</div>
-
-		<!-- federation -->
-		<div class="tab-pane" id="federation">
-			<table class="plain">
-				<tbody class="settings">
-					<tr><th><wicket:message key="gb.federationStrategy"></wicket:message></th><td class="edit"><select class="span4" wicket:id="federationStrategy" tabindex="20" /></td></tr>
-					<tr><th><wicket:message key="gb.federationSets"></wicket:message></th><td style="padding:2px;"><span wicket:id="federationSets"></span></td></tr>
-				</tbody>
-			</table>
-		</div>
-
-		<!-- search -->
-		<div class="tab-pane" id="search">
-			<table class="plain">
-				<tbody class="settings">
-					<tr><th><wicket:message key="gb.indexedBranches"></wicket:message></th><td style="padding:2px;"><span wicket:id="indexedBranches"></span></td></tr>
-				</tbody>
-			</table>
-		</div>
-
-		<!-- hooks -->
-		<div class="tab-pane" id="hooks">
-			<table class="plain">
-				<tbody class="settings">
-					<tr><th><wicket:message key="gb.preReceiveScripts"></wicket:message><p></p><span wicket:id="inheritedPreReceive"></span></th><td style="padding:2px;"><span wicket:id="preReceiveScripts"></span></td></tr>
-					<tr><th><wicket:message key="gb.postReceiveScripts"></wicket:message><p></p><span wicket:id="inheritedPostReceive"></span></th><td style="padding:2px;"><span wicket:id="postReceiveScripts"></span></td></tr>
-					<div wicket:id="customFieldsSection">
-						<tr><td colspan="2"><h3><wicket:message key="gb.customFields"></wicket:message> &nbsp;<small><wicket:message key="gb.customFieldsDescription"></wicket:message></small></h3></td></tr>
-						<tr wicket:id="customFieldsListView"><th><span wicket:id="customFieldLabel"></span></th><td class="edit"><input class="span8" type="text" wicket:id="customFieldValue" /></td></tr>
-					</div>
-				</tbody>
-			</table>
-		</div>
-	</div>
-
-	<div class="row">
-		<div class="form-actions"><input class="btn btn-primary" type="submit" value="Save" wicket:message="value:gb.save" wicket:id="save" /> &nbsp; <input class="btn" type="submit" value="Cancel" wicket:message="value:gb.cancel" wicket:id="cancel" /></div>
-	</div>
-</div>
-</form>	
-</body>
-</wicket:extend>
-</html>
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/pages/EditRepositoryPage.java b/src/com/gitblit/wicket/pages/EditRepositoryPage.java
deleted file mode 100644
index d68d655..0000000
--- a/src/com/gitblit/wicket/pages/EditRepositoryPage.java
+++ /dev/null
@@ -1,696 +0,0 @@
-/*
- * 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.wicket.pages;
-
-import java.text.MessageFormat;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import org.apache.wicket.PageParameters;
-import org.apache.wicket.ajax.AjaxRequestTarget;
-import org.apache.wicket.ajax.form.AjaxFormChoiceComponentUpdatingBehavior;
-import org.apache.wicket.ajax.form.AjaxFormComponentUpdatingBehavior;
-import org.apache.wicket.behavior.SimpleAttributeModifier;
-import org.apache.wicket.extensions.markup.html.form.palette.Palette;
-import org.apache.wicket.markup.html.WebMarkupContainer;
-import org.apache.wicket.markup.html.basic.Label;
-import org.apache.wicket.markup.html.form.Button;
-import org.apache.wicket.markup.html.form.CheckBox;
-import org.apache.wicket.markup.html.form.DropDownChoice;
-import org.apache.wicket.markup.html.form.Form;
-import org.apache.wicket.markup.html.form.IChoiceRenderer;
-import org.apache.wicket.markup.html.form.RadioChoice;
-import org.apache.wicket.markup.html.form.TextField;
-import org.apache.wicket.markup.html.list.ListItem;
-import org.apache.wicket.markup.html.list.ListView;
-import org.apache.wicket.model.CompoundPropertyModel;
-import org.apache.wicket.model.IModel;
-import org.apache.wicket.model.Model;
-import org.apache.wicket.model.util.CollectionModel;
-import org.apache.wicket.model.util.ListModel;
-
-import com.gitblit.Constants;
-import com.gitblit.Constants.AccessRestrictionType;
-import com.gitblit.Constants.AuthorizationControl;
-import com.gitblit.Constants.FederationStrategy;
-import com.gitblit.Constants.RegistrantType;
-import com.gitblit.GitBlit;
-import com.gitblit.GitBlitException;
-import com.gitblit.Keys;
-import com.gitblit.models.RegistrantAccessPermission;
-import com.gitblit.models.RepositoryModel;
-import com.gitblit.models.UserModel;
-import com.gitblit.utils.ArrayUtils;
-import com.gitblit.utils.StringUtils;
-import com.gitblit.wicket.GitBlitWebSession;
-import com.gitblit.wicket.StringChoiceRenderer;
-import com.gitblit.wicket.WicketUtils;
-import com.gitblit.wicket.panels.BulletListPanel;
-import com.gitblit.wicket.panels.RegistrantPermissionsPanel;
-
-public class EditRepositoryPage extends RootSubPage {
-
-	private final boolean isCreate;
-
-	private boolean isAdmin;
-	
-	RepositoryModel repositoryModel;
-
-	private IModel<String> mailingLists;
-
-	public EditRepositoryPage() {
-		// create constructor
-		super();
-		isCreate = true;
-		RepositoryModel model = new RepositoryModel();
-		String restriction = GitBlit.getString(Keys.git.defaultAccessRestriction, null);
-		model.accessRestriction = AccessRestrictionType.fromName(restriction);
-		String authorization = GitBlit.getString(Keys.git.defaultAuthorizationControl, null);
-		model.authorizationControl = AuthorizationControl.fromName(authorization);
-		
-		GitBlitWebSession session = GitBlitWebSession.get();
-		UserModel user = session.getUser();
-		if (user != null && user.canCreate() && !user.canAdmin()) {
-			// personal create permissions, inject personal repository path
-			model.name = user.getPersonalPath() + "/";
-			model.projectPath = user.getPersonalPath();
-			model.addOwner(user.username);
-			// personal repositories are private by default
-			model.accessRestriction = AccessRestrictionType.VIEW;
-			model.authorizationControl = AuthorizationControl.NAMED;
-		}
-		
-		setupPage(model);
-		setStatelessHint(false);
-		setOutputMarkupId(true);
-	}
-
-	public EditRepositoryPage(PageParameters params) {
-		// edit constructor
-		super(params);
-		isCreate = false;
-		String name = WicketUtils.getRepositoryName(params);
-		RepositoryModel model = GitBlit.self().getRepositoryModel(name);
-		setupPage(model);
-		setStatelessHint(false);
-		setOutputMarkupId(true);
-	}
-	
-	@Override
-	protected boolean requiresPageMap() {
-		return true;
-	}
-
-	protected void setupPage(RepositoryModel model) {
-		this.repositoryModel = model;
-		
-		// ensure this user can create or edit this repository
-		checkPermissions(repositoryModel);
-
-		List<String> indexedBranches = new ArrayList<String>();
-		List<String> federationSets = new ArrayList<String>();
-		final List<RegistrantAccessPermission> repositoryUsers = new ArrayList<RegistrantAccessPermission>();
-		final List<RegistrantAccessPermission> repositoryTeams = new ArrayList<RegistrantAccessPermission>();
-		List<String> preReceiveScripts = new ArrayList<String>();
-		List<String> postReceiveScripts = new ArrayList<String>();
-
-		GitBlitWebSession session = GitBlitWebSession.get();
-		final UserModel user = session.getUser() == null ? UserModel.ANONYMOUS : session.getUser();
-		final boolean allowEditName = isCreate || isAdmin || repositoryModel.isUsersPersonalRepository(user.username);
-		
-		if (isCreate) {
-			if (user.canAdmin()) {
-				super.setupPage(getString("gb.newRepository"), "");
-			} else {
-				super.setupPage(getString("gb.newRepository"), user.getDisplayName());
-			}
-		} else {
-			super.setupPage(getString("gb.edit"), repositoryModel.name);
-			repositoryUsers.addAll(GitBlit.self().getUserAccessPermissions(repositoryModel));
-			repositoryTeams.addAll(GitBlit.self().getTeamAccessPermissions(repositoryModel));
-			Collections.sort(repositoryUsers);
-			Collections.sort(repositoryTeams);
-			
-			federationSets.addAll(repositoryModel.federationSets);
-			if (!ArrayUtils.isEmpty(repositoryModel.indexedBranches)) {
-				indexedBranches.addAll(repositoryModel.indexedBranches);
-			}
-		}
-
-		final String oldName = repositoryModel.name;
-
-		final RegistrantPermissionsPanel usersPalette = new RegistrantPermissionsPanel("users", 
-				RegistrantType.USER, GitBlit.self().getAllUsernames(), repositoryUsers, getAccessPermissions());
-		final RegistrantPermissionsPanel teamsPalette = new RegistrantPermissionsPanel("teams", 
-				RegistrantType.TEAM, GitBlit.self().getAllTeamnames(), repositoryTeams, getAccessPermissions());
-
-		// owners palette
-		List<String> owners = new ArrayList<String>(repositoryModel.owners);
-		List<String> persons = GitBlit.self().getAllUsernames();
-		final Palette<String> ownersPalette = new Palette<String>("owners", new ListModel<String>(owners), new CollectionModel<String>(
-		      persons), new StringChoiceRenderer(), 12, true);
-		
-		// indexed local branches palette
-		List<String> allLocalBranches = new ArrayList<String>();
-		allLocalBranches.add(Constants.DEFAULT_BRANCH);
-		allLocalBranches.addAll(repositoryModel.getLocalBranches());
-		boolean luceneEnabled = GitBlit.getBoolean(Keys.web.allowLuceneIndexing, true);
-		final Palette<String> indexedBranchesPalette = new Palette<String>("indexedBranches", new ListModel<String>(
-				indexedBranches), new CollectionModel<String>(allLocalBranches),
-				new StringChoiceRenderer(), 8, false);
-		indexedBranchesPalette.setEnabled(luceneEnabled);
-		
-		// federation sets palette
-		List<String> sets = GitBlit.getStrings(Keys.federation.sets);
-		final Palette<String> federationSetsPalette = new Palette<String>("federationSets",
-				new ListModel<String>(federationSets), new CollectionModel<String>(sets),
-				new StringChoiceRenderer(), 8, false);
-
-		// pre-receive palette
-		if (!ArrayUtils.isEmpty(repositoryModel.preReceiveScripts)) {
-			preReceiveScripts.addAll(repositoryModel.preReceiveScripts);
-		}
-		final Palette<String> preReceivePalette = new Palette<String>("preReceiveScripts",
-				new ListModel<String>(preReceiveScripts), new CollectionModel<String>(GitBlit
-						.self().getPreReceiveScriptsUnused(repositoryModel)),
-				new StringChoiceRenderer(), 12, true);
-
-		// post-receive palette
-		if (!ArrayUtils.isEmpty(repositoryModel.postReceiveScripts)) {
-			postReceiveScripts.addAll(repositoryModel.postReceiveScripts);
-		}
-		final Palette<String> postReceivePalette = new Palette<String>("postReceiveScripts",
-				new ListModel<String>(postReceiveScripts), new CollectionModel<String>(GitBlit
-						.self().getPostReceiveScriptsUnused(repositoryModel)),
-				new StringChoiceRenderer(), 12, true);
-		
-		// custom fields
-		final Map<String, String> customFieldsMap = GitBlit.getMap(Keys.groovy.customFields);
-		List<String> customKeys = new ArrayList<String>(customFieldsMap.keySet());
-		final ListView<String> customFieldsListView = new ListView<String>("customFieldsListView", customKeys) {
-			
-			private static final long serialVersionUID = 1L;
-
-			@Override
-			protected void populateItem(ListItem<String> item) {
-				String key = item.getModelObject();
-				item.add(new Label("customFieldLabel", customFieldsMap.get(key)));
-				
-				String value = "";
-				if (repositoryModel.customFields != null && repositoryModel.customFields.containsKey(key)) {
-					value = repositoryModel.customFields.get(key);
-				}
-				TextField<String> field = new TextField<String>("customFieldValue", new Model<String>(value));
-				item.add(field);
-			}
-		};
-		customFieldsListView.setReuseItems(true);
-
-		CompoundPropertyModel<RepositoryModel> rModel = new CompoundPropertyModel<RepositoryModel>(
-				repositoryModel);
-		Form<RepositoryModel> form = new Form<RepositoryModel>("editForm", rModel) {
-
-			private static final long serialVersionUID = 1L;
-
-			@Override
-			protected void onSubmit() {
-				try {
-					// confirm a repository name was entered
-					if (repositoryModel.name == null && StringUtils.isEmpty(repositoryModel.name)) {
-						error(getString("gb.pleaseSetRepositoryName"));
-						return;
-					}
-					
-					// ensure name is trimmed
-					repositoryModel.name = repositoryModel.name.trim();
-					
-					// automatically convert backslashes to forward slashes
-					repositoryModel.name = repositoryModel.name.replace('\\', '/');
-					// Automatically replace // with /
-					repositoryModel.name = repositoryModel.name.replace("//", "/");
-
-					// prohibit folder paths
-					if (repositoryModel.name.startsWith("/")) {
-						error(getString("gb.illegalLeadingSlash"));
-						return;
-					}
-					if (repositoryModel.name.startsWith("../")) {
-						error(getString("gb.illegalRelativeSlash"));
-						return;
-					}
-					if (repositoryModel.name.contains("/../")) {
-						error(getString("gb.illegalRelativeSlash"));
-						return;
-					}					
-					if (repositoryModel.name.endsWith("/")) {
-						repositoryModel.name = repositoryModel.name.substring(0, repositoryModel.name.length() - 1);
-					}
-
-					// confirm valid characters in repository name
-					Character c = StringUtils.findInvalidCharacter(repositoryModel.name);
-					if (c != null) {
-						error(MessageFormat.format(getString("gb.illegalCharacterRepositoryName"),
-								c));
-						return;
-					}
-					
-					if (user.canCreate() && !user.canAdmin() && allowEditName) {
-						// ensure repository name begins with the user's path
-						if (!repositoryModel.name.startsWith(user.getPersonalPath())) {
-							error(MessageFormat.format(getString("gb.illegalPersonalRepositoryLocation"),
-									user.getPersonalPath()));
-							return;
-						}
-						
-						if (repositoryModel.name.equals(user.getPersonalPath())) {
-							// reset path prefix and show error
-							repositoryModel.name = user.getPersonalPath() + "/";
-							error(getString("gb.pleaseSetRepositoryName"));
-							return;
-						}
-					}
-
-					// confirm access restriction selection
-					if (repositoryModel.accessRestriction == null) {
-						error(getString("gb.selectAccessRestriction"));
-						return;
-					}
-
-					// confirm federation strategy selection
-					if (repositoryModel.federationStrategy == null) {
-						error(getString("gb.selectFederationStrategy"));
-						return;
-					}
-
-					// save federation set preferences
-					if (repositoryModel.federationStrategy.exceeds(FederationStrategy.EXCLUDE)) {
-						repositoryModel.federationSets.clear();
-						Iterator<String> sets = federationSetsPalette.getSelectedChoices();
-						while (sets.hasNext()) {
-							repositoryModel.federationSets.add(sets.next());
-						}
-					}
-
-					// set mailing lists
-					String ml = mailingLists.getObject();
-					if (!StringUtils.isEmpty(ml)) {
-						Set<String> list = new HashSet<String>();
-						for (String address : ml.split("(,|\\s)")) {
-							if (StringUtils.isEmpty(address)) {
-								continue;
-							}
-							list.add(address.toLowerCase());
-						}
-						repositoryModel.mailingLists = new ArrayList<String>(list);
-					}
-
-					// indexed branches
-					List<String> indexedBranches = new ArrayList<String>();
-					Iterator<String> branches = indexedBranchesPalette.getSelectedChoices();
-					while (branches.hasNext()) {
-						indexedBranches.add(branches.next());
-					}
-					repositoryModel.indexedBranches = indexedBranches;
-
-					// owners
-					repositoryModel.owners.clear();
-					Iterator<String> owners = ownersPalette.getSelectedChoices();
-					while (owners.hasNext()) {
-						repositoryModel.addOwner(owners.next());
-					}
-					
-					// pre-receive scripts
-					List<String> preReceiveScripts = new ArrayList<String>();
-					Iterator<String> pres = preReceivePalette.getSelectedChoices();
-					while (pres.hasNext()) {
-						preReceiveScripts.add(pres.next());
-					}
-					repositoryModel.preReceiveScripts = preReceiveScripts;
-
-					// post-receive scripts
-					List<String> postReceiveScripts = new ArrayList<String>();
-					Iterator<String> post = postReceivePalette.getSelectedChoices();
-					while (post.hasNext()) {
-						postReceiveScripts.add(post.next());
-					}
-					repositoryModel.postReceiveScripts = postReceiveScripts;
-					
-					// custom fields
-					repositoryModel.customFields = new LinkedHashMap<String, String>();
-					for (int i = 0; i < customFieldsListView.size(); i++) {
-						ListItem<String> child = (ListItem<String>) customFieldsListView.get(i);
-						String key = child.getModelObject();
-
-						TextField<String> field = (TextField<String>) child.get("customFieldValue");
-						String value = field.getValue();
-						
-						repositoryModel.customFields.put(key, value);
-					}
-					
-					// save the repository
-					GitBlit.self().updateRepositoryModel(oldName, repositoryModel, isCreate);
-
-					// repository access permissions
-					if (repositoryModel.accessRestriction.exceeds(AccessRestrictionType.NONE)) {
-						GitBlit.self().setUserAccessPermissions(repositoryModel, repositoryUsers);
-						GitBlit.self().setTeamAccessPermissions(repositoryModel, repositoryTeams);
-					}
-				} catch (GitBlitException e) {
-					error(e.getMessage());
-					return;
-				}
-				setRedirect(false);
-				setResponsePage(RepositoriesPage.class);
-			}
-		};
-
-		// do not let the browser pre-populate these fields
-		form.add(new SimpleAttributeModifier("autocomplete", "off"));
-
-		// field names reflective match RepositoryModel fields
-		form.add(new TextField<String>("name").setEnabled(allowEditName));
-		form.add(new TextField<String>("description"));
-		form.add(ownersPalette);
-		form.add(new CheckBox("allowForks").setEnabled(GitBlit.getBoolean(Keys.web.allowForking, true)));
-		DropDownChoice<AccessRestrictionType> accessRestriction = new DropDownChoice<AccessRestrictionType>("accessRestriction", Arrays
-				.asList(AccessRestrictionType.values()), new AccessRestrictionRenderer());
-		form.add(accessRestriction);
-		form.add(new CheckBox("isFrozen"));
-		// TODO enable origin definition
-		form.add(new TextField<String>("origin").setEnabled(false/* isCreate */));
-		
-		// allow relinking HEAD to a branch or tag other than master on edit repository
-		List<String> availableRefs = new ArrayList<String>();
-		if (!ArrayUtils.isEmpty(repositoryModel.availableRefs)) {
-			availableRefs.addAll(repositoryModel.availableRefs);
-		}
-		form.add(new DropDownChoice<String>("HEAD", availableRefs).setEnabled(availableRefs.size() > 0));
-
-		boolean gcEnabled = GitBlit.getBoolean(Keys.git.enableGarbageCollection, false); 
-		List<Integer> gcPeriods = Arrays.asList(1, 2, 3, 4, 5, 7, 10, 14 );
-		form.add(new DropDownChoice<Integer>("gcPeriod", gcPeriods, new GCPeriodRenderer()).setEnabled(gcEnabled));
-		form.add(new TextField<String>("gcThreshold").setEnabled(gcEnabled));
-
-		// federation strategies - remove ORIGIN choice if this repository has
-		// no origin.
-		List<FederationStrategy> federationStrategies = new ArrayList<FederationStrategy>(
-				Arrays.asList(FederationStrategy.values()));
-		if (StringUtils.isEmpty(repositoryModel.origin)) {
-			federationStrategies.remove(FederationStrategy.FEDERATE_ORIGIN);
-		}
-		form.add(new DropDownChoice<FederationStrategy>("federationStrategy", federationStrategies,
-				new FederationTypeRenderer()));
-		form.add(new CheckBox("useTickets"));
-		form.add(new CheckBox("useDocs"));
-		form.add(new CheckBox("showRemoteBranches"));
-		form.add(new CheckBox("showReadme"));
-		form.add(new CheckBox("skipSizeCalculation"));
-		form.add(new CheckBox("skipSummaryMetrics"));
-		List<Integer> maxActivityCommits  = Arrays.asList(-1, 0, 25, 50, 75, 100, 150, 200, 250, 500 );
-		form.add(new DropDownChoice<Integer>("maxActivityCommits", maxActivityCommits, new MaxActivityCommitsRenderer()));
-
-		mailingLists = new Model<String>(ArrayUtils.isEmpty(repositoryModel.mailingLists) ? ""
-				: StringUtils.flattenStrings(repositoryModel.mailingLists, " "));
-		form.add(new TextField<String>("mailingLists", mailingLists));
-		form.add(indexedBranchesPalette);
-		
-		List<AuthorizationControl> acList = Arrays.asList(AuthorizationControl.values());
-		final RadioChoice<AuthorizationControl> authorizationControl = new RadioChoice<Constants.AuthorizationControl>(
-				"authorizationControl", acList, new AuthorizationControlRenderer());
-		form.add(authorizationControl);
-		
-		final CheckBox verifyCommitter = new CheckBox("verifyCommitter");
-		verifyCommitter.setOutputMarkupId(true);
-		form.add(verifyCommitter);
-
-		form.add(usersPalette);
-		form.add(teamsPalette);
-		form.add(federationSetsPalette);
-		form.add(preReceivePalette);
-		form.add(new BulletListPanel("inheritedPreReceive", getString("gb.inherited"), GitBlit.self()
-				.getPreReceiveScriptsInherited(repositoryModel)));
-		form.add(postReceivePalette);
-		form.add(new BulletListPanel("inheritedPostReceive", getString("gb.inherited"), GitBlit.self()
-				.getPostReceiveScriptsInherited(repositoryModel)));
-		
-		WebMarkupContainer customFieldsSection = new WebMarkupContainer("customFieldsSection");
-		customFieldsSection.add(customFieldsListView);
-		form.add(customFieldsSection.setVisible(!GitBlit.getString(Keys.groovy.customFields, "").isEmpty()));
-		
-		// initial enable/disable of permission controls
-		if (repositoryModel.accessRestriction.equals(AccessRestrictionType.NONE)) {
-			// anonymous everything, disable all controls
-			usersPalette.setEnabled(false);
-			teamsPalette.setEnabled(false);
-			authorizationControl.setEnabled(false);
-			verifyCommitter.setEnabled(false);
-		} else {
-			// authenticated something
-			// enable authorization controls
-			authorizationControl.setEnabled(true);
-			verifyCommitter.setEnabled(true);
-			
-			boolean allowFineGrainedControls = repositoryModel.authorizationControl.equals(AuthorizationControl.NAMED);
-			usersPalette.setEnabled(allowFineGrainedControls);
-			teamsPalette.setEnabled(allowFineGrainedControls);
-		}
-		
-		accessRestriction.add(new AjaxFormComponentUpdatingBehavior("onchange") {
-	           
-			private static final long serialVersionUID = 1L;
-
-			protected void onUpdate(AjaxRequestTarget target) {
-				// enable/disable permissions panel based on access restriction
-				boolean allowAuthorizationControl = repositoryModel.accessRestriction.exceeds(AccessRestrictionType.NONE);
-				authorizationControl.setEnabled(allowAuthorizationControl);
-				verifyCommitter.setEnabled(allowAuthorizationControl);
-				
-				boolean allowFineGrainedControls = allowAuthorizationControl && repositoryModel.authorizationControl.equals(AuthorizationControl.NAMED);
-				usersPalette.setEnabled(allowFineGrainedControls);
-				teamsPalette.setEnabled(allowFineGrainedControls);
-				
-				if (allowFineGrainedControls) {
-					repositoryModel.authorizationControl = AuthorizationControl.NAMED;
-				}
-				
-				target.addComponent(authorizationControl);
-				target.addComponent(verifyCommitter);
-				target.addComponent(usersPalette);
-				target.addComponent(teamsPalette);
-			}
-		});
-		
-		authorizationControl.add(new AjaxFormChoiceComponentUpdatingBehavior() {
-	           
-			private static final long serialVersionUID = 1L;
-
-			protected void onUpdate(AjaxRequestTarget target) {
-				// enable/disable permissions panel based on access restriction
-				boolean allowAuthorizationControl = repositoryModel.accessRestriction.exceeds(AccessRestrictionType.NONE);
-				authorizationControl.setEnabled(allowAuthorizationControl);
-				
-				boolean allowFineGrainedControls = allowAuthorizationControl && repositoryModel.authorizationControl.equals(AuthorizationControl.NAMED);
-				usersPalette.setEnabled(allowFineGrainedControls);
-				teamsPalette.setEnabled(allowFineGrainedControls);
-				
-				if (allowFineGrainedControls) {
-					repositoryModel.authorizationControl = AuthorizationControl.NAMED;
-				}
-				
-				target.addComponent(authorizationControl);
-				target.addComponent(usersPalette);
-				target.addComponent(teamsPalette);
-			}
-		});
-		
-		form.add(new Button("save"));
-		Button cancel = new Button("cancel") {
-			private static final long serialVersionUID = 1L;
-
-			@Override
-			public void onSubmit() {
-				setResponsePage(RepositoriesPage.class);
-			}
-		};
-		cancel.setDefaultFormProcessing(false);
-		form.add(cancel);
-
-		add(form);
-	}
-
-	/**
-	 * Unfortunately must repeat part of AuthorizaitonStrategy here because that
-	 * mechanism does not take PageParameters into consideration, only page
-	 * instantiation.
-	 * 
-	 * Repository Owners should be able to edit their repository.
-	 */
-	private void checkPermissions(RepositoryModel model) {
-		boolean authenticateAdmin = GitBlit.getBoolean(Keys.web.authenticateAdminPages, true);
-		boolean allowAdmin = GitBlit.getBoolean(Keys.web.allowAdministration, true);
-
-		GitBlitWebSession session = GitBlitWebSession.get();
-		UserModel user = session.getUser();
-
-		if (allowAdmin) {
-			if (authenticateAdmin) {
-				if (user == null) {
-					// No Login Available
-					error(getString("gb.errorAdminLoginRequired"), true);
-				}
-				if (isCreate) {
-					// Create Repository
-					if (!user.canCreate() && !user.canAdmin()) {
-						// Only administrators or permitted users may create
-						error(getString("gb.errorOnlyAdminMayCreateRepository"), true);
-					}
-				} else {
-					// Edit Repository
-					if (user.canAdmin()) {
-						// Admins can edit everything
-						isAdmin = true;
-						return;
-					} else {
-						if (!model.isOwner(user.username)) {
-							// User is not an Admin nor Owner
-							error(getString("gb.errorOnlyAdminOrOwnerMayEditRepository"), true);
-						}
-					}
-				}
-			}
-		} else {
-			// No Administration Permitted
-			error(getString("gb.errorAdministrationDisabled"), true);
-		}
-	}
-
-	private class AccessRestrictionRenderer implements IChoiceRenderer<AccessRestrictionType> {
-
-		private static final long serialVersionUID = 1L;
-
-		private final Map<AccessRestrictionType, String> map;
-
-		public AccessRestrictionRenderer() {
-			map = getAccessRestrictions();
-		}
-
-		@Override
-		public String getDisplayValue(AccessRestrictionType type) {
-			return map.get(type);
-		}
-
-		@Override
-		public String getIdValue(AccessRestrictionType type, int index) {
-			return Integer.toString(index);
-		}
-	}
-
-	private class FederationTypeRenderer implements IChoiceRenderer<FederationStrategy> {
-
-		private static final long serialVersionUID = 1L;
-
-		private final Map<FederationStrategy, String> map;
-
-		public FederationTypeRenderer() {
-			map = getFederationTypes();
-		}
-
-		@Override
-		public String getDisplayValue(FederationStrategy type) {
-			return map.get(type);
-		}
-
-		@Override
-		public String getIdValue(FederationStrategy type, int index) {
-			return Integer.toString(index);
-		}
-	}
-	
-	private class AuthorizationControlRenderer implements IChoiceRenderer<AuthorizationControl> {
-
-		private static final long serialVersionUID = 1L;
-
-		private final Map<AuthorizationControl, String> map;
-
-		public AuthorizationControlRenderer() {
-			map = getAuthorizationControls();
-		}
-
-		@Override
-		public String getDisplayValue(AuthorizationControl type) {
-			return map.get(type);
-		}
-
-		@Override
-		public String getIdValue(AuthorizationControl type, int index) {
-			return Integer.toString(index);
-		}
-	}
-	
-	private class GCPeriodRenderer implements IChoiceRenderer<Integer> {
-
-		private static final long serialVersionUID = 1L;
-
-		public GCPeriodRenderer() {
-		}
-
-		@Override
-		public String getDisplayValue(Integer value) {
-			if (value == 1) {
-				return getString("gb.duration.oneDay");
-			} else {
-				return MessageFormat.format(getString("gb.duration.days"), value);
-			}
-		}
-
-		@Override
-		public String getIdValue(Integer value, int index) {
-			return Integer.toString(index);
-		}
-	}
-	
-	private class MaxActivityCommitsRenderer implements IChoiceRenderer<Integer> {
-
-		private static final long serialVersionUID = 1L;
-
-		public MaxActivityCommitsRenderer() {
-		}
-
-		@Override
-		public String getDisplayValue(Integer value) {
-			if (value == -1) {
-				return getString("gb.excludeFromActivity");
-			} else if (value == 0) {
-				return getString("gb.noMaximum");
-			} else {
-				return value + " " + getString("gb.commits");
-			}
-		}
-
-		@Override
-		public String getIdValue(Integer value, int index) {
-			return Integer.toString(index);
-		}
-	}
-	
-}
diff --git a/src/com/gitblit/wicket/pages/EditTeamPage.java b/src/com/gitblit/wicket/pages/EditTeamPage.java
deleted file mode 100644
index 8344d38..0000000
--- a/src/com/gitblit/wicket/pages/EditTeamPage.java
+++ /dev/null
@@ -1,250 +0,0 @@
-/*
- * 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.wicket.pages;
-
-import java.text.MessageFormat;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Set;
-
-import org.apache.wicket.PageParameters;
-import org.apache.wicket.behavior.SimpleAttributeModifier;
-import org.apache.wicket.extensions.markup.html.form.palette.Palette;
-import org.apache.wicket.markup.html.form.Button;
-import org.apache.wicket.markup.html.form.CheckBox;
-import org.apache.wicket.markup.html.form.Form;
-import org.apache.wicket.markup.html.form.TextField;
-import org.apache.wicket.model.CompoundPropertyModel;
-import org.apache.wicket.model.IModel;
-import org.apache.wicket.model.Model;
-import org.apache.wicket.model.util.CollectionModel;
-import org.apache.wicket.model.util.ListModel;
-
-import com.gitblit.GitBlit;
-import com.gitblit.GitBlitException;
-import com.gitblit.Keys;
-import com.gitblit.Constants.RegistrantType;
-import com.gitblit.models.RegistrantAccessPermission;
-import com.gitblit.models.TeamModel;
-import com.gitblit.utils.StringUtils;
-import com.gitblit.wicket.RequiresAdminRole;
-import com.gitblit.wicket.StringChoiceRenderer;
-import com.gitblit.wicket.WicketUtils;
-import com.gitblit.wicket.panels.BulletListPanel;
-import com.gitblit.wicket.panels.RegistrantPermissionsPanel;
-
-@RequiresAdminRole
-public class EditTeamPage extends RootSubPage {
-
-	private final boolean isCreate;
-
-	private IModel<String> mailingLists;
-
-	public EditTeamPage() {
-		// create constructor
-		super();
-		isCreate = true;
-		setupPage(new TeamModel(""));
-		setStatelessHint(false);
-		setOutputMarkupId(true);
-	}
-
-	public EditTeamPage(PageParameters params) {
-		// edit constructor
-		super(params);
-		isCreate = false;
-		String name = WicketUtils.getTeamname(params);
-		TeamModel model = GitBlit.self().getTeamModel(name);
-		setupPage(model);
-		setStatelessHint(false);
-		setOutputMarkupId(true);
-	}
-
-	@Override
-	protected boolean requiresPageMap() {
-		return true;
-	}
-
-	protected void setupPage(final TeamModel teamModel) {
-		if (isCreate) {
-			super.setupPage(getString("gb.newTeam"), "");
-		} else {
-			super.setupPage(getString("gb.edit"), teamModel.name);
-		}
-
-		CompoundPropertyModel<TeamModel> model = new CompoundPropertyModel<TeamModel>(teamModel);
-
-		List<String> repos = getAccessRestrictedRepositoryList(true, null);
-
-		List<String> teamUsers = new ArrayList<String>(teamModel.users);
-		Collections.sort(teamUsers);
-		List<String> preReceiveScripts = new ArrayList<String>();
-		List<String> postReceiveScripts = new ArrayList<String>();
-
-		final String oldName = teamModel.name;
-		final List<RegistrantAccessPermission> permissions = teamModel.getRepositoryPermissions();
-
-		// users palette
-		final Palette<String> users = new Palette<String>("users", new ListModel<String>(
-				new ArrayList<String>(teamUsers)), new CollectionModel<String>(GitBlit.self()
-				.getAllUsernames()), new StringChoiceRenderer(), 10, false);
-
-		// pre-receive palette
-		if (teamModel.preReceiveScripts != null) {
-			preReceiveScripts.addAll(teamModel.preReceiveScripts);
-		}
-		final Palette<String> preReceivePalette = new Palette<String>("preReceiveScripts",
-				new ListModel<String>(preReceiveScripts), new CollectionModel<String>(GitBlit
-						.self().getPreReceiveScriptsUnused(null)), new StringChoiceRenderer(),
-						12, true);
-
-		// post-receive palette
-		if (teamModel.postReceiveScripts != null) {
-			postReceiveScripts.addAll(teamModel.postReceiveScripts);
-		}
-		final Palette<String> postReceivePalette = new Palette<String>("postReceiveScripts",
-				new ListModel<String>(postReceiveScripts), new CollectionModel<String>(GitBlit
-						.self().getPostReceiveScriptsUnused(null)), new StringChoiceRenderer(),
-								12, true);
-
-		Form<TeamModel> form = new Form<TeamModel>("editForm", model) {
-
-			private static final long serialVersionUID = 1L;
-
-			/*
-			 * (non-Javadoc)
-			 * 
-			 * @see org.apache.wicket.markup.html.form.Form#onSubmit()
-			 */
-			@Override
-			protected void onSubmit() {
-				String teamname = teamModel.name;
-				if (StringUtils.isEmpty(teamname)) {
-					error(getString("gb.pleaseSetTeamName"));
-					return;
-				}
-				if (isCreate) {
-					TeamModel model = GitBlit.self().getTeamModel(teamname);
-					if (model != null) {
-						error(MessageFormat.format(getString("gb.teamNameUnavailable"), teamname));
-						return;
-					}
-				}
-				// update team permissions
-				for (RegistrantAccessPermission repositoryPermission : permissions) {
-					teamModel.setRepositoryPermission(repositoryPermission.registrant, repositoryPermission.permission);
-				}
-
-				Iterator<String> selectedUsers = users.getSelectedChoices();
-				List<String> members = new ArrayList<String>();
-				while (selectedUsers.hasNext()) {
-					members.add(selectedUsers.next().toLowerCase());
-				}
-				teamModel.users.clear();
-				teamModel.users.addAll(members);
-
-				// set mailing lists
-				String ml = mailingLists.getObject();
-				if (!StringUtils.isEmpty(ml)) {
-					Set<String> list = new HashSet<String>();
-					for (String address : ml.split("(,|\\s)")) {
-						if (StringUtils.isEmpty(address)) {
-							continue;
-						}
-						list.add(address.toLowerCase());
-					}
-					teamModel.mailingLists.clear();
-					teamModel.mailingLists.addAll(list);
-				}
-
-				// pre-receive scripts
-				List<String> preReceiveScripts = new ArrayList<String>();
-				Iterator<String> pres = preReceivePalette.getSelectedChoices();
-				while (pres.hasNext()) {
-					preReceiveScripts.add(pres.next());
-				}
-				teamModel.preReceiveScripts.clear();
-				teamModel.preReceiveScripts.addAll(preReceiveScripts);
-
-				// post-receive scripts
-				List<String> postReceiveScripts = new ArrayList<String>();
-				Iterator<String> post = postReceivePalette.getSelectedChoices();
-				while (post.hasNext()) {
-					postReceiveScripts.add(post.next());
-				}
-				teamModel.postReceiveScripts.clear();
-				teamModel.postReceiveScripts.addAll(postReceiveScripts);
-
-				try {
-					GitBlit.self().updateTeamModel(oldName, teamModel, isCreate);
-				} catch (GitBlitException e) {
-					error(e.getMessage());
-					return;
-				}
-				setRedirect(false);
-				if (isCreate) {
-					// create another team
-					info(MessageFormat.format(getString("gb.teamCreated"),
-							teamModel.name));
-				}
-				// back to users page
-				setResponsePage(UsersPage.class);
-			}
-		};
-
-		// do not let the browser pre-populate these fields
-		form.add(new SimpleAttributeModifier("autocomplete", "off"));
-
-		// not all user services support manipulating team memberships
-		boolean editMemberships = GitBlit.self().supportsTeamMembershipChanges(null);
-		
-		// field names reflective match TeamModel fields
-		form.add(new TextField<String>("name"));
-		form.add(new CheckBox("canAdmin"));
-		form.add(new CheckBox("canFork").setEnabled(GitBlit.getBoolean(Keys.web.allowForking, true)));
-		form.add(new CheckBox("canCreate"));
-		form.add(users.setEnabled(editMemberships));
-		mailingLists = new Model<String>(teamModel.mailingLists == null ? ""
-				: StringUtils.flattenStrings(teamModel.mailingLists, " "));
-		form.add(new TextField<String>("mailingLists", mailingLists));
-
-		form.add(new RegistrantPermissionsPanel("repositories", RegistrantType.REPOSITORY,
-				repos, permissions, getAccessPermissions()));
-		form.add(preReceivePalette);
-		form.add(new BulletListPanel("inheritedPreReceive", "inherited", GitBlit.self()
-				.getPreReceiveScriptsInherited(null)));
-		form.add(postReceivePalette);
-		form.add(new BulletListPanel("inheritedPostReceive", "inherited", GitBlit.self()
-				.getPostReceiveScriptsInherited(null)));
-
-		form.add(new Button("save"));
-		Button cancel = new Button("cancel") {
-			private static final long serialVersionUID = 1L;
-
-			@Override
-			public void onSubmit() {
-				setResponsePage(UsersPage.class);
-			}
-		};
-		cancel.setDefaultFormProcessing(false);
-		form.add(cancel);
-
-		add(form);
-	}
-}
diff --git a/src/com/gitblit/wicket/pages/EditUserPage.java b/src/com/gitblit/wicket/pages/EditUserPage.java
deleted file mode 100644
index c060f23..0000000
--- a/src/com/gitblit/wicket/pages/EditUserPage.java
+++ /dev/null
@@ -1,261 +0,0 @@
-/*
- * 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.wicket.pages;
-
-import java.text.MessageFormat;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.List;
-
-import org.apache.wicket.PageParameters;
-import org.apache.wicket.behavior.SimpleAttributeModifier;
-import org.apache.wicket.extensions.markup.html.form.palette.Palette;
-import org.apache.wicket.markup.html.form.Button;
-import org.apache.wicket.markup.html.form.CheckBox;
-import org.apache.wicket.markup.html.form.Form;
-import org.apache.wicket.markup.html.form.PasswordTextField;
-import org.apache.wicket.markup.html.form.TextField;
-import org.apache.wicket.model.CompoundPropertyModel;
-import org.apache.wicket.model.Model;
-import org.apache.wicket.model.util.CollectionModel;
-import org.apache.wicket.model.util.ListModel;
-
-import com.gitblit.Constants.RegistrantType;
-import com.gitblit.GitBlit;
-import com.gitblit.GitBlitException;
-import com.gitblit.Keys;
-import com.gitblit.models.RegistrantAccessPermission;
-import com.gitblit.models.TeamModel;
-import com.gitblit.models.UserModel;
-import com.gitblit.utils.StringUtils;
-import com.gitblit.wicket.RequiresAdminRole;
-import com.gitblit.wicket.StringChoiceRenderer;
-import com.gitblit.wicket.WicketUtils;
-import com.gitblit.wicket.panels.RegistrantPermissionsPanel;
-
-@RequiresAdminRole
-public class EditUserPage extends RootSubPage {
-
-	private final boolean isCreate;
-	
-	public EditUserPage() {
-		// create constructor
-		super();
-		if (!GitBlit.self().supportsAddUser()) {
-			error(MessageFormat.format(getString("gb.userServiceDoesNotPermitAddUser"),
-					GitBlit.getString(Keys.realm.userService, "${baseFolder}/users.conf")), true);
-		}
-		isCreate = true;
-		setupPage(new UserModel(""));
-		setStatelessHint(false);
-		setOutputMarkupId(true);
-	}
-
-	public EditUserPage(PageParameters params) {
-		// edit constructor
-		super(params);
-		isCreate = false;
-		String name = WicketUtils.getUsername(params);
-		UserModel model = GitBlit.self().getUserModel(name);
-		setupPage(model);
-		setStatelessHint(false);
-		setOutputMarkupId(true);
-	}
-	
-	@Override
-	protected boolean requiresPageMap() {
-		return true;
-	}
-
-	protected void setupPage(final UserModel userModel) {
-		if (isCreate) {
-			super.setupPage(getString("gb.newUser"), "");
-		} else {
-			super.setupPage(getString("gb.edit"), userModel.username);
-		}
-
-		final Model<String> confirmPassword = new Model<String>(
-				StringUtils.isEmpty(userModel.password) ? "" : userModel.password);
-		CompoundPropertyModel<UserModel> model = new CompoundPropertyModel<UserModel>(userModel);
-
-		// build list of projects including all repositories wildcards
-		List<String> repos = getAccessRestrictedRepositoryList(true, userModel);
-		
-		List<String> userTeams = new ArrayList<String>();
-		for (TeamModel team : userModel.teams) {
-			userTeams.add(team.name);
-		}
-		Collections.sort(userTeams);
-		
-		final String oldName = userModel.username;
-		final List<RegistrantAccessPermission> permissions = GitBlit.self().getUserAccessPermissions(userModel);
-
-		final Palette<String> teams = new Palette<String>("teams", new ListModel<String>(
-				new ArrayList<String>(userTeams)), new CollectionModel<String>(GitBlit.self()
-				.getAllTeamnames()), new StringChoiceRenderer(), 10, false);
-		Form<UserModel> form = new Form<UserModel>("editForm", model) {
-
-			private static final long serialVersionUID = 1L;
-			
-			/*
-			 * (non-Javadoc)
-			 * 
-			 * @see org.apache.wicket.markup.html.form.Form#onSubmit()
-			 */
-			@Override
-			protected void onSubmit() {
-				if (StringUtils.isEmpty(userModel.username)) {
-					error(getString("gb.pleaseSetUsername"));
-					return;
-				}
-				// force username to lower-case
-				userModel.username = userModel.username.toLowerCase();
-				String username = userModel.username;
-				if (isCreate) {
-					UserModel model = GitBlit.self().getUserModel(username);
-					if (model != null) {
-						error(MessageFormat.format(getString("gb.usernameUnavailable"), username));
-						return;
-					}
-				}
-				boolean rename = !StringUtils.isEmpty(oldName)
-						&& !oldName.equalsIgnoreCase(username);
-				if (GitBlit.self().supportsCredentialChanges(userModel)) {
-					if (!userModel.password.equals(confirmPassword.getObject())) {
-						error(getString("gb.passwordsDoNotMatch"));
-						return;
-					}
-					String password = userModel.password;
-					if (!password.toUpperCase().startsWith(StringUtils.MD5_TYPE)
-							&& !password.toUpperCase().startsWith(StringUtils.COMBINED_MD5_TYPE)) {
-						// This is a plain text password.
-						// Check length.
-						int minLength = GitBlit.getInteger(Keys.realm.minPasswordLength, 5);
-						if (minLength < 4) {
-							minLength = 4;
-						}
-						if (password.trim().length() < minLength) {
-							error(MessageFormat.format(getString("gb.passwordTooShort"),
-									minLength));
-							return;
-						}
-	
-						// Optionally store the password MD5 digest.
-						String type = GitBlit.getString(Keys.realm.passwordStorage, "md5");
-						if (type.equalsIgnoreCase("md5")) {
-							// store MD5 digest of password
-							userModel.password = StringUtils.MD5_TYPE
-									+ StringUtils.getMD5(userModel.password);
-						} else if (type.equalsIgnoreCase("combined-md5")) {
-							// store MD5 digest of username+password
-							userModel.password = StringUtils.COMBINED_MD5_TYPE
-									+ StringUtils.getMD5(username + userModel.password);
-						}
-					} else if (rename
-							&& password.toUpperCase().startsWith(StringUtils.COMBINED_MD5_TYPE)) {
-						error(getString("gb.combinedMd5Rename"));
-						return;
-					}
-				}
-
-				// update user permissions
-				for (RegistrantAccessPermission repositoryPermission : permissions) {
-					userModel.setRepositoryPermission(repositoryPermission.registrant, repositoryPermission.permission);
-				}
-
-				Iterator<String> selectedTeams = teams.getSelectedChoices();
-				userModel.teams.clear();
-				while (selectedTeams.hasNext()) {
-					TeamModel team = GitBlit.self().getTeamModel(selectedTeams.next());
-					if (team == null) {
-						continue;
-					}
-					userModel.teams.add(team);
-				}
-
-				try {					
-					GitBlit.self().updateUserModel(oldName, userModel, isCreate);
-				} catch (GitBlitException e) {
-					error(e.getMessage());
-					return;
-				}
-				setRedirect(false);
-				if (isCreate) {
-					// create another user
-					info(MessageFormat.format(getString("gb.userCreated"),
-							userModel.username));
-					setResponsePage(EditUserPage.class);
-				} else {
-					// back to users page
-					setResponsePage(UsersPage.class);
-				}
-			}
-		};
-		
-		// do not let the browser pre-populate these fields
-		form.add(new SimpleAttributeModifier("autocomplete", "off"));
-		
-		// not all user services support manipulating username and password
-		boolean editCredentials = GitBlit.self().supportsCredentialChanges(userModel);
-		
-		// not all user services support manipulating display name
-		boolean editDisplayName = GitBlit.self().supportsDisplayNameChanges(userModel);
-
-		// not all user services support manipulating email address
-		boolean editEmailAddress = GitBlit.self().supportsEmailAddressChanges(userModel);
-
-		// not all user services support manipulating team memberships
-		boolean editTeams = GitBlit.self().supportsTeamMembershipChanges(userModel);
-
-		// field names reflective match UserModel fields
-		form.add(new TextField<String>("username").setEnabled(editCredentials));
-		PasswordTextField passwordField = new PasswordTextField("password");
-		passwordField.setResetPassword(false);
-		form.add(passwordField.setEnabled(editCredentials));
-		PasswordTextField confirmPasswordField = new PasswordTextField("confirmPassword",
-				confirmPassword);
-		confirmPasswordField.setResetPassword(false);
-		form.add(confirmPasswordField.setEnabled(editCredentials));
-		form.add(new TextField<String>("displayName").setEnabled(editDisplayName));
-		form.add(new TextField<String>("emailAddress").setEnabled(editEmailAddress));
-		form.add(new CheckBox("canAdmin"));
-		form.add(new CheckBox("canFork").setEnabled(GitBlit.getBoolean(Keys.web.allowForking, true)));
-		form.add(new CheckBox("canCreate"));
-		form.add(new CheckBox("excludeFromFederation"));
-		form.add(new RegistrantPermissionsPanel("repositories",	RegistrantType.REPOSITORY, repos, permissions, getAccessPermissions()));
-		form.add(teams.setEnabled(editTeams));
-
-		form.add(new TextField<String>("organizationalUnit").setEnabled(editDisplayName));
-		form.add(new TextField<String>("organization").setEnabled(editDisplayName));
-		form.add(new TextField<String>("locality").setEnabled(editDisplayName));
-		form.add(new TextField<String>("stateProvince").setEnabled(editDisplayName));
-		form.add(new TextField<String>("countryCode").setEnabled(editDisplayName));
-		form.add(new Button("save"));
-		Button cancel = new Button("cancel") {
-			private static final long serialVersionUID = 1L;
-
-			@Override
-			public void onSubmit() {
-				setResponsePage(UsersPage.class);
-			}
-		};
-		cancel.setDefaultFormProcessing(false);
-		form.add(cancel);
-
-		add(form);
-	}
-}
diff --git a/src/com/gitblit/wicket/pages/EmptyRepositoryPage.html b/src/com/gitblit/wicket/pages/EmptyRepositoryPage.html
deleted file mode 100644
index d46a5de..0000000
--- a/src/com/gitblit/wicket/pages/EmptyRepositoryPage.html
+++ /dev/null
@@ -1,53 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml"  
-      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"  
-      xml:lang="en"  
-      lang="en"> 
-
-<body>
-<wicket:extend>
-
-	<h2>Empty Repository</h2>
-	<p></p>
-		<div class="row">
-			<div class="span10">
-				<div class="alert alert-success">
-					<span wicket:id="repository" style="font-weight: bold;">[repository]</span> is an empty repository and can not be viewed by Gitblit.
-					<p></p>		
-					Please push some commits to <span wicket:id="pushurl"></span>
-					<p></p>
-					<hr/>
-					After you have pushed commits you may <b>refresh</b> this page to view your repository.
-				</div>
-			</div>
-		</div>
-		
-		<h3>Git Command-Line Syntax</h3>
-		<span style="padding-bottom:5px;">If you do not have a local Git repository, then you should clone this repository, commit some files, and then push your commits back to Gitblit.</span>
-		<p></p>
-		<pre style="padding: 5px 30px;" wicket:id="cloneSyntax"></pre>
-		<p></p>
-		<span style="padding-bottom:5px;">If you already have a local Git repository with commits, then you may add this repository as a remote and push to it.</span>
-		<p></p>
-		<pre wicket:id="remoteSyntax" style="padding: 5px 30px;"></pre>
-		<p></p>
-		<h3>Learn Git</h3>
-		If you are unsure how to use this information, consider reviewing the <a href="http://book.git-scm.com">Git Community Book</a> or <a href="http://progit.org/book" target="_blank">Pro Git</a> for a better understanding on how to use Git.
-		<p></p>
-		<h4>Open Source Git Clients</h4>
-		<ul>
-			<li><a href="http://git-scm.com">Git</a> - the official, command-line Git</li>
-			<li><a href="http://tortoisegit.googlecode.com">TortoiseGit</a> - Windows file explorer integration (requires official, command-line Git)</li>
-			<li><a href="http://eclipse.org/egit">Eclipse/EGit</a> - Git for the Eclipse IDE (based on JGit, like Gitblit)</li>
-			<li><a href="https://code.google.com/p/gitextensions/">Git Extensions</a> - C# frontend for Git that features Windows Explorer and Visual Studio integration</li>
-			<li><a href="http://gitx.laullon.com/">GitX (L)</a> - a Mac OS X Git client</li>			
-		</ul>
-		<p></p>
-		<h4>Commercial/Closed-Source Git Clients</h4>
-		<ul>
-			<li><a href="http://www.syntevo.com/smartgit">SmartGit</a> - A Java Git, Mercurial, and SVN client application (requires official, command-line Git)</li>
-			<li><a href="http://www.sourcetreeapp.com/">SourceTree</a> - A free Mac Client for Git, Mercurial, and SVN</li>
-		</ul>
-</wicket:extend>	
-</body>
-</html>
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/pages/EmptyRepositoryPage.java b/src/com/gitblit/wicket/pages/EmptyRepositoryPage.java
deleted file mode 100644
index be0dad9..0000000
--- a/src/com/gitblit/wicket/pages/EmptyRepositoryPage.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * 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.wicket.pages;
-
-import java.text.MessageFormat;
-import java.util.ArrayList;
-import java.util.List;
-
-import org.apache.wicket.PageParameters;
-import org.apache.wicket.markup.html.basic.Label;
-
-import com.gitblit.GitBlit;
-import com.gitblit.Keys;
-import com.gitblit.models.RepositoryModel;
-import com.gitblit.utils.ArrayUtils;
-import com.gitblit.wicket.GitblitRedirectException;
-import com.gitblit.wicket.WicketUtils;
-import com.gitblit.wicket.panels.RepositoryUrlPanel;
-
-public class EmptyRepositoryPage extends RootPage {
-
-	public EmptyRepositoryPage(PageParameters params) {
-		super(params);
-
-		setVersioned(false);
-
-		String repositoryName = WicketUtils.getRepositoryName(params);
-		RepositoryModel repository = GitBlit.self().getRepositoryModel(repositoryName);
-		if (repository == null) {
-			error(getString("gb.canNotLoadRepository") + " " + repositoryName, true);
-		}
-		
-		if (repository.hasCommits) {
-			// redirect to the summary page if this repository is not empty
-			throw new GitblitRedirectException(SummaryPage.class, params);
-		}
-		
-		setupPage(repositoryName, getString("gb.emptyRepository"));
-
-		List<String> repositoryUrls = new ArrayList<String>();
-
-		if (GitBlit.getBoolean(Keys.git.enableGitServlet, true)) {
-			// add the Gitblit repository url
-			repositoryUrls.add(getRepositoryUrl(repository));
-		}
-		repositoryUrls.addAll(GitBlit.self().getOtherCloneUrls(repositoryName));
-		
-		String primaryUrl = ArrayUtils.isEmpty(repositoryUrls) ? "" : repositoryUrls.get(0);
-		add(new Label("repository", repositoryName));
-		add(new RepositoryUrlPanel("pushurl", primaryUrl));
-		add(new Label("cloneSyntax", MessageFormat.format("git clone {0}", repositoryUrls.get(0))));
-		add(new Label("remoteSyntax", MessageFormat.format("git remote add gitblit {0}\ngit push gitblit master", primaryUrl)));
-	}
-}
diff --git a/src/com/gitblit/wicket/pages/EmptyRepositoryPage_es.html b/src/com/gitblit/wicket/pages/EmptyRepositoryPage_es.html
deleted file mode 100644
index 2849fc7..0000000
--- a/src/com/gitblit/wicket/pages/EmptyRepositoryPage_es.html
+++ /dev/null
@@ -1,56 +0,0 @@
-
-
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml"  
-      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"  
-      xml:lang="en"  
-      lang="es"> 
-
-<body>
-<wicket:extend>
-
-		<h2>Repositorio Vac&iacute;o</h2>
-		<p></p>
-		<div class="row">
-			<div class="span7">
-				<div class="alert alert-success">
-					<span wicket:id="repository" style="font-weight: bold;">[repository]</span> es un repositorio vac&iacute;o y no puede ser visto en Gitblit.
-					<p></p>
-					Por favor, empuja algunas consignas a <span wicket:id="pushurl"></span>
-					<p></p>
-					<hr/>
-					Despu&eacute;s de empujar tus consignas puedes <b>refrescar</b> &eacute;sta p&aacute;gina para ver tu Repositorio.
-				</div>
-			</div>
-		</div>
-		
-		<h3>Sintaxis de la L&iacute;nea de Comandos de Git</h3>
-		<span style="padding-bottom:5px;">Si no tienes un Repositiorio local Git, puedes clonar &eacute;ste, consignar algunos archivos, y despu&eacute;s empujar las consignas de vuelta a Gitblit.</span>
-		<p></p>
-		<pre style="padding: 5px 30px;" wicket:id="cloneSyntax"></pre>
-		<p></p>
-		<span style="padding-bottom:5px;">Si ya tienes un repositorio local Git con algunas consignas, puedes a&ntilde;adir &eacute;ste como remoto y empujar desde all&iacute;.</span>
-		<p></p>
-		<pre style="padding: 5px 30px;" wicket:id="remoteSyntax"></pre>
-		<p></p>
-		<h3>Aprender Git</h3>
-		Si no est&aacute;s seguro de como usar esta informaci&oacute;n, &eacute;chale un vistazo al <a href="http://book.git-scm.com">Libro de la cominidad Git</a> o <a href="http://progit.org/book" target="_blank">Pro Git</a> para una mejor compresi&oacute;n de como usar Git.
-		<p></p>
-		<h4>Clientes Git de C&oacute;digo abierto.</h4>
-		<ul>
-			<li><a href="http://git-scm.com">Git</a> - El Git oficial en l&iacute;nea de comandos</li>
-			<li><a href="http://tortoisegit.googlecode.com">TortoiseGit</a> - Explorador de archivos integrado en Windows (necesita Git oficial en l&iacute;nea de comandos)</li>
-			<li><a href="http://eclipse.org/egit">Eclipse/EGit</a> - Git para el IDE de Eclipse (basado en JGit, como Gitblit)</li>
-			<li><a href="https://code.google.com/p/gitextensions/">Git Extensions</a> - Interfaz de usuario gr&aacute;fico Git en C# con integraci&oacute;n en IE y en Visual Studio</li>
-			<li><a href="http://gitx.laullon.com/">GitX (L)</a> - Cliente Git para Mac OS X</li>			
-		</ul>
-		<p></p>
-		<h4>Clientes Git comerciales</h4>
-		<ul>
-			<li><a href="http://www.syntevo.com/smartgit">SmartGit</a> - aplicaci&oacute;n Java (necesita Git oficial en l&iacute;nea de comandos)</li>
-			<li><a href="http://www.sourcetreeapp.com/">SourceTree</a> - Un cliente Git gratuito para Mac, Mercurial, y SVN</li>
-		</ul>
-</wicket:extend>	
-</body>
-</html>
-
diff --git a/src/com/gitblit/wicket/pages/EmptyRepositoryPage_ko.html b/src/com/gitblit/wicket/pages/EmptyRepositoryPage_ko.html
deleted file mode 100644
index 591335e..0000000
--- a/src/com/gitblit/wicket/pages/EmptyRepositoryPage_ko.html
+++ /dev/null
@@ -1,57 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml"  
-      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"  
-      xml:lang="en"  
-      lang="en"> 
-
-<body>
-<wicket:extend>
-
-		<h2>비어있는 저장소</h2>
-		<p></p>
-		<div class="row">
-			<div class="span10">
-				<div class="alert alert-success">
-					<span wicket:id="repository" style="font-weight: bold;">[repository]</span> 저장소는 비어 있어서 Gitblit 에서 볼 수 없습니다.
-					<p></p>
-					이 Git url 에 커밋해 주세요. <span wicket:id="pushurl"></span>
-					<p></p>
-					<hr/>
-					After you have pushed commits you may <b>refresh</b> this page to view your repository.
-				</div>
-			</div>
-		</div>
-
-		<p></p>
-		<h3>Git 명령어</h3>
-		<span style="padding-bottom:5px;">로컬 Git 저장소가 없다면, 이 저장소를 클론(clone) 한 후, 몇 파일을 커밋하고, 그 커밋을 Gitblit 에 푸시(push) 하세요.</span>
-		<p></p>
-		<pre style="padding: 5px 30px;" wicket:id="cloneSyntax"></pre>
-		<p></p>
-		<span style="padding-bottom:5px;">만약 커밋된 로컬 Git 저장소가 있다면, 다음과 같이 저장소에 리모트를 추가하고 푸시(push)할 수 있습니다.</span>
-		<p></p>
-		<pre style="padding: 5px 30px;" wicket:id="remoteSyntax"></pre>
-		<p></p>
-		<h3>Git 배우기</h3>
-		만약 사용법에 자신이 없다면, Git 사용법을 더 잘 이해하기 위해 
-		 <a href="http://book.git-scm.com">Git Community Book</a> 또는 
-		 <a href="http://progit.org/book" target="_blank">Pro Git</a>, 
-		 <a href="http://dogfeet.github.com/articles/2012/progit.html" target="_blank">Pro Git 한글</a> 을 볼 것을 고려해 보세요.
-		<p></p>
-		<h4>오픈소스 Git 클라이언트</h4>
-		<ul>
-			<li><a href="http://git-scm.com">Git</a> - 명령어 기반 공식 Git</li>
-			<li><a href="http://tortoisegit.googlecode.com">TortoiseGit</a> - 윈도의 파일 탐색기에 통합된 UI 클라이언트 (명령어 기반 공식 Git 필요)</li>
-			<li><a href="http://eclipse.org/egit">Eclipse/EGit</a> - 이클립스 IDE 플러그인 (Gitblit 과 같은 JGit 기반)</li>
-			<li><a href="https://code.google.com/p/gitextensions/">Git Extensions</a> - C# frontend for Git that features Windows Explorer and Visual Studio integration</li>
-			<li><a href="http://gitx.laullon.com/">GitX (L)</a> - a Mac OS X Git client</li>			
-		</ul>
-		<p></p>
-		<h4>유료 Git 클라이언트</h4>
-		<ul>
-			<li><a href="http://www.syntevo.com/smartgit">SmartGit</a> - 자바 어플리케이션 (명령어 기반 공식 Git 필요)</li>
-			<li><a href="http://www.sourcetreeapp.com/">SourceTree</a> - A free Mac Client for Git, Mercurial, and SVN</li>
-		</ul>
-</wicket:extend>	
-</body>
-</html>
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/pages/EmptyRepositoryPage_nl.html b/src/com/gitblit/wicket/pages/EmptyRepositoryPage_nl.html
deleted file mode 100644
index a8ee2e2..0000000
--- a/src/com/gitblit/wicket/pages/EmptyRepositoryPage_nl.html
+++ /dev/null
@@ -1,53 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml"  
-      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"  
-      xml:lang="nl"  
-      lang="nl"> 
-
-<body>
-<wicket:extend>
-
-	<h2>Empty Repository</h2>
-	<p></p>
-		<div class="row">
-			<div class="span10">
-				<div class="alert alert-success">
-					<span wicket:id="repository" style="font-weight: bold;">[repository]</span> is een lege repositorie en kan niet bekeken worden door Gitblit.
-					<p></p>		
-					Push aub een paar commitsome commits naar <span wicket:id="pushurl"></span>
-					<p></p>
-					<hr/>
-					Nadat u een paar commits gepushed hebt kunt u deze pagina <b>verversen</b> om de repository te bekijken.
-				</div>
-			</div>
-		</div>
-		
-		<h3>Git Command-Line Syntax</h3>
-		<span style="padding-bottom:5px;">Als u geen lokale Git repositorie heeft, kunt u deze repository clonen, er een paar bestanden naar committen en deze commits teug pushen naar Gitblit.</span>
-		<p></p>
-		<pre style="padding: 5px 30px;" wicket:id="cloneSyntax"></pre>
-		<p></p>
-		<span style="padding-bottom:5px;">Als u al een lokale Git repositorie heeft met commits kunt u deze repository als een remote toevoegen en er naar toe pushen.</span>
-		<p></p>
-		<pre wicket:id="remoteSyntax" style="padding: 5px 30px;"></pre>
-		<p></p>
-		<h3>Learn Git</h3>
-		Als u niet goed weet wat u met deze informatie aan moet raden we aan om het <a href="http://book.git-scm.com">Git Community Book</a> of <a href="http://progit.org/book" target="_blank">Pro Git</a> te bestuderen voor een betere begrip van hoe u Git kunt gebruiken.
-		<p></p>
-		<h4>Open Source Git Clients</h4>
-		<ul>
-			<li><a href="http://git-scm.com">Git</a> - de officiele, command-line Git</li>
-			<li><a href="http://tortoisegit.googlecode.com">TortoiseGit</a> - Windows bestandsverkenner ingetratie (officiele command-line Git is wel nodig)</li>
-			<li><a href="http://eclipse.org/egit">Eclipse/EGit</a> - Git voor de Eclipse IDE (gebaseerd op JGit, zoals Gitblit)</li>
-			<li><a href="https://code.google.com/p/gitextensions/">Git Extensions</a> - C# frontend voor Git met Windows Explorer en Visual Studio integratie</li>
-			<li><a href="http://gitx.laullon.com/">GitX (L)</a> - een Mac OS X Git client</li>			
-		</ul>
-		<p></p>
-		<h4>Commercial/Closed-Source Git Clients</h4>
-		<ul>
-			<li><a href="http://www.syntevo.com/smartgit">SmartGit</a> - Een Java Git, Mercurial, en SVN client applicatie (officiele command-line Git is wel nodig)</li>
-			<li><a href="http://www.sourcetreeapp.com/">SourceTree</a> - Een gratis Mac Client voor Git, Mercurial, en SVN</li>
-		</ul>
-</wicket:extend>	
-</body>
-</html>
diff --git a/src/com/gitblit/wicket/pages/EmptyRepositoryPage_pl.html b/src/com/gitblit/wicket/pages/EmptyRepositoryPage_pl.html
deleted file mode 100644
index 109899a..0000000
--- a/src/com/gitblit/wicket/pages/EmptyRepositoryPage_pl.html
+++ /dev/null
@@ -1,56 +0,0 @@
-
-
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml"  
-      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"  
-      xml:lang="en"  
-      lang="es"> 
-
-<body>
-<wicket:extend>
-
-		<h2>Puste repozytorium</h2>
-		<p></p>
-		<div class="row">
-			<div class="span10">
-				<div class="alert alert-success">
-					<span wicket:id="repository" style="font-weight: bold;">[repository]</span> jest pustym repozytorium i nie mo&#380;e by&#263; zaprezentowane przez Gitblit.
-					<p></p>
-					Wgraj, poprzez push, dowolne zmiany do lokalizacji <span wicket:id="pushurl"></span>
-					<p></p>
-					<hr/>
-					Po wgraniu zmian <b>od&#347;wie&#380;</b> stron&#281;, aby podejrze&#263; repozytorium.
-				</div>
-			</div>
-		</div>
-		
-		<h3>Sk&#322;adnia linii polece&#324; GITa</h3>
-		<span style="padding-bottom:5px;">Je&#347;li nie posiadasz lokalnego repozytorium GITa to sklonuj to repozytorium, wgraj dowolne pliki, a nast&#281;pnie wy&#347;lij poprzez push zmiany na Gitblit.</span>
-		<p></p>
-		<pre style="padding: 5px 30px;" wicket:id="cloneSyntax"></pre>
-		<p></p>
-		<span style="padding-bottom:5px;">Gdy posiadasz lokalne repozytorium GITa z dowolnymi zmianami, to mo&#380;esz doda&#263; to repozytorium jako remote i wys&#322;a&#263; do niego zmiany poprzez push.</span>
-		<p></p>
-		<pre style="padding: 5px 30px;" wicket:id="remoteSyntax"></pre>
-		<p></p>
-		<h3>Nauka GITa</h3>
-		Je&#380;eli powy&#380;sze informacje s&#261; dla Ciebie niezrozumia&#322;e, zapoznaj si&#281; z ksi&#261;&#380;k&#261; <a href="http://git-scm.com/book/pl" target="_blank">Pro Git - Wersja PL</a> dla lepszego zrozumienia, jak poprawnie u&#380;ywa&#263; GITa.
-		<p></p>
-		<h4>Darmowi klienci GITa</h4>
-		<ul>
-			<li><a href="http://git-scm.com">Git</a> - Oficjalny klient, dost&#281;pny przez lini&#281; polece&#324;</li>
-			<li><a href="http://tortoisegit.googlecode.com">TortoiseGit</a> - Rozszerzenie eksploratora Windows (wymaga oficjalnego, dost&#281;pnego przez lini&#281; polece&#324; klienta)</li>
-			<li><a href="http://eclipse.org/egit">Eclipse/EGit</a> - GIT dla edytora Eclipse (oparty o JGit, podobnie jak Gitblit)</li>
-			<li><a href="https://code.google.com/p/gitextensions/">Git Extensions</a> - napisana w C# fasada na GIT, udost&#281;pniaj&#261;ca integracj&#281; dla Windows Explorer oraz Visual Studio</li>
-			<li><a href="http://gitx.laullon.com/">GitX (L)</a> - klient GIT na Mac OS X</li>			
-		</ul>
-		<p></p>
-		<h4>Komercyjni klienci GITa</h4>
-		<ul>
-			<li><a href="http://www.syntevo.com/smartgit">SmartGit</a> - aplikacja napisana w Javie (wymaga oficjalnego, dost&#281;pnego przez lini&#281; polece&#324; klienta)</li>
-			<li><a href="http://www.sourcetreeapp.com/">SourceTree</a> - darmowy klient GIT, Mercurial i SVN na Mac OS X</li>
-		</ul>
-</wicket:extend>	
-</body>
-</html>
-
diff --git a/src/com/gitblit/wicket/pages/EmptyRepositoryPage_pt_BR.html b/src/com/gitblit/wicket/pages/EmptyRepositoryPage_pt_BR.html
deleted file mode 100644
index 351ef87..0000000
--- a/src/com/gitblit/wicket/pages/EmptyRepositoryPage_pt_BR.html
+++ /dev/null
@@ -1,53 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml"  
-      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"  
-      xml:lang="pt-br"  
-      lang="pt-br"> 
-
-<body>
-<wicket:extend>
-
-	<h2>Repositório Vazio</h2>
-	<p></p>
-		<div class="row">
-			<div class="span10">
-				<div class="alert alert-success">
-					<span wicket:id="repository" style="font-weight: bold;">[repository]</span> é um repositório vazio e não pode ser visualizado pelo Gitblit.
-					<p></p>		
-					Por favor faça o push de alguns commits para <span wicket:id="pushurl"></span>
-					<p></p>
-					<hr/>
-					Depois de ter feito push você poderá <b>atualizar</b> esta página para visualizar seu repositório.
-				</div>
-			</div>
-		</div>
-		
-		<h3>Sintaxe dos comandos do Git</h3>
-		<span style="padding-bottom:5px;">Se você ainda não tem um repositório local do Git, então você deve primeiro clonar este repositório, fazer commit de alguns arquivos e então fazer push desses commits para o Gitblit.</span>
-		<p></p>
-		<pre style="padding: 5px 30px;" wicket:id="cloneSyntax"></pre>
-		<p></p>
-		<span style="padding-bottom:5px;">Se você já tem um repositório Git local com alguns commits, então você deve adicionar este repositório como uma referência remota e então fazer push.</span>
-		<p></p>
-		<pre wicket:id="remoteSyntax" style="padding: 5px 30px;"></pre>
-		<p></p>
-		<h3>Aprenda Git</h3>
-		Se você estiver com dúvidas sobre como ultilizar essas informações, uma sugestão seria dar uma olhada no livro <a href="http://book.git-scm.com">Git Community Book</a> ou <a href="http://progit.org/book" target="_blank">Pro Git</a> para entender melhor como usar o Git.
-		<p></p>
-		<h4>Alguns clients do Git que são Open Source</h4>
-		<ul>
-			<li><a href="http://git-scm.com">Git</a> - o Git oficial através de linhas de comando</li>
-			<li><a href="http://tortoisegit.googlecode.com">TortoiseGit</a> - Faz integração do Explorer do Windows com o Git (por isso requer o Git Oficial)</li>
-			<li><a href="http://eclipse.org/egit">Eclipse/EGit</a> - Git para a IDE Eclipse (baseada no JGit, como o Gitblit)</li>
-			<li><a href="https://code.google.com/p/gitextensions/">Git Extensions</a> - Interface (em C#) para o Git cuja a característica é a integração com o Windows Explorer e o Visual Studio</li>
-			<li><a href="http://gitx.laullon.com/">GitX (L)</a> - um Cliente do Git para Mac OS X</li>			
-		</ul>
-		<p></p>
-		<h4>Clients do Git proprietários ou com Código Fechado</h4>
-		<ul>
-			<li><a href="http://www.syntevo.com/smartgit">SmartGit</a> - Aplicação Client (em Java) para Git, Mercurial, e SVN (por isso requer o Git Oficial)</li>
-			<li><a href="http://www.sourcetreeapp.com/">SourceTree</a> - Client gratuito para o Mac que suporta Git, Mercurial e SVN</li>
-		</ul>
-</wicket:extend>	
-</body>
-</html>
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/pages/EmptyRepositoryPage_zh_CN.html b/src/com/gitblit/wicket/pages/EmptyRepositoryPage_zh_CN.html
deleted file mode 100644
index 4b21800..0000000
--- a/src/com/gitblit/wicket/pages/EmptyRepositoryPage_zh_CN.html
+++ /dev/null
@@ -1,55 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml"  
-      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"  
-      xml:lang="zh-CN"  
-      lang="zh-CN"> 
-
-<body>
-<wicket:extend>
-
-	<h2>空版本库</h2>
-	<p></p>
-		<div class="row">
-			<div class="span10">
-				<div class="alert alert-success">
-					<span wicket:id="repository" style="font-weight: bold;">[repository]</span> 版本库目前为空。
-                    Gitblit 无法查看。
-					<p></p>		
-					请往此网址进行推送 <span wicket:id="pushurl"></span>
-					<p></p>
-					<hr/>
-					当你推送完毕后你可以 <b>刷新</b> 此页面重新查看您的版本库。
-				</div>
-			</div>
-		</div>
-		
-		<h3>Git 命令行格式</h3>
-		<span style="padding-bottom:5px;">如果您没有本地 Git 版本库, 您可以克隆此版本库, 提交一些文件, 然后将您的提交推送回Gitblit。</span>
-		<p></p>
-		<pre style="padding: 5px 30px;" wicket:id="cloneSyntax"></pre>
-		<p></p>
-		<span style="padding-bottom:5px;">如果您已经有一个本地的提交过的版本库, 那么您可以将此版本库加为远程
-        版本库,并进行推送。</span>
-		<p></p>
-		<pre wicket:id="remoteSyntax" style="padding: 5px 30px;"></pre>
-		<p></p>
-		<h3>学习 Git</h3>
-		如果您不明白这些信息什么意思, 您可以参考 <a href="http://book.git-scm.com">Git Community Book</a> 或者 <a href="http://progit.org/book" target="_blank">Pro Git</a> 去更加深入的学习 Git 的用法。
-		<p></p>
-		<h4>开源 Git 客户端</h4>
-		<ul>
-			<li><a href="http://git-scm.com">Git</a> - 官方, 命令行版本 Git</li>
-			<li><a href="http://tortoisegit.googlecode.com">TortoiseGit</a> - 与 Windows 资源管理器集成 (需要官方, 命令行 Git 的支持)</li>
-			<li><a href="http://eclipse.org/egit">Eclipse/EGit</a> - Git for the Eclipse IDE (基于 JGit, 类似 Gitblit)</li>
-			<li><a href="https://code.google.com/p/gitextensions/">Git Extensions</a> - C# 版本的 Git 前端,与 Windows 资源管理器和 Visual Studio 集成</li>
-			<li><a href="http://gitx.laullon.com/">GitX (L)</a> - Mac OS X Git 客户端</li>			
-		</ul>
-		<p></p>
-		<h4>商业/闭源 Git 客户端</h4>
-		<ul>
-			<li><a href="http://www.syntevo.com/smartgit">SmartGit</a> - Java 版本的支持 Git, Mercurial 和 SVN 客户端应用 (需要官方, 命令行 Git 的支持)</li>
-			<li><a href="http://www.sourcetreeapp.com/">SourceTree</a> - 免费的 Mac Git Mercurial 以及 SVN 客户端, Mercurial, and SVN</li>
-		</ul>
-</wicket:extend>	
-</body>
-</html>
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/pages/FederationPage.html b/src/com/gitblit/wicket/pages/FederationPage.html
deleted file mode 100644
index bb39d34..0000000
--- a/src/com/gitblit/wicket/pages/FederationPage.html
+++ /dev/null
@@ -1,17 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml"  
-      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"  
-      xml:lang="en"  
-      lang="en"> 
-<body>
-<wicket:extend>
-
-	<div wicket:id="federationTokensPanel">[federation tokens panel]</div>
-		
-	<div style="padding-top: 10px;" wicket:id="federationProposalsPanel">[federation proposals panel]</div>
-
-	<div style="padding-top: 10px;" wicket:id="federationRegistrationsPanel">[federation registrations panel]</div>
-
-</wicket:extend>
-</body>
-</html>
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/pages/FederationRegistrationPage.html b/src/com/gitblit/wicket/pages/FederationRegistrationPage.html
deleted file mode 100644
index d7b9bdd..0000000
--- a/src/com/gitblit/wicket/pages/FederationRegistrationPage.html
+++ /dev/null
@@ -1,39 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml"  
-      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"  
-      xml:lang="en"  
-      lang="en"> 
-
-<body>
-<wicket:extend>
-	<!-- registration info -->
-	<table class="plain">
-		<tr><th><wicket:message key="gb.url">url</wicket:message></th><td><img style="border:0px;vertical-align:middle;" wicket:id="typeIcon" /> <span wicket:id="url">[url]</span></td></tr>
-		<tr><th><wicket:message key="gb.token">token</wicket:message></th><td><span class="sha1" wicket:id="token">[token]</span></td></tr>
-		<tr><th><wicket:message key="gb.folder">folder</wicket:message></th><td><span wicket:id="folder">[folder]</span></td></tr>
-		<tr><th><wicket:message key="gb.frequency">frequency</wicket:message></th><td><span wicket:id="frequency">[frequency]</span></td></tr>
-		<tr><th><wicket:message key="gb.lastPull">lastPull</wicket:message></th><td><span wicket:id="lastPull">[lastPull]</span></td></tr>
-		<tr><th><wicket:message key="gb.nextPull">nextPull</wicket:message></th><td><span wicket:id="nextPull">[nextPull]</span></td></tr>
-		<tr><th valign="top"><wicket:message key="gb.exclusions">exclusions</wicket:message></th><td><span class="sha1" wicket:id="exclusions">[exclusions]</span></td></tr>
-		<tr><th valign="top"><wicket:message key="gb.inclusions">inclusions</wicket:message></th><td><span class="sha1" wicket:id="inclusions">[inclusions]</span></td></tr>
-	</table>
-	
-	<table class="repositories">
-		<tr>
-			<th class="left">
-				<img style="vertical-align: top; border: 1px solid #888; background-color: white;" src="git-black-16x16.png"/>
-				<wicket:message key="gb.repositories">[repositories]</wicket:message>
-			</th>
-			<th class="right"><wicket:message key="gb.status">[status]</wicket:message></th>
-		</tr>
-		<tbody>		
-       		<tr wicket:id="row">
-       			<td class="left"><img style="border:0px;vertical-align:middle;" wicket:id="statusIcon" /><span wicket:id="name">[name]</span></td>
-       			<td class="right"><span wicket:id="status">[status]</span></td>
-       		</tr>
-    	</tbody>
-	</table>
-		
-</wicket:extend>    
-</body>
-</html>
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/pages/FederationRegistrationPage.java b/src/com/gitblit/wicket/pages/FederationRegistrationPage.java
deleted file mode 100644
index 19c30a5..0000000
--- a/src/com/gitblit/wicket/pages/FederationRegistrationPage.java
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * 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.wicket.pages;
-
-import java.util.Collections;
-import java.util.List;
-
-import org.apache.wicket.PageParameters;
-import org.apache.wicket.markup.html.basic.Label;
-import org.apache.wicket.markup.repeater.Item;
-import org.apache.wicket.markup.repeater.data.DataView;
-import org.apache.wicket.markup.repeater.data.ListDataProvider;
-
-import com.gitblit.GitBlit;
-import com.gitblit.models.FederationModel;
-import com.gitblit.models.FederationModel.RepositoryStatus;
-import com.gitblit.wicket.WicketUtils;
-
-public class FederationRegistrationPage extends RootSubPage {
-
-	public FederationRegistrationPage(PageParameters params) {
-		super(params);
-		
-		setStatelessHint(true);
-
-		String url = WicketUtils.getUrlParameter(params);
-		String name = WicketUtils.getNameParameter(params);
-
-		FederationModel registration = GitBlit.self().getFederationRegistration(url, name);
-		if (registration == null) {
-			error(getString("gb.couldNotFindFederationRegistration"), true);
-		}
-
-		setupPage(registration.isResultData() ? getString("gb.federationResults")
-				: getString("gb.federationRegistration"), registration.url);
-
-		add(new Label("url", registration.url));
-		add(WicketUtils.getRegistrationImage("typeIcon", registration, this));
-		add(new Label("frequency", registration.frequency));
-		add(new Label("folder", registration.folder));
-		add(new Label("token", showAdmin ? registration.token : "--"));
-		add(WicketUtils.createTimestampLabel("lastPull", registration.lastPull, getTimeZone(), getTimeUtils()));
-		add(WicketUtils.createTimestampLabel("nextPull", registration.nextPull, getTimeZone(), getTimeUtils()));
-
-		StringBuilder inclusions = new StringBuilder();
-		for (String inc : registration.inclusions) {
-			inclusions.append(inc).append("<br/>");
-		}
-		StringBuilder exclusions = new StringBuilder();
-		for (String ex : registration.exclusions) {
-			exclusions.append(ex).append("<br/>");
-		}
-
-		add(new Label("inclusions", inclusions.toString()).setEscapeModelStrings(false));
-
-		add(new Label("exclusions", exclusions.toString()).setEscapeModelStrings(false));
-
-		List<RepositoryStatus> list = registration.getStatusList();
-		Collections.sort(list);
-		DataView<RepositoryStatus> dataView = new DataView<RepositoryStatus>("row",
-				new ListDataProvider<RepositoryStatus>(list)) {
-			private static final long serialVersionUID = 1L;
-			private int counter;
-
-			@Override
-			protected void onBeforeRender() {
-				super.onBeforeRender();
-				counter = 0;
-			}
-
-			public void populateItem(final Item<RepositoryStatus> item) {
-				final RepositoryStatus entry = item.getModelObject();
-				item.add(WicketUtils.getPullStatusImage("statusIcon", entry.status));
-				item.add(new Label("name", entry.name));
-				item.add(new Label("status", entry.status.name()));
-				WicketUtils.setAlternatingBackground(item, counter);
-				counter++;
-			}
-		};
-		add(dataView);
-	}
-}
diff --git a/src/com/gitblit/wicket/pages/ForksPage.java b/src/com/gitblit/wicket/pages/ForksPage.java
deleted file mode 100644
index cc48387..0000000
--- a/src/com/gitblit/wicket/pages/ForksPage.java
+++ /dev/null
@@ -1,156 +0,0 @@
-/*
- * 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.wicket.pages;
-
-import java.io.Serializable;
-import java.util.ArrayList;
-import java.util.List;
-
-import org.apache.wicket.Component;
-import org.apache.wicket.PageParameters;
-import org.apache.wicket.markup.html.basic.Label;
-import org.apache.wicket.markup.repeater.Item;
-import org.apache.wicket.markup.repeater.data.DataView;
-import org.apache.wicket.markup.repeater.data.ListDataProvider;
-import org.eclipse.jgit.lib.PersonIdent;
-
-import com.gitblit.GitBlit;
-import com.gitblit.Keys;
-import com.gitblit.models.ForkModel;
-import com.gitblit.models.RepositoryModel;
-import com.gitblit.models.UserModel;
-import com.gitblit.utils.StringUtils;
-import com.gitblit.wicket.GitBlitWebSession;
-import com.gitblit.wicket.WicketUtils;
-import com.gitblit.wicket.panels.GravatarImage;
-import com.gitblit.wicket.panels.LinkPanel;
-
-public class ForksPage extends RepositoryPage {
-
-	public ForksPage(PageParameters params) {
-		super(params);
-		
-		final RepositoryModel pageRepository = getRepositoryModel();
-		
-		ForkModel root = GitBlit.self().getForkNetwork(pageRepository.name);
-		List<FlatFork> network = flatten(root);
-		
-		ListDataProvider<FlatFork> forksDp = new ListDataProvider<FlatFork>(network);
-		DataView<FlatFork> forksList = new DataView<FlatFork>("fork", forksDp) {
-			private static final long serialVersionUID = 1L;
-
-			public void populateItem(final Item<FlatFork> item) {
-				FlatFork fork = item.getModelObject();
-				RepositoryModel repository = fork.repository;
-				
-				if (repository.isPersonalRepository()) {
-					UserModel user = GitBlit.self().getUserModel(repository.projectPath.substring(1));
-					PersonIdent ident = new PersonIdent(user.getDisplayName(), user.emailAddress == null ? user.getDisplayName() : user.emailAddress);
-					item.add(new GravatarImage("anAvatar", ident, 20));
-					if (pageRepository.equals(repository)) {
-						// do not link to self
-						item.add(new Label("aProject", user.getDisplayName()));
-					} else {
-						item.add(new LinkPanel("aProject", null, user.getDisplayName(), UserPage.class, WicketUtils.newUsernameParameter(user.username)));
-					}
-				} else {
-					Component swatch;
-					if (repository.isBare){
-						swatch = new Label("anAvatar", "&nbsp;").setEscapeModelStrings(false);
-					} else {
-						swatch = new Label("anAvatar", "!");
-						WicketUtils.setHtmlTooltip(swatch, getString("gb.workingCopyWarning"));
-					}
-					WicketUtils.setCssClass(swatch,  "repositorySwatch");
-					WicketUtils.setCssBackground(swatch, repository.toString());
-					item.add(swatch);
-					String projectName = repository.projectPath;
-					if (StringUtils.isEmpty(projectName)) {
-						projectName = GitBlit.getString(Keys.web.repositoryRootGroupName, "main");
-					}
-					if (pageRepository.equals(repository)) {
-						// do not link to self
-						item.add(new Label("aProject", projectName));
-					} else {
-						item.add(new LinkPanel("aProject", null, projectName, ProjectPage.class, WicketUtils.newProjectParameter(projectName)));
-					}
-				}
-				
-				String repo = StringUtils.getLastPathElement(repository.name);
-				UserModel user = GitBlitWebSession.get().getUser();
-				if (user == null) {
-					user = UserModel.ANONYMOUS;
-				}
-				if (user.canView(repository)) {
-					if (pageRepository.equals(repository)) {
-						// do not link to self
-						item.add(new Label("aFork", StringUtils.stripDotGit(repo)));
-					} else {
-						item.add(new LinkPanel("aFork", null, StringUtils.stripDotGit(repo), SummaryPage.class, WicketUtils.newRepositoryParameter(repository.name)));
-					}
-					item.add(WicketUtils.createDateLabel("lastChange", repository.lastChange, getTimeZone(), getTimeUtils()));
-				} else {
-					item.add(new Label("aFork", repo));
-					item.add(new Label("lastChange").setVisible(false));
-				}
-				
-				WicketUtils.setCssStyle(item, "margin-left:" + (32*fork.level) + "px;");
-				if (fork.level == 0) {
-					WicketUtils.setCssClass(item, "forkSource");
-				} else {
-					WicketUtils.setCssClass(item,  "forkEntry");
-				}
-			}
-		};
-		
-		add(forksList);
-	}
-
-	@Override
-	protected String getPageName() {
-		return getString("gb.forks");
-	}
-	
-	protected List<FlatFork> flatten(ForkModel root) {
-		List<FlatFork> list = new ArrayList<FlatFork>();
-		list.addAll(flatten(root, 0));
-		return list;
-	}
-	
-	protected List<FlatFork> flatten(ForkModel node, int level) {
-		List<FlatFork> list = new ArrayList<FlatFork>();
-		list.add(new FlatFork(node.repository, level));
-		if (!node.isLeaf()) {
-			for (ForkModel fork : node.forks) {
-				list.addAll(flatten(fork, level + 1));
-			}
-		}
-		return list;
-	}
-	
-	private class FlatFork implements Serializable {
-		
-		private static final long serialVersionUID = 1L;
-
-		public final RepositoryModel repository;
-		public final int level;
-		
-		public FlatFork(RepositoryModel repository, int level) {
-			this.repository = repository;
-			this.level = level;
-		}
-	}
-}
diff --git a/src/com/gitblit/wicket/pages/GitSearchPage.html b/src/com/gitblit/wicket/pages/GitSearchPage.html
deleted file mode 100644
index 9bb1f41..0000000
--- a/src/com/gitblit/wicket/pages/GitSearchPage.html
+++ /dev/null
@@ -1,25 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml"  
-      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"  
-      xml:lang="en"  
-      lang="en"> 
-
-<body>
-<wicket:extend>
-
-	<!-- pager links -->
-	<div style="padding-top:5px;">
-		<a wicket:id="firstPageTop"><wicket:message key="gb.pageFirst"></wicket:message></a> | <a wicket:id="prevPageTop"><wicket:message key="gb.pagePrevious"></wicket:message></a> | <a wicket:id="nextPageTop"><wicket:message key="gb.pageNext"></wicket:message></a> 
-	</div>
-	
-	<!-- history -->
-	<div style="margin-top:5px;" wicket:id="searchPanel">[search panel]</div>
-
-	<!-- pager links -->
-	<div style="padding-bottom:5px;">
-		<a wicket:id="firstPageBottom"><wicket:message key="gb.pageFirst"></wicket:message></a> | <a wicket:id="prevPageBottom"><wicket:message key="gb.pagePrevious"></wicket:message></a> | <a wicket:id="nextPageBottom"><wicket:message key="gb.pageNext"></wicket:message></a> 
-	</div>
-
-</wicket:extend>
-</body>
-</html>
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/pages/GitSearchPage.java b/src/com/gitblit/wicket/pages/GitSearchPage.java
deleted file mode 100644
index 6b0714f..0000000
--- a/src/com/gitblit/wicket/pages/GitSearchPage.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * 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.wicket.pages;
-
-import org.apache.wicket.PageParameters;
-import org.apache.wicket.markup.html.link.BookmarkablePageLink;
-
-import com.gitblit.Constants;
-import com.gitblit.wicket.WicketUtils;
-import com.gitblit.wicket.panels.SearchPanel;
-
-public class GitSearchPage extends RepositoryPage {
-
-	public GitSearchPage(PageParameters params) {
-		super(params);
-
-		String value = WicketUtils.getSearchString(params);
-		String type = WicketUtils.getSearchType(params);
-		Constants.SearchType searchType = Constants.SearchType.forName(type);
-
-		int pageNumber = WicketUtils.getPage(params);
-		int prevPage = Math.max(0, pageNumber - 1);
-		int nextPage = pageNumber + 1;
-
-		SearchPanel search = new SearchPanel("searchPanel", repositoryName, objectId, value,
-				searchType, getRepository(), -1, pageNumber - 1, getRepositoryModel().showRemoteBranches);
-		boolean hasMore = search.hasMore();
-		add(search);
-
-		add(new BookmarkablePageLink<Void>("firstPageTop", GitSearchPage.class,
-				WicketUtils.newSearchParameter(repositoryName, objectId, value, searchType))
-				.setEnabled(pageNumber > 1));
-		add(new BookmarkablePageLink<Void>("prevPageTop", GitSearchPage.class,
-				WicketUtils.newSearchParameter(repositoryName, objectId, value, searchType,
-						prevPage)).setEnabled(pageNumber > 1));
-		add(new BookmarkablePageLink<Void>("nextPageTop", GitSearchPage.class,
-				WicketUtils.newSearchParameter(repositoryName, objectId, value, searchType,
-						nextPage)).setEnabled(hasMore));
-
-		add(new BookmarkablePageLink<Void>("firstPageBottom", GitSearchPage.class,
-				WicketUtils.newSearchParameter(repositoryName, objectId, value, searchType))
-				.setEnabled(pageNumber > 1));
-		add(new BookmarkablePageLink<Void>("prevPageBottom", GitSearchPage.class,
-				WicketUtils.newSearchParameter(repositoryName, objectId, value, searchType,
-						prevPage)).setEnabled(pageNumber > 1));
-		add(new BookmarkablePageLink<Void>("nextPageBottom", GitSearchPage.class,
-				WicketUtils.newSearchParameter(repositoryName, objectId, value, searchType,
-						nextPage)).setEnabled(hasMore));
-
-	}
-
-	@Override
-	protected String getPageName() {
-		return getString("gb.search");
-	}
-}
diff --git a/src/com/gitblit/wicket/pages/GravatarProfilePage.html b/src/com/gitblit/wicket/pages/GravatarProfilePage.html
deleted file mode 100644
index 0cc0f1f..0000000
--- a/src/com/gitblit/wicket/pages/GravatarProfilePage.html
+++ /dev/null
@@ -1,20 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml"  
-      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"  
-      xml:lang="en"  
-      lang="en"> 
-<body>
-<wicket:extend>
-	<div class="pageTitle">
-		<h2>Gravatar<small> / <span wicket:id="username">[username]</span></small></h2>
-	</div>
-	<img class="gravatar" wicket:id="profileImage"></img>
-	<h2 wicket:id="displayName"></h2>
-	<div style="color:#888;"wicket:id="location"></div>
-	<div style="padding-top:5px;" wicket:id="aboutMe"></div>
-	<p></p>
-	<a wicket:id="profileLink"><wicket:message key="gb.completeGravatarProfile">[Complete profile on Gravatar.com]</wicket:message></a>
-	<p></p>
-</wicket:extend>
-</body>
-</html>
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/pages/HistoryPage.html b/src/com/gitblit/wicket/pages/HistoryPage.html
deleted file mode 100644
index f9bd2f6..0000000
--- a/src/com/gitblit/wicket/pages/HistoryPage.html
+++ /dev/null
@@ -1,25 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml"  
-      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"  
-      xml:lang="en"  
-      lang="en"> 
-
-<body>
-<wicket:extend>
-
-	<!-- pager links -->
-	<div style="padding-top:5px;">
-		<a wicket:id="firstPageTop"><wicket:message key="gb.pageFirst"></wicket:message></a> | <a wicket:id="prevPageTop"><wicket:message key="gb.pagePrevious"></wicket:message></a> | <a wicket:id="nextPageTop"><wicket:message key="gb.pageNext"></wicket:message></a> 
-	</div>
-	
-	<!-- history -->
-	<div style="margin-top:5px;" wicket:id="historyPanel">[history panel]</div>
-
-	<!-- pager links -->
-	<div style="padding-bottom:5px;">
-		<a wicket:id="firstPageBottom"><wicket:message key="gb.pageFirst"></wicket:message></a> | <a wicket:id="prevPageBottom"><wicket:message key="gb.pagePrevious"></wicket:message></a> | <a wicket:id="nextPageBottom"><wicket:message key="gb.pageNext"></wicket:message></a> 
-	</div>
-
-</wicket:extend>
-</body>
-</html>
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/pages/HistoryPage.java b/src/com/gitblit/wicket/pages/HistoryPage.java
deleted file mode 100644
index 563202e..0000000
--- a/src/com/gitblit/wicket/pages/HistoryPage.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * 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.wicket.pages;
-
-import org.apache.wicket.PageParameters;
-import org.apache.wicket.markup.html.link.BookmarkablePageLink;
-
-import com.gitblit.wicket.WicketUtils;
-import com.gitblit.wicket.panels.HistoryPanel;
-
-public class HistoryPage extends RepositoryPage {
-
-	public HistoryPage(PageParameters params) {
-		super(params);
-
-		String path = WicketUtils.getPath(params);
-		int pageNumber = WicketUtils.getPage(params);
-		int prevPage = Math.max(0, pageNumber - 1);
-		int nextPage = pageNumber + 1;
-
-		HistoryPanel history = new HistoryPanel("historyPanel", repositoryName, objectId, path,
-				getRepository(), -1, pageNumber - 1, getRepositoryModel().showRemoteBranches);
-		boolean hasMore = history.hasMore();
-		add(history);
-
-		add(new BookmarkablePageLink<Void>("firstPageTop", HistoryPage.class,
-				WicketUtils.newPathParameter(repositoryName, objectId, path))
-				.setEnabled(pageNumber > 1));
-		add(new BookmarkablePageLink<Void>("prevPageTop", HistoryPage.class,
-				WicketUtils.newHistoryPageParameter(repositoryName, objectId, path, prevPage))
-				.setEnabled(pageNumber > 1));
-		add(new BookmarkablePageLink<Void>("nextPageTop", HistoryPage.class,
-				WicketUtils.newHistoryPageParameter(repositoryName, objectId, path, nextPage))
-				.setEnabled(hasMore));
-
-		add(new BookmarkablePageLink<Void>("firstPageBottom", HistoryPage.class,
-				WicketUtils.newPathParameter(repositoryName, objectId, path))
-				.setEnabled(pageNumber > 1));
-		add(new BookmarkablePageLink<Void>("prevPageBottom", HistoryPage.class,
-				WicketUtils.newHistoryPageParameter(repositoryName, objectId, path, prevPage))
-				.setEnabled(pageNumber > 1));
-		add(new BookmarkablePageLink<Void>("nextPageBottom", HistoryPage.class,
-				WicketUtils.newHistoryPageParameter(repositoryName, objectId, path, nextPage))
-				.setEnabled(hasMore));
-
-	}
-
-	@Override
-	protected String getPageName() {
-		return getString("gb.history");
-	}
-}
diff --git a/src/com/gitblit/wicket/pages/LogPage.html b/src/com/gitblit/wicket/pages/LogPage.html
deleted file mode 100644
index 8e5cb96..0000000
--- a/src/com/gitblit/wicket/pages/LogPage.html
+++ /dev/null
@@ -1,25 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml"  
-      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"  
-      xml:lang="en"  
-      lang="en"> 
-
-<body>
-<wicket:extend>
-
-	<!-- pager links -->
-	<div style="padding-top:5px;">
-		<a wicket:id="firstPageTop"><wicket:message key="gb.pageFirst"></wicket:message></a> | <a wicket:id="prevPageTop"><wicket:message key="gb.pagePrevious"></wicket:message></a> | <a wicket:id="nextPageTop"><wicket:message key="gb.pageNext"></wicket:message></a> 
-	</div>
-	
-	<!-- log -->
-	<div style="margin-top:5px;" wicket:id="logPanel">[log panel]</div>
-
-	<!-- pager links -->
-	<div style="padding-bottom:5px;">
-		<a wicket:id="firstPageBottom"><wicket:message key="gb.pageFirst"></wicket:message></a> | <a wicket:id="prevPageBottom"><wicket:message key="gb.pagePrevious"></wicket:message></a> | <a wicket:id="nextPageBottom"><wicket:message key="gb.pageNext"></wicket:message></a> 
-	</div>
-	
-</wicket:extend>
-</body>
-</html>
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/pages/LogPage.java b/src/com/gitblit/wicket/pages/LogPage.java
deleted file mode 100644
index ee8ddfe..0000000
--- a/src/com/gitblit/wicket/pages/LogPage.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * 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.wicket.pages;
-
-import org.apache.wicket.PageParameters;
-import org.apache.wicket.markup.html.link.BookmarkablePageLink;
-
-import com.gitblit.utils.StringUtils;
-import com.gitblit.wicket.WicketUtils;
-import com.gitblit.wicket.panels.LogPanel;
-
-public class LogPage extends RepositoryPage {
-
-	public LogPage(PageParameters params) {
-		super(params);
-
-		addSyndicationDiscoveryLink();
-
-		int pageNumber = WicketUtils.getPage(params);
-		int prevPage = Math.max(0, pageNumber - 1);
-		int nextPage = pageNumber + 1;
-		String refid = objectId;
-		if (StringUtils.isEmpty(refid)) {
-			refid = getRepositoryModel().HEAD;
-		}
-		LogPanel logPanel = new LogPanel("logPanel", repositoryName, refid, getRepository(), -1,
-				pageNumber - 1, getRepositoryModel().showRemoteBranches);
-		boolean hasMore = logPanel.hasMore();
-		add(logPanel);
-
-		add(new BookmarkablePageLink<Void>("firstPageTop", LogPage.class,
-				WicketUtils.newObjectParameter(repositoryName, objectId))
-				.setEnabled(pageNumber > 1));
-		add(new BookmarkablePageLink<Void>("prevPageTop", LogPage.class,
-				WicketUtils.newLogPageParameter(repositoryName, objectId, prevPage))
-				.setEnabled(pageNumber > 1));
-		add(new BookmarkablePageLink<Void>("nextPageTop", LogPage.class,
-				WicketUtils.newLogPageParameter(repositoryName, objectId, nextPage))
-				.setEnabled(hasMore));
-
-		add(new BookmarkablePageLink<Void>("firstPageBottom", LogPage.class,
-				WicketUtils.newObjectParameter(repositoryName, objectId))
-				.setEnabled(pageNumber > 1));
-		add(new BookmarkablePageLink<Void>("prevPageBottom", LogPage.class,
-				WicketUtils.newLogPageParameter(repositoryName, objectId, prevPage))
-				.setEnabled(pageNumber > 1));
-		add(new BookmarkablePageLink<Void>("nextPageBottom", LogPage.class,
-				WicketUtils.newLogPageParameter(repositoryName, objectId, nextPage))
-				.setEnabled(hasMore));
-	}
-
-	@Override
-	protected String getPageName() {
-		return getString("gb.log");
-	}
-}
diff --git a/src/com/gitblit/wicket/pages/LogoutPage.java b/src/com/gitblit/wicket/pages/LogoutPage.java
deleted file mode 100644
index 4690ad1..0000000
--- a/src/com/gitblit/wicket/pages/LogoutPage.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * 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.wicket.pages;
-
-import org.apache.wicket.markup.html.WebPage;
-import org.apache.wicket.protocol.http.WebResponse;
-
-import com.gitblit.GitBlit;
-import com.gitblit.models.UserModel;
-import com.gitblit.wicket.GitBlitWebSession;
-
-public class LogoutPage extends WebPage {
-
-	public LogoutPage() {
-		GitBlitWebSession session = GitBlitWebSession.get();
-		UserModel user = session.getUser();
-		GitBlit.self().setCookie((WebResponse) getResponse(), null);
-		GitBlit.self().logout(user);
-		session.invalidate();		
-		setRedirect(true);
-		setResponsePage(getApplication().getHomePage());
-	}
-}
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/pages/LuceneSearchPage.html b/src/com/gitblit/wicket/pages/LuceneSearchPage.html
deleted file mode 100644
index aba43de..0000000
--- a/src/com/gitblit/wicket/pages/LuceneSearchPage.html
+++ /dev/null
@@ -1,92 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml"  
-      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"  
-      xml:lang="en"  
-      lang="en"> 
-
-<!-- contribute google-code-prettify resources to the page header -->
-<wicket:head>
-  <wicket:link>
-   	<link href="prettify/prettify.css" type="text/css" rel="stylesheet" />
-	<script type="text/javascript" src="prettify/prettify.js"></script>
-  </wicket:link>
-</wicket:head>
-      
-<wicket:extend>
-<body onload="document.getElementById('query').focus(); prettyPrint();">
-	<div class="pageTitle">
-		<h2><wicket:message key="gb.search"></wicket:message></h2>
-	</div>
-	<form class="form-inline" wicket:id="searchForm">
-		<div class="row">
-			<div class="span3">
-				<h3><wicket:message key="gb.repositories"></wicket:message></h3>
-				<select wicket:id="repositories" ></select>
-			</div>
-			<div class="span9" style="margin-left:10px">
-				<div>
-					<h3><wicket:message key="gb.query"></wicket:message></h3>
-					<input class="span8" id="query" type="text" wicket:id="query" placeholder="enter search text"></input>
-					<button class="btn btn-primary" type="submit" value="Search"><wicket:message key="gb.search"></wicket:message></button>
-				</div>
-				<div style="margin-top:10px;">
-					<div style="margin-left:0px;" class="span3">
-						<div class="alert alert">
-							<b>type:</b> commit or blob<br/>
-							<b>commit:</b> commit id<br/>
-							<b>path:</b> "path/to/blob"<br/>
-							<b>branch:</b> "refs/heads/master"<br/>
-							<b>author:</b> or <b>committer:</b>							
-						</div>
-					</div>
-					<div style="margin-left:10px;" class="span4">						
-						<div class="alert alert-info">
-							type:commit AND "bug fix"<br/>
-							type:commit AND author:james*<br/>
-							type:blob AND "int errorCode"<br/>
-							type:blob AND test AND path:*.java<br/>
-							commit:d91e5*
-						</div>
-					</div>
-					<div style="margin-left:10px;" class="span2">
-						<wicket:message key="gb.queryHelp"></wicket:message>
-					</div>
-				</div>
-			</div>
-		</div>
-	</form>
-
-	<div class="row-fluid">	
-	<!-- results header -->
-	<div class="span8">
-		<h3><span wicket:id="resultsHeader"></span> <small><br/><span wicket:id="resultsCount"></span></small></h3>
-	</div>
-	<!-- pager links -->
-	<div class="span4" wicket:id="topPager"></div>
-	</div>
-	
-	<div class="row-fluid">	
-	<!--  search result repeater -->
-	<div class="searchResult" wicket:id="searchResults">
-		<div><i wicket:id="type"></i><span class="summary" wicket:id="summary"></span> <span wicket:id="tags" style="padding-left:10px;"></span></div>
-		<div class="body">
-			<div class="fragment" wicket:id="fragment"></div>
-			<div><span class="author" wicket:id="author"></span> <span class="date" ><wicket:message key="gb.authored"></wicket:message> <span class="date" wicket:id="date"></span></span></div>
-			<span class="repository" wicket:id="repository"></span>:<span class="branch" wicket:id="branch"></span>		
-		</div>
-	</div>
-
-	<!-- pager links -->
-	<div wicket:id="bottomPager"></div>
-
-	</div>	
-</body>
-
-	<wicket:fragment wicket:id="tagsPanel">
-		<span wicket:id="tag">
-			<span wicket:id="tagLink">[tag]</span>
-		</span>	
-	</wicket:fragment>
-
-</wicket:extend>
-</html>
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/pages/MarkdownPage.java b/src/com/gitblit/wicket/pages/MarkdownPage.java
deleted file mode 100644
index e032cbf..0000000
--- a/src/com/gitblit/wicket/pages/MarkdownPage.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * 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.wicket.pages;
-
-import java.text.MessageFormat;
-import java.text.ParseException;
-
-import org.apache.wicket.PageParameters;
-import org.apache.wicket.markup.html.basic.Label;
-import org.apache.wicket.markup.html.link.BookmarkablePageLink;
-import org.eclipse.jgit.lib.Constants;
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.revwalk.RevCommit;
-
-import com.gitblit.GitBlit;
-import com.gitblit.utils.JGitUtils;
-import com.gitblit.utils.MarkdownUtils;
-import com.gitblit.utils.StringUtils;
-import com.gitblit.wicket.WicketUtils;
-
-public class MarkdownPage extends RepositoryPage {
-
-	public MarkdownPage(PageParameters params) {
-		super(params);
-
-		final String markdownPath = WicketUtils.getPath(params);
-
-		Repository r = getRepository();
-		RevCommit commit = JGitUtils.getCommit(r, objectId);
-		String [] encodings = GitBlit.getEncodings();
-		
-		// markdown page links
-		add(new BookmarkablePageLink<Void>("blameLink", BlamePage.class,
-				WicketUtils.newPathParameter(repositoryName, objectId, markdownPath)));
-		add(new BookmarkablePageLink<Void>("historyLink", HistoryPage.class,
-				WicketUtils.newPathParameter(repositoryName, objectId, markdownPath)));
-		add(new BookmarkablePageLink<Void>("rawLink", RawPage.class, WicketUtils.newPathParameter(
-				repositoryName, objectId, markdownPath)));
-		add(new BookmarkablePageLink<Void>("headLink", MarkdownPage.class,
-				WicketUtils.newPathParameter(repositoryName, Constants.HEAD, markdownPath)));
-
-		// Read raw markdown content and transform it to html
-		String markdownText = JGitUtils.getStringContent(r, commit.getTree(), markdownPath, encodings);
-		String htmlText;
-		try {
-			htmlText = MarkdownUtils.transformMarkdown(markdownText);
-		} catch (ParseException p) {
-			markdownText = MessageFormat.format("<div class=\"alert alert-error\"><strong>{0}:</strong> {1}</div>{2}", getString("gb.error"), getString("gb.markdownFailure"), markdownText);
-			htmlText = StringUtils.breakLinesForHtml(markdownText);
-		}
-
-		// Add the html to the page
-		add(new Label("markdownText", htmlText).setEscapeModelStrings(false));
-	}
-
-	@Override
-	protected String getPageName() {
-		return getString("gb.markdown");
-	}
-}
diff --git a/src/com/gitblit/wicket/pages/MetricsPage.java b/src/com/gitblit/wicket/pages/MetricsPage.java
deleted file mode 100644
index 5904a64..0000000
--- a/src/com/gitblit/wicket/pages/MetricsPage.java
+++ /dev/null
@@ -1,184 +0,0 @@
-/*
- * 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.wicket.pages;
-
-import java.awt.Color;
-import java.awt.Dimension;
-import java.text.MessageFormat;
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Calendar;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
-
-import org.apache.wicket.PageParameters;
-import org.apache.wicket.markup.html.basic.Label;
-import org.eclipse.jgit.lib.Repository;
-import org.wicketstuff.googlecharts.Chart;
-import org.wicketstuff.googlecharts.ChartAxis;
-import org.wicketstuff.googlecharts.ChartAxisType;
-import org.wicketstuff.googlecharts.ChartProvider;
-import org.wicketstuff.googlecharts.ChartType;
-import org.wicketstuff.googlecharts.IChartData;
-import org.wicketstuff.googlecharts.LineStyle;
-import org.wicketstuff.googlecharts.MarkerType;
-import org.wicketstuff.googlecharts.ShapeMarker;
-
-import com.gitblit.models.Metric;
-import com.gitblit.utils.MetricUtils;
-import com.gitblit.utils.StringUtils;
-import com.gitblit.wicket.WicketUtils;
-
-public class MetricsPage extends RepositoryPage {
-
-	public MetricsPage(PageParameters params) {
-		super(params);
-		Repository r = getRepository();
-		if (StringUtils.isEmpty(objectId)) {
-			add(new Label("branchTitle", getRepositoryModel().HEAD));
-		} else {
-			add(new Label("branchTitle", objectId));
-		}
-		Metric metricsTotal = null;
-		List<Metric> metrics = MetricUtils.getDateMetrics(r, objectId, true, null, getTimeZone());
-		metricsTotal = metrics.remove(0);
-		if (metricsTotal == null) {
-			add(new Label("branchStats", ""));
-		} else {
-			add(new Label("branchStats",
-					MessageFormat.format(getString("gb.branchStats"), metricsTotal.count,
-							metricsTotal.tag, getTimeUtils().duration(metricsTotal.duration))));
-		}
-		insertLinePlot("commitsChart", metrics);
-		insertBarPlot("dayOfWeekChart", getDayOfWeekMetrics(r, objectId));
-		insertPieChart("authorsChart", getAuthorMetrics(r, objectId));
-	}
-
-	private void insertLinePlot(String wicketId, List<Metric> metrics) {
-		if ((metrics != null) && (metrics.size() > 0)) {
-			IChartData data = WicketUtils.getChartData(metrics);
-
-			ChartProvider provider = new ChartProvider(new Dimension(400, 100), ChartType.LINE,
-					data);
-			ChartAxis dateAxis = new ChartAxis(ChartAxisType.BOTTOM);
-			dateAxis.setLabels(new String[] { metrics.get(0).name,
-					metrics.get(metrics.size() / 2).name, metrics.get(metrics.size() - 1).name });
-			provider.addAxis(dateAxis);
-
-			ChartAxis commitAxis = new ChartAxis(ChartAxisType.LEFT);
-			commitAxis.setLabels(new String[] { "",
-					String.valueOf((int) WicketUtils.maxValue(metrics)) });
-			provider.addAxis(commitAxis);
-
-			provider.setLineStyles(new LineStyle[] { new LineStyle(2, 4, 0), new LineStyle(0, 4, 1) });
-			provider.addShapeMarker(new ShapeMarker(MarkerType.CIRCLE, Color.BLUE, 1, -1, 5));
-
-			add(new Chart(wicketId, provider));
-		} else {
-			add(WicketUtils.newBlankImage(wicketId));
-		}
-	}
-
-	private void insertBarPlot(String wicketId, List<Metric> metrics) {
-		if ((metrics != null) && (metrics.size() > 0)) {
-			IChartData data = WicketUtils.getChartData(metrics);
-
-			ChartProvider provider = new ChartProvider(new Dimension(400, 100),
-					ChartType.BAR_VERTICAL_SET, data);
-			ChartAxis dateAxis = new ChartAxis(ChartAxisType.BOTTOM);
-			List<String> labels = new ArrayList<String>();
-			for (Metric metric : metrics) {
-				labels.add(metric.name);
-			}
-			dateAxis.setLabels(labels.toArray(new String[labels.size()]));
-			provider.addAxis(dateAxis);
-
-			ChartAxis commitAxis = new ChartAxis(ChartAxisType.LEFT);
-			commitAxis.setLabels(new String[] { "",
-					String.valueOf((int) WicketUtils.maxValue(metrics)) });
-			provider.addAxis(commitAxis);
-
-			add(new Chart(wicketId, provider));
-		} else {
-			add(WicketUtils.newBlankImage(wicketId));
-		}
-	}
-
-	private void insertPieChart(String wicketId, List<Metric> metrics) {
-		if ((metrics != null) && (metrics.size() > 0)) {
-			IChartData data = WicketUtils.getChartData(metrics);
-			List<String> labels = new ArrayList<String>();
-			for (Metric metric : metrics) {
-				labels.add(metric.name);
-			}
-			ChartProvider provider = new ChartProvider(new Dimension(800, 200), ChartType.PIE, data);
-			provider.setPieLabels(labels.toArray(new String[labels.size()]));
-			add(new Chart(wicketId, provider));
-		} else {
-			add(WicketUtils.newBlankImage(wicketId));
-		}
-	}
-
-	private List<Metric> getDayOfWeekMetrics(Repository repository, String objectId) {
-		List<Metric> list = MetricUtils.getDateMetrics(repository, objectId, false, "E", getTimeZone());
-		SimpleDateFormat sdf = new SimpleDateFormat("E");
-		Calendar cal = Calendar.getInstance();
-
-		List<Metric> sorted = new ArrayList<Metric>();
-		int firstDayOfWeek = cal.getFirstDayOfWeek();
-		int dayOfWeek = cal.get(Calendar.DAY_OF_WEEK);
-
-		// rewind date to first day of week
-		cal.add(Calendar.DATE, firstDayOfWeek - dayOfWeek);
-		for (int i = 0; i < 7; i++) {
-			String day = sdf.format(cal.getTime());
-			for (Metric metric : list) {
-				if (metric.name.equals(day)) {
-					sorted.add(metric);
-					list.remove(metric);
-					break;
-				}
-			}
-			cal.add(Calendar.DATE, 1);
-		}
-		return sorted;
-	}
-
-	private List<Metric> getAuthorMetrics(Repository repository, String objectId) {
-		List<Metric> authors = MetricUtils.getAuthorMetrics(repository, objectId, true);
-		Collections.sort(authors, new Comparator<Metric>() {
-			@Override
-			public int compare(Metric o1, Metric o2) {
-				if (o1.count > o2.count) {
-					return -1;
-				} else if (o1.count < o2.count) {
-					return 1;
-				}
-				return 0;
-			}
-		});
-		if (authors.size() > 10) {
-			return authors.subList(0, 9);
-		}
-		return authors;
-	}
-
-	@Override
-	protected String getPageName() {
-		return getString("gb.metrics");
-	}
-}
diff --git a/src/com/gitblit/wicket/pages/PatchPage.java b/src/com/gitblit/wicket/pages/PatchPage.java
deleted file mode 100644
index 878cfb4..0000000
--- a/src/com/gitblit/wicket/pages/PatchPage.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * 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.wicket.pages;
-
-import org.apache.wicket.PageParameters;
-import org.apache.wicket.markup.html.WebPage;
-import org.apache.wicket.markup.html.basic.Label;
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.revwalk.RevCommit;
-
-import com.gitblit.GitBlit;
-import com.gitblit.utils.DiffUtils;
-import com.gitblit.utils.JGitUtils;
-import com.gitblit.utils.StringUtils;
-import com.gitblit.wicket.GitBlitWebSession;
-import com.gitblit.wicket.WicketUtils;
-
-public class PatchPage extends WebPage {
-
-	public PatchPage(PageParameters params) {
-		super(params);
-
-		if (!params.containsKey("r")) {
-			GitBlitWebSession.get().cacheErrorMessage(getString("gb.repositoryNotSpecified"));
-			redirectToInterceptPage(new RepositoriesPage());
-			return;
-		}
-
-		final String repositoryName = WicketUtils.getRepositoryName(params);
-		final String baseObjectId = WicketUtils.getBaseObjectId(params);
-		final String objectId = WicketUtils.getObject(params);
-		final String blobPath = WicketUtils.getPath(params);
-
-		Repository r = GitBlit.self().getRepository(repositoryName);
-		if (r == null) {
-			GitBlitWebSession.get().cacheErrorMessage(getString("gb.canNotLoadRepository") + " " + repositoryName);
-			redirectToInterceptPage(new RepositoriesPage());
-			return;
-		}
-
-		RevCommit commit = JGitUtils.getCommit(r, objectId);
-		if (commit == null) {
-			GitBlitWebSession.get().cacheErrorMessage(getString("gb.commitIsNull"));
-			redirectToInterceptPage(new RepositoriesPage());
-			return;
-		}
-
-		RevCommit baseCommit = null;
-		if (!StringUtils.isEmpty(baseObjectId)) {
-			baseCommit = JGitUtils.getCommit(r, baseObjectId);
-		}
-		String patch = DiffUtils.getCommitPatch(r, baseCommit, commit, blobPath);
-		add(new Label("patchText", patch));
-		r.close();
-	}
-}
diff --git a/src/com/gitblit/wicket/pages/ProjectPage.html b/src/com/gitblit/wicket/pages/ProjectPage.html
deleted file mode 100644
index 3e73ba5..0000000
--- a/src/com/gitblit/wicket/pages/ProjectPage.html
+++ /dev/null
@@ -1,70 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml"  
-      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"  
-      xml:lang="en"  
-      lang="en"> 
-
-<body>
-<wicket:extend>
-
-
-	<div class="row">
-		<div class="span12">
-			<h2><span wicket:id="projectTitle"></span> <small><span wicket:id="projectDescription"></span></small>
-				<a class="hidden-phone hidden-tablet brand" style="text-decoration: none;" wicket:id="syndication" wicket:message="title:gb.feed">
-					<img style="border:0px;vertical-align:middle;" src="feed_16x16.png"></img>
-				</a>
-			</h2>
-			<div class="markdown" wicket:id="projectMessage">[project message]</div>
-		</div>
-	</div>
-
-	<div class="tabbable">
-		<!-- tab titles -->
-		<ul class="nav nav-tabs">
-			<li class="active"><a href="#repositories" data-toggle="tab"><wicket:message key="gb.repositories"></wicket:message></a></li>
-			<li ><a href="#activity" data-toggle="tab"><wicket:message key="gb.activity"></wicket:message></a></li>
-		</ul>
-	
-		<!-- tab content -->
-		<div class="tab-content">
-
-			<!-- repositories tab -->
-			<div class="tab-pane active" id="repositories">
-				<!-- markdown -->
-				<div class="row">
-					<div class="span12">
-						<div class="markdown" wicket:id="repositoriesMessage">[repositories message]</div>
-					</div>
-				</div>
-				<div class="row">
-					<div class="span6" style="border-bottom:1px solid #eee;" wicket:id="repositoryList">
-						<span wicket:id="repository"></span>
-					</div>
-				</div>				
-			</div>
-			
-			<!-- activity tab -->
-			<div class="tab-pane" id="activity">
-				<div class="pageTitle">
-					<h2><wicket:message key="gb.recentActivity"></wicket:message><small> <span class="hidden-phone">/ <span wicket:id="subheader">[days back]</span></span></small></h2>
-				</div>
-			
-				<div class="hidden-phone" style="height: 155px;text-align: center;">
-					<table>
-					<tr>
-						<td><span class="hidden-tablet" id="chartDaily"></span></td>
-						<td><span id="chartRepositories"></span></td>
-						<td><span id="chartAuthors"></span></td>
-					</tr>
-					</table>
-				</div>
-			
-				<div wicket:id="activityPanel">[activity panel]</div>
-			</div>
-		
-		</div>
-	</div>
-</wicket:extend>
-</body>
-</html>
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/pages/ProjectPage.java b/src/com/gitblit/wicket/pages/ProjectPage.java
deleted file mode 100644
index 7eba033..0000000
--- a/src/com/gitblit/wicket/pages/ProjectPage.java
+++ /dev/null
@@ -1,355 +0,0 @@
-/*
- * 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.wicket.pages;
-
-import java.text.MessageFormat;
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import org.apache.wicket.Component;
-import org.apache.wicket.PageParameters;
-import org.apache.wicket.behavior.HeaderContributor;
-import org.apache.wicket.markup.html.basic.Label;
-import org.apache.wicket.markup.html.link.ExternalLink;
-import org.apache.wicket.markup.repeater.Item;
-import org.apache.wicket.markup.repeater.data.DataView;
-import org.apache.wicket.markup.repeater.data.ListDataProvider;
-
-import com.gitblit.GitBlit;
-import com.gitblit.Keys;
-import com.gitblit.SyndicationServlet;
-import com.gitblit.models.Activity;
-import com.gitblit.models.Metric;
-import com.gitblit.models.ProjectModel;
-import com.gitblit.models.RepositoryModel;
-import com.gitblit.utils.ActivityUtils;
-import com.gitblit.utils.MarkdownUtils;
-import com.gitblit.utils.StringUtils;
-import com.gitblit.wicket.GitBlitWebApp;
-import com.gitblit.wicket.GitBlitWebSession;
-import com.gitblit.wicket.GitblitRedirectException;
-import com.gitblit.wicket.PageRegistration;
-import com.gitblit.wicket.PageRegistration.DropDownMenuItem;
-import com.gitblit.wicket.PageRegistration.DropDownMenuRegistration;
-import com.gitblit.wicket.WicketUtils;
-import com.gitblit.wicket.charting.GoogleChart;
-import com.gitblit.wicket.charting.GoogleCharts;
-import com.gitblit.wicket.charting.GoogleLineChart;
-import com.gitblit.wicket.charting.GooglePieChart;
-import com.gitblit.wicket.panels.ActivityPanel;
-import com.gitblit.wicket.panels.ProjectRepositoryPanel;
-
-public class ProjectPage extends RootPage {
-	
-	List<ProjectModel> projectModels = new ArrayList<ProjectModel>();
-
-	public ProjectPage() {
-		super();
-		throw new GitblitRedirectException(GitBlitWebApp.get().getHomePage());
-	}
-
-	public ProjectPage(PageParameters params) {
-		super(params);
-		setup(params);
-	}
-
-	@Override
-	protected boolean reusePageParameters() {
-		return true;
-	}
-
-	private void setup(PageParameters params) {
-		setupPage("", "");
-		// check to see if we should display a login message
-		boolean authenticateView = GitBlit.getBoolean(Keys.web.authenticateViewPages, true);
-		if (authenticateView && !GitBlitWebSession.get().isLoggedIn()) {
-			authenticationError("Please login");
-			return;
-		}
-
-		String projectName = WicketUtils.getProjectName(params);
-		if (StringUtils.isEmpty(projectName)) {
-			throw new GitblitRedirectException(GitBlitWebApp.get().getHomePage());
-		}
-		
-		ProjectModel project = getProjectModel(projectName);
-		if (project == null) {
-			throw new GitblitRedirectException(GitBlitWebApp.get().getHomePage());
-		}
-		
-		add(new Label("projectTitle", project.getDisplayName()));
-		add(new Label("projectDescription", project.description));
-		
-		String feedLink = SyndicationServlet.asLink(getRequest().getRelativePathPrefixToContextRoot(), projectName, null, 0);
-		add(new ExternalLink("syndication", feedLink));
-
-		add(WicketUtils.syndicationDiscoveryLink(SyndicationServlet.getTitle(project.getDisplayName(),
-				null), feedLink));
-		
-		// project markdown message
-		String pmessage = transformMarkdown(project.projectMarkdown);
-		Component projectMessage = new Label("projectMessage", pmessage)
-				.setEscapeModelStrings(false).setVisible(pmessage.length() > 0);
-		add(projectMessage);
-
-		// markdown message above repositories list
-		String rmessage = transformMarkdown(project.repositoriesMarkdown);
-		Component repositoriesMessage = new Label("repositoriesMessage", rmessage)
-				.setEscapeModelStrings(false).setVisible(rmessage.length() > 0);
-		add(repositoriesMessage);
-
-		List<RepositoryModel> repositories = getRepositories(params);
-		
-		Collections.sort(repositories, new Comparator<RepositoryModel>() {
-			@Override
-			public int compare(RepositoryModel o1, RepositoryModel o2) {
-				// reverse-chronological sort
-				return o2.lastChange.compareTo(o1.lastChange);
-			}
-		});
-
-		final ListDataProvider<RepositoryModel> dp = new ListDataProvider<RepositoryModel>(repositories);
-		DataView<RepositoryModel> dataView = new DataView<RepositoryModel>("repositoryList", dp) {
-			private static final long serialVersionUID = 1L;
-
-			public void populateItem(final Item<RepositoryModel> item) {
-				final RepositoryModel entry = item.getModelObject();
-				
-				ProjectRepositoryPanel row = new ProjectRepositoryPanel("repository", 
-						getLocalizer(), this, showAdmin, entry, getAccessRestrictions());
-				item.add(row);
-			}
-		};
-		add(dataView);
-
-		// project activity
-		// parameters
-		int daysBack = WicketUtils.getDaysBack(params);
-		if (daysBack < 1) {
-			daysBack = 14;
-		}
-		String objectId = WicketUtils.getObject(params);
-
-		List<Activity> recentActivity = ActivityUtils.getRecentActivity(repositories, 
-				daysBack, objectId, getTimeZone());
-		if (recentActivity.size() == 0) {
-			// no activity, skip graphs and activity panel
-			add(new Label("subheader", MessageFormat.format(getString("gb.recentActivityNone"),
-					daysBack)));
-			add(new Label("activityPanel"));
-		} else {
-			// calculate total commits and total authors
-			int totalCommits = 0;
-			Set<String> uniqueAuthors = new HashSet<String>();
-			for (Activity activity : recentActivity) {
-				totalCommits += activity.getCommitCount();
-				uniqueAuthors.addAll(activity.getAuthorMetrics().keySet());
-			}
-			int totalAuthors = uniqueAuthors.size();
-
-			// add the subheader with stat numbers
-			add(new Label("subheader", MessageFormat.format(getString("gb.recentActivityStats"),
-					daysBack, totalCommits, totalAuthors)));
-
-			// create the activity charts
-			GoogleCharts charts = createCharts(recentActivity);
-			add(new HeaderContributor(charts));
-
-			// add activity panel
-			add(new ActivityPanel("activityPanel", recentActivity));
-		}
-	}
-	
-	/**
-	 * Creates the daily activity line chart, the active repositories pie chart,
-	 * and the active authors pie chart
-	 * 
-	 * @param recentActivity
-	 * @return
-	 */
-	private GoogleCharts createCharts(List<Activity> recentActivity) {
-		// activity metrics
-		Map<String, Metric> repositoryMetrics = new HashMap<String, Metric>();
-		Map<String, Metric> authorMetrics = new HashMap<String, Metric>();
-
-		// aggregate repository and author metrics
-		for (Activity activity : recentActivity) {
-
-			// aggregate author metrics
-			for (Map.Entry<String, Metric> entry : activity.getAuthorMetrics().entrySet()) {
-				String author = entry.getKey();
-				if (!authorMetrics.containsKey(author)) {
-					authorMetrics.put(author, new Metric(author));
-				}
-				authorMetrics.get(author).count += entry.getValue().count;
-			}
-
-			// aggregate repository metrics
-			for (Map.Entry<String, Metric> entry : activity.getRepositoryMetrics().entrySet()) {
-				String repository = StringUtils.stripDotGit(entry.getKey());
-				if (!repositoryMetrics.containsKey(repository)) {
-					repositoryMetrics.put(repository, new Metric(repository));
-				}
-				repositoryMetrics.get(repository).count += entry.getValue().count;
-			}
-		}
-
-		// build google charts
-		int w = 310;
-		int h = 150;
-		GoogleCharts charts = new GoogleCharts();
-
-		// sort in reverse-chronological order and then reverse that
-		Collections.sort(recentActivity);
-		Collections.reverse(recentActivity);
-
-		// daily line chart
-		GoogleChart chart = new GoogleLineChart("chartDaily", getString("gb.dailyActivity"), "day",
-				getString("gb.commits"));
-		SimpleDateFormat df = new SimpleDateFormat("MMM dd");
-		df.setTimeZone(getTimeZone());
-		for (Activity metric : recentActivity) {
-			chart.addValue(df.format(metric.startDate), metric.getCommitCount());
-		}
-		chart.setWidth(w);
-		chart.setHeight(h);
-		charts.addChart(chart);
-
-		// active repositories pie chart
-		chart = new GooglePieChart("chartRepositories", getString("gb.activeRepositories"),
-				getString("gb.repository"), getString("gb.commits"));
-		for (Metric metric : repositoryMetrics.values()) {
-			chart.addValue(metric.name, metric.count);
-		}
-		chart.setWidth(w);
-		chart.setHeight(h);
-		charts.addChart(chart);
-
-		// active authors pie chart
-		chart = new GooglePieChart("chartAuthors", getString("gb.activeAuthors"),
-				getString("gb.author"), getString("gb.commits"));
-		for (Metric metric : authorMetrics.values()) {
-			chart.addValue(metric.name, metric.count);
-		}
-		chart.setWidth(w);
-		chart.setHeight(h);
-		charts.addChart(chart);
-
-		return charts;
-	}
-
-	@Override
-	protected void addDropDownMenus(List<PageRegistration> pages) {
-		PageParameters params = getPageParameters();
-
-		DropDownMenuRegistration projects = new DropDownMenuRegistration("gb.projects",
-				ProjectPage.class);
-		projects.menuItems.addAll(getProjectsMenu());
-		pages.add(0, projects);
-
-		DropDownMenuRegistration menu = new DropDownMenuRegistration("gb.filters",
-				ProjectPage.class);
-		// preserve time filter option on repository choices
-		menu.menuItems.addAll(getRepositoryFilterItems(params));
-
-		// preserve repository filter option on time choices
-		menu.menuItems.addAll(getTimeFilterItems(params));
-
-		if (menu.menuItems.size() > 0) {
-			// Reset Filter
-			menu.menuItems.add(new DropDownMenuItem(getString("gb.reset"), null, null));
-		}
-
-		pages.add(menu);
-	}
-	
-	@Override
-	protected List<ProjectModel> getProjectModels() {
-		if (projectModels.isEmpty()) {
-			List<RepositoryModel> repositories = getRepositoryModels();
-			List<ProjectModel> projects = GitBlit.self().getProjectModels(repositories, false);
-			projectModels.addAll(projects);
-		}
-		return projectModels;
-	}
-	
-	private ProjectModel getProjectModel(String name) {
-		for (ProjectModel project : getProjectModels()) {
-			if (name.equalsIgnoreCase(project.name)) {
-				return project;
-			}
-		}
-		return null;
-	}
-	
-	protected List<DropDownMenuItem> getProjectsMenu() {
-		List<DropDownMenuItem> menu = new ArrayList<DropDownMenuItem>();
-		List<ProjectModel> projects = new ArrayList<ProjectModel>();
-		for (ProjectModel model : getProjectModels()) {
-			if (!model.isUserProject()) {
-				projects.add(model);
-			}
-		}
-		int maxProjects = 15;
-		boolean showAllProjects = projects.size() > maxProjects;
-		if (showAllProjects) {
-
-			// sort by last changed
-			Collections.sort(projects, new Comparator<ProjectModel>() {
-				@Override
-				public int compare(ProjectModel o1, ProjectModel o2) {
-					return o2.lastChange.compareTo(o1.lastChange);
-				}
-			});
-
-			// take most recent subset
-			projects = projects.subList(0, maxProjects);
-
-			// sort those by name
-			Collections.sort(projects);
-		}
-
-		for (ProjectModel project : projects) {
-			menu.add(new DropDownMenuItem(project.getDisplayName(), "p", project.name));
-		}
-		if (showAllProjects) {
-			menu.add(new DropDownMenuItem());
-			menu.add(new DropDownMenuItem("all projects", null, null));
-		}
-		return menu;
-	}
-	
-	private String transformMarkdown(String markdown) {
-		String message = "";
-		if (!StringUtils.isEmpty(markdown)) {
-			// Read user-supplied message
-			try {
-				message = MarkdownUtils.transformMarkdown(markdown);
-			} catch (Throwable t) {
-				message = getString("gb.failedToRead") + " " + markdown;
-				warn(message, t);
-			}
-		}
-		return message;
-	}
-}
diff --git a/src/com/gitblit/wicket/pages/ProjectsPage.html b/src/com/gitblit/wicket/pages/ProjectsPage.html
deleted file mode 100644
index 528ed48..0000000
--- a/src/com/gitblit/wicket/pages/ProjectsPage.html
+++ /dev/null
@@ -1,37 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml"  
-      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"  
-      xml:lang="en"  
-      lang="en"> 
-
-<body>
-<wicket:extend>
-	<div class="markdown" style="padding-bottom:5px;" wicket:id="projectsMessage">[projects message]</div>
-	
-	<table class="repositories">
-		<thead>
-			<tr>	
-				<th class="left">
-					<i class="icon-folder-close" ></i>
-					<wicket:message key="gb.project">Project</wicket:message>
-				</th>
-				<th class="hidden-phone" ><span><wicket:message key="gb.description">Description</wicket:message></span></th>
-				<th class="hidden-phone"><wicket:message key="gb.repositories">Repositories</wicket:message></th>
-				<th><wicket:message key="gb.lastChange">Last Change</wicket:message></th>
-				<th class="right"></th>
-			</tr>
-		</thead>
-		<tbody>		
-       		<tr wicket:id="project">
-				<td class="left" style="padding-left:3px;" ><span style="padding-left:3px;" wicket:id="projectTitle">[project title]</span></td>
-        		<td class="hidden-phone"><span class="list" wicket:id="projectDescription">[project description]</span></td>
-        		<td class="hidden-phone" style="padding-right:15px;"><span style="font-size:0.8em;" wicket:id="repositoryCount">[repository count]</span></td>
-        		<td><span wicket:id="projectLastChange">[last change]</span></td>
-		        <td class="rightAlign"></td>				       			
-       		</tr>
-    	</tbody>
-	</table>
-
-</wicket:extend>
-</body>
-</html>
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/pages/ProjectsPage.java b/src/com/gitblit/wicket/pages/ProjectsPage.java
deleted file mode 100644
index 7f0b002..0000000
--- a/src/com/gitblit/wicket/pages/ProjectsPage.java
+++ /dev/null
@@ -1,235 +0,0 @@
-/*
- * 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.wicket.pages;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.text.MessageFormat;
-import java.util.ArrayList;
-import java.util.List;
-
-import org.apache.wicket.Component;
-import org.apache.wicket.PageParameters;
-import org.apache.wicket.markup.html.basic.Label;
-import org.apache.wicket.markup.repeater.Item;
-import org.apache.wicket.markup.repeater.data.DataView;
-import org.apache.wicket.markup.repeater.data.ListDataProvider;
-import org.apache.wicket.resource.ContextRelativeResource;
-import org.apache.wicket.util.resource.ResourceStreamNotFoundException;
-import org.eclipse.jgit.lib.Constants;
-
-import com.gitblit.GitBlit;
-import com.gitblit.Keys;
-import com.gitblit.models.ProjectModel;
-import com.gitblit.utils.MarkdownUtils;
-import com.gitblit.utils.StringUtils;
-import com.gitblit.wicket.GitBlitWebSession;
-import com.gitblit.wicket.PageRegistration;
-import com.gitblit.wicket.PageRegistration.DropDownMenuItem;
-import com.gitblit.wicket.PageRegistration.DropDownMenuRegistration;
-import com.gitblit.wicket.WicketUtils;
-import com.gitblit.wicket.panels.LinkPanel;
-
-public class ProjectsPage extends RootPage {
-
-	public ProjectsPage() {
-		super();
-		setup(null);
-	}
-
-	public ProjectsPage(PageParameters params) {
-		super(params);
-		setup(params);
-	}
-
-	@Override
-	protected boolean reusePageParameters() {
-		return true;
-	}
-	
-	@Override
-	protected List<ProjectModel> getProjectModels() {
-		return GitBlit.self().getProjectModels(getRepositoryModels(), false);
-	}
-
-	private void setup(PageParameters params) {
-		setupPage("", "");
-		// check to see if we should display a login message
-		boolean authenticateView = GitBlit.getBoolean(Keys.web.authenticateViewPages, true);
-		if (authenticateView && !GitBlitWebSession.get().isLoggedIn()) {
-			String messageSource = GitBlit.getString(Keys.web.loginMessage, "gitblit");
-			String message = readMarkdown(messageSource, "login.mkd");
-			Component repositoriesMessage = new Label("projectsMessage", message);
-			add(repositoriesMessage.setEscapeModelStrings(false));
-			add(new Label("projectsPanel"));
-			return;
-		}
-
-		// Load the markdown welcome message
-		String messageSource = GitBlit.getString(Keys.web.repositoriesMessage, "gitblit");
-		String message = readMarkdown(messageSource, "welcome.mkd");
-		Component projectsMessage = new Label("projectsMessage", message).setEscapeModelStrings(
-				false).setVisible(message.length() > 0);
-		add(projectsMessage);
-
-		List<ProjectModel> projects = getProjects(params);
-
-		ListDataProvider<ProjectModel> dp = new ListDataProvider<ProjectModel>(projects);
-
-		DataView<ProjectModel> dataView = new DataView<ProjectModel>("project", dp) {
-			private static final long serialVersionUID = 1L;
-			int counter;
-
-			@Override
-			protected void onBeforeRender() {
-				super.onBeforeRender();
-				counter = 0;
-			}
-
-			public void populateItem(final Item<ProjectModel> item) {
-				final ProjectModel entry = item.getModelObject();
-
-				PageParameters pp = WicketUtils.newProjectParameter(entry.name);
-				item.add(new LinkPanel("projectTitle", "list", entry.getDisplayName(),
-						ProjectPage.class, pp));
-				item.add(new LinkPanel("projectDescription", "list", entry.description,
-						ProjectPage.class, pp));
-
-				item.add(new Label("repositoryCount", entry.repositories.size()
-						+ " "
-						+ (entry.repositories.size() == 1 ? getString("gb.repository")
-								: getString("gb.repositories"))));
-
-				String lastChange;
-				if (entry.lastChange.getTime() == 0) {
-					lastChange = "--";
-				} else {
-					lastChange = getTimeUtils().timeAgo(entry.lastChange);
-				}
-				Label lastChangeLabel = new Label("projectLastChange", lastChange);
-				item.add(lastChangeLabel);
-				WicketUtils.setCssClass(lastChangeLabel, getTimeUtils()
-						.timeAgoCss(entry.lastChange));
-				WicketUtils.setAlternatingBackground(item, counter);
-				counter++;
-			}
-		};
-		add(dataView);
-
-		// push the panel down if we are hiding the admin controls and the
-		// welcome message
-		if (!showAdmin && !projectsMessage.isVisible()) {
-			WicketUtils.setCssStyle(dataView, "padding-top:5px;");
-		}
-	}
-
-	@Override
-	protected void addDropDownMenus(List<PageRegistration> pages) {
-		PageParameters params = getPageParameters();
-		
-		pages.add(0, new PageRegistration("gb.projects", ProjectsPage.class, params));
-
-		DropDownMenuRegistration menu = new DropDownMenuRegistration("gb.filters",
-				ProjectsPage.class);
-		// preserve time filter option on repository choices
-		menu.menuItems.addAll(getRepositoryFilterItems(params));
-
-		// preserve repository filter option on time choices
-		menu.menuItems.addAll(getTimeFilterItems(params));
-
-		if (menu.menuItems.size() > 0) {
-			// Reset Filter
-			menu.menuItems.add(new DropDownMenuItem(getString("gb.reset"), null, null));
-		}
-
-		pages.add(menu);
-	}
-
-	private String readMarkdown(String messageSource, String resource) {
-		String message = "";
-		if (messageSource.equalsIgnoreCase("gitblit")) {
-			// Read default message
-			message = readDefaultMarkdown(resource);
-		} else {
-			// Read user-supplied message
-			if (!StringUtils.isEmpty(messageSource)) {
-				File file = new File(messageSource);
-				if (file.exists()) {
-					try {
-						FileInputStream fis = new FileInputStream(file);
-						InputStreamReader reader = new InputStreamReader(fis,
-								Constants.CHARACTER_ENCODING);
-						message = MarkdownUtils.transformMarkdown(reader);
-						reader.close();
-					} catch (Throwable t) {
-						message = getString("gb.failedToRead") + " " + file;
-						warn(message, t);
-					}
-				} else {
-					message = messageSource + " " + getString("gb.isNotValidFile");
-				}
-			}
-		}
-		return message;
-	}
-
-	private String readDefaultMarkdown(String file) {
-		String base = file.substring(0, file.lastIndexOf('.'));
-		String ext = file.substring(file.lastIndexOf('.'));
-		String lc = getLanguageCode();
-		String cc = getCountryCode();
-
-		// try to read file_en-us.ext, file_en.ext, file.ext
-		List<String> files = new ArrayList<String>();
-		if (!StringUtils.isEmpty(lc)) {
-			if (!StringUtils.isEmpty(cc)) {
-				files.add(base + "_" + lc + "-" + cc + ext);
-				files.add(base + "_" + lc + "_" + cc + ext);
-			}
-			files.add(base + "_" + lc + ext);
-		}
-		files.add(file);
-		
-		for (String name : files) {
-			String message;
-			InputStreamReader reader = null;
-			try {
-				ContextRelativeResource res = WicketUtils.getResource(name);
-				InputStream is = res.getResourceStream().getInputStream();
-				reader = new InputStreamReader(is, Constants.CHARACTER_ENCODING);
-				message = MarkdownUtils.transformMarkdown(reader);
-				reader.close();
-				return message;
-			} catch (ResourceStreamNotFoundException t) {
-				continue;
-			} catch (Throwable t) {
-				message = MessageFormat.format(getString("gb.failedToReadMessage"), file);
-				error(message, t, false);
-				return message;
-			} finally {
-				if (reader != null) {
-					try {
-						reader.close();
-					} catch (Exception e) {
-					}
-				}
-			}			
-		}
-		return MessageFormat.format(getString("gb.failedToReadMessage"), file);
-	}
-}
diff --git a/src/com/gitblit/wicket/pages/RawPage.java b/src/com/gitblit/wicket/pages/RawPage.java
deleted file mode 100644
index 28e8bae..0000000
--- a/src/com/gitblit/wicket/pages/RawPage.java
+++ /dev/null
@@ -1,161 +0,0 @@
-/*
- * 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.wicket.pages;
-
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.Map;
-
-import org.apache.wicket.IRequestTarget;
-import org.apache.wicket.PageParameters;
-import org.apache.wicket.RequestCycle;
-import org.apache.wicket.markup.html.WebPage;
-import org.apache.wicket.protocol.http.WebResponse;
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.revwalk.RevCommit;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.gitblit.GitBlit;
-import com.gitblit.Keys;
-import com.gitblit.utils.JGitUtils;
-import com.gitblit.utils.StringUtils;
-import com.gitblit.wicket.WicketUtils;
-
-public class RawPage extends WebPage {
-
-	private final Logger logger = LoggerFactory.getLogger(getClass().getSimpleName());
-
-	public RawPage(final PageParameters params) {
-		super(params);
-
-		if (!params.containsKey("r")) {
-			error(getString("gb.repositoryNotSpecified"));
-			redirectToInterceptPage(new RepositoriesPage());
-		}
-
-		getRequestCycle().setRequestTarget(new IRequestTarget() {
-			@Override
-			public void detach(RequestCycle requestCycle) {
-			}
-
-			@Override
-			public void respond(RequestCycle requestCycle) {
-				WebResponse response = (WebResponse) requestCycle.getResponse();
-
-				final String repositoryName = WicketUtils.getRepositoryName(params);
-				final String objectId = WicketUtils.getObject(params);
-				final String blobPath = WicketUtils.getPath(params);
-				String[] encodings = GitBlit.getEncodings();
-
-				Repository r = GitBlit.self().getRepository(repositoryName);
-				if (r == null) {
-					error(getString("gb.canNotLoadRepository") + " " + repositoryName);
-					redirectToInterceptPage(new RepositoriesPage());
-					return;
-				}
-
-				if (StringUtils.isEmpty(blobPath)) {
-					// objectid referenced raw view
-					byte [] binary = JGitUtils.getByteContent(r, objectId);
-					response.setContentType("application/octet-stream");
-					response.setContentLength(binary.length);
-					try {
-						response.getOutputStream().write(binary);
-					} catch (Exception e) {
-						logger.error("Failed to write binary response", e);
-					}
-				} else {
-					// standard raw blob view
-					RevCommit commit = JGitUtils.getCommit(r, objectId);
-
-					String filename = blobPath;
-					if (blobPath.indexOf('/') > -1) {
-						filename = blobPath.substring(blobPath.lastIndexOf('/') + 1);
-					}
-
-					String extension = null;
-					if (blobPath.lastIndexOf('.') > -1) {
-						extension = blobPath.substring(blobPath.lastIndexOf('.') + 1);
-					}
-
-					// Map the extensions to types
-					Map<String, Integer> map = new HashMap<String, Integer>();
-					for (String ext : GitBlit.getStrings(Keys.web.imageExtensions)) {
-						map.put(ext.toLowerCase(), 2);
-					}
-					for (String ext : GitBlit.getStrings(Keys.web.binaryExtensions)) {
-						map.put(ext.toLowerCase(), 3);
-					}
-
-					if (extension != null) {
-						int type = 0;
-						if (map.containsKey(extension)) {
-							type = map.get(extension);
-						}
-						switch (type) {
-						case 2:
-							// image blobs
-							byte[] image = JGitUtils.getByteContent(r, commit.getTree(), blobPath, true);
-							response.setContentType("image/" + extension.toLowerCase());
-							response.setContentLength(image.length);
-							try {
-								response.getOutputStream().write(image);
-							} catch (IOException e) {
-								logger.error("Failed to write image response", e);
-							}
-							break;
-						case 3:
-							// binary blobs (download)
-							byte[] binary = JGitUtils.getByteContent(r, commit.getTree(), blobPath, true);
-							response.setContentLength(binary.length);
-							response.setContentType("application/octet-stream");
-							response.setHeader("Content-Disposition", "attachment; filename=\"" + filename + "\"");
-							try {
-								response.getOutputStream().write(binary);
-							} catch (IOException e) {
-								logger.error("Failed to write binary response", e);
-							}
-							break;
-						default:
-							// plain text
-							String content = JGitUtils.getStringContent(r, commit.getTree(),
-									blobPath, encodings);
-							response.setContentType("text/plain; charset=UTF-8");
-							try {
-								response.getOutputStream().write(content.getBytes("UTF-8"));
-							} catch (Exception e) {
-								logger.error("Failed to write text response", e);
-							}
-						}
-
-					} else {
-						// plain text
-						String content = JGitUtils.getStringContent(r, commit.getTree(), blobPath,
-								encodings);
-						response.setContentType("text/plain; charset=UTF-8");
-						try {
-							response.getOutputStream().write(content.getBytes("UTF-8"));
-						} catch (Exception e) {
-							logger.error("Failed to write text response", e);
-						}
-					}
-				}
-				r.close();
-			}
-		});
-	}
-}
diff --git a/src/com/gitblit/wicket/pages/RepositoriesPage.html b/src/com/gitblit/wicket/pages/RepositoriesPage.html
deleted file mode 100644
index d2d2715..0000000
--- a/src/com/gitblit/wicket/pages/RepositoriesPage.html
+++ /dev/null
@@ -1,14 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml"  
-      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"  
-      xml:lang="en"  
-      lang="en"> 
-
-<body>
-<wicket:extend>
-	<div class="markdown" style="padding-bottom:5px;" wicket:id="repositoriesMessage">[repositories message]</div>
-	
-	<div wicket:id="repositoriesPanel">[repositories panel]</div>
-</wicket:extend>
-</body>
-</html>
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/pages/RepositoriesPage.java b/src/com/gitblit/wicket/pages/RepositoriesPage.java
deleted file mode 100644
index 4bce77f..0000000
--- a/src/com/gitblit/wicket/pages/RepositoriesPage.java
+++ /dev/null
@@ -1,186 +0,0 @@
-/*
- * 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.wicket.pages;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.text.MessageFormat;
-import java.util.ArrayList;
-import java.util.List;
-
-import org.apache.wicket.Component;
-import org.apache.wicket.PageParameters;
-import org.apache.wicket.markup.html.basic.Label;
-import org.apache.wicket.resource.ContextRelativeResource;
-import org.apache.wicket.util.resource.ResourceStreamNotFoundException;
-import org.eclipse.jgit.lib.Constants;
-
-import com.gitblit.GitBlit;
-import com.gitblit.Keys;
-import com.gitblit.models.RepositoryModel;
-import com.gitblit.utils.MarkdownUtils;
-import com.gitblit.utils.StringUtils;
-import com.gitblit.wicket.GitBlitWebSession;
-import com.gitblit.wicket.PageRegistration;
-import com.gitblit.wicket.PageRegistration.DropDownMenuItem;
-import com.gitblit.wicket.PageRegistration.DropDownMenuRegistration;
-import com.gitblit.wicket.WicketUtils;
-import com.gitblit.wicket.panels.RepositoriesPanel;
-
-public class RepositoriesPage extends RootPage {
-
-	public RepositoriesPage() {
-		super();
-		setup(null);
-	}
-
-	public RepositoriesPage(PageParameters params) {
-		super(params);
-		setup(params);
-	}
-
-	@Override
-	protected boolean reusePageParameters() {
-		return true;
-	}
-
-	private void setup(PageParameters params) {
-		setupPage("", "");
-		// check to see if we should display a login message
-		boolean authenticateView = GitBlit.getBoolean(Keys.web.authenticateViewPages, true);
-		if (authenticateView && !GitBlitWebSession.get().isLoggedIn()) {
-			String messageSource = GitBlit.getString(Keys.web.loginMessage, "gitblit");
-			String message = readMarkdown(messageSource, "login.mkd");
-			Component repositoriesMessage = new Label("repositoriesMessage", message);
-			add(repositoriesMessage.setEscapeModelStrings(false));
-			add(new Label("repositoriesPanel"));
-			return;
-		}
-
-		// Load the markdown welcome message
-		String messageSource = GitBlit.getString(Keys.web.repositoriesMessage, "gitblit");
-		String message = readMarkdown(messageSource, "welcome.mkd");
-		Component repositoriesMessage = new Label("repositoriesMessage", message)
-				.setEscapeModelStrings(false).setVisible(message.length() > 0);
-		add(repositoriesMessage);
-
-		List<RepositoryModel> repositories = getRepositories(params);
-
-		RepositoriesPanel repositoriesPanel = new RepositoriesPanel("repositoriesPanel", showAdmin,
-				true, repositories, true, getAccessRestrictions());
-		// push the panel down if we are hiding the admin controls and the
-		// welcome message
-		if (!showAdmin && !repositoriesMessage.isVisible()) {
-			WicketUtils.setCssStyle(repositoriesPanel, "padding-top:5px;");
-		}
-		add(repositoriesPanel);
-	}
-
-	@Override
-	protected void addDropDownMenus(List<PageRegistration> pages) {
-		PageParameters params = getPageParameters();
-
-		DropDownMenuRegistration menu = new DropDownMenuRegistration("gb.filters",
-				RepositoriesPage.class);
-		// preserve time filter option on repository choices
-		menu.menuItems.addAll(getRepositoryFilterItems(params));
-
-		// preserve repository filter option on time choices
-		menu.menuItems.addAll(getTimeFilterItems(params));
-
-		if (menu.menuItems.size() > 0) {
-			// Reset Filter
-			menu.menuItems.add(new DropDownMenuItem(getString("gb.reset"), null, null));
-		}
-
-		pages.add(menu);
-	}
-
-	private String readMarkdown(String messageSource, String resource) {
-		String message = "";
-		if (messageSource.equalsIgnoreCase("gitblit")) {
-			// Read default message
-			message = readDefaultMarkdown(resource);
-		} else {
-			// Read user-supplied message
-			if (!StringUtils.isEmpty(messageSource)) {
-				File file = GitBlit.getFileOrFolder(messageSource);
-				if (file.exists()) {
-					try {
-						FileInputStream fis = new FileInputStream(file);
-						InputStreamReader reader = new InputStreamReader(fis,
-								Constants.CHARACTER_ENCODING);
-						message = MarkdownUtils.transformMarkdown(reader);
-						reader.close();
-					} catch (Throwable t) {
-						message = getString("gb.failedToRead") + " " + file;
-						warn(message, t);
-					}
-				} else {
-					message = messageSource + " " + getString("gb.isNotValidFile");
-				}
-			}
-		}
-		return message;
-	}
-
-	private String readDefaultMarkdown(String file) {
-		String base = file.substring(0, file.lastIndexOf('.'));
-		String ext = file.substring(file.lastIndexOf('.'));
-		String lc = getLanguageCode();
-		String cc = getCountryCode();
-
-		// try to read file_en-us.ext, file_en.ext, file.ext
-		List<String> files = new ArrayList<String>();
-		if (!StringUtils.isEmpty(lc)) {
-			if (!StringUtils.isEmpty(cc)) {
-				files.add(base + "_" + lc + "-" + cc + ext);
-				files.add(base + "_" + lc + "_" + cc + ext);
-			}
-			files.add(base + "_" + lc + ext);
-		}
-		files.add(file);
-
-		for (String name : files) {
-			String message;
-			InputStreamReader reader = null;
-			try {
-				ContextRelativeResource res = WicketUtils.getResource(name);
-				InputStream is = res.getResourceStream().getInputStream();
-				reader = new InputStreamReader(is, Constants.CHARACTER_ENCODING);
-				message = MarkdownUtils.transformMarkdown(reader);
-				reader.close();
-				return message;
-			} catch (ResourceStreamNotFoundException t) {
-				continue;
-			} catch (Throwable t) {
-				message = MessageFormat.format(getString("gb.failedToReadMessage"), file);
-				error(message, t, false);
-				return message;
-			} finally {
-				if (reader != null) {
-					try {
-						reader.close();
-					} catch (Exception e) {
-					}
-				}
-			}			
-		}
-		return MessageFormat.format(getString("gb.failedToReadMessage"), file);
-	}
-}
diff --git a/src/com/gitblit/wicket/pages/RepositoryPage.html b/src/com/gitblit/wicket/pages/RepositoryPage.html
deleted file mode 100644
index d49f018..0000000
--- a/src/com/gitblit/wicket/pages/RepositoryPage.html
+++ /dev/null
@@ -1,82 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml"  
-      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"  
-      xml:lang="en"  
-      lang="en"> 
-
-<body>
-	<wicket:extend>
-		<!-- page nav links -->
-		<div class="navbar navbar-fixed-top">
-			<div class="navbar-inner">
-				<div class="container">
-					<a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
-            			<span class="icon-bar"></span>
-            			<span class="icon-bar"></span>
-            			<span class="icon-bar"></span>
-          			</a>
-					<a class="brand" wicket:id="rootLink">
-						<img src="gitblt_25_white.png" width="79" height="25" alt="gitblit" class="logo"/>
-					</a>
-				
-					<div class="nav-collapse" wicket:id="navPanel"></div>
-				
-					<a class="hidden-phone hidden-tablet brand" style="text-decoration: none;" wicket:id="syndication" wicket:message="title:gb.feed">
-						<img style="border:0px;vertical-align:middle;" src="feed_16x16.png"></img>
-					</a>
-				
-					<form class="hidden-phone hidden-tablet pull-right" style="margin-top:10px;" wicket:id="searchForm">
-						<span class="search">
-							<select class="small" wicket:id="searchType"/>			
-							<input type="text" id="searchBox" wicket:id="searchBox" value=""/>
-						</span>
-					</form>
-				</div>
-			</div>
-		</div>
-				
-		<!-- page content -->
-		<div class="container">
-			<div style="text-align:center;" wicket:id="feedback">[Feedback Panel]</div>
-
-			<!-- page header -->
-			<div class="pageTitle">
-				<div class="row">
-					<div class="controls">
-						<span wicket:id="workingCopyIndicator"></span>
-						<span class="hidden-phone hidden-tablet" wicket:id="forksProhibitedIndicator"></span>
-						<div class="hidden-phone btn-group pull-right">
-							<!-- future spot for other repo buttons -->
-							<a class="btn btn-info" wicket:id="myForkLink"><img style="border:0px;vertical-align:middle;" src="fork_16x16.png"></img> <wicket:message key="gb.myFork"></wicket:message></a>
-							<a class="btn btn-info" wicket:id="forkLink"><img style="border:0px;vertical-align:middle;" src="fork_16x16.png"></img> <wicket:message key="gb.fork"></wicket:message></a>
-						</div>
-					</div>
-					<div class="span7">
-						<div><span class="project" wicket:id="projectTitle">[project title]</span>/<img wicket:id="repositoryIcon" style="padding-left: 10px;"></img><span class="repository" wicket:id="repositoryName">[repository name]</span> <span class="hidden-phone"><span wicket:id="pageName">[page name]</span></span></div>
-						<span wicket:id="originRepository">[origin repository]</span>
-					</div>
-				</div>
-			</div>
-
-			<wicket:child />
-		</div>
-		
-		<wicket:fragment wicket:id="originFragment">
-			<p class="originRepository"><wicket:message key="gb.forkedFrom">[forked from]</wicket:message> <span wicket:id="originRepository">[origin repository]</span></p>
-		</wicket:fragment>
-		
-		<wicket:fragment wicket:id="workingCopyFragment">
-			<div class="pull-right" style="padding-top:0px;margin-bottom:0px;padding-left:5px">
-				<span class="alert alert-info" style="padding: 6px 14px 6px 14px;vertical-align: middle;"><i class="icon-exclamation-sign"></i>&nbsp;<span class="hidden-phone" wicket:id="workingCopy" style="font-weight:bold;">[working copy]</span></span>
-			</div>
-		</wicket:fragment>
-
-		<wicket:fragment wicket:id="forksProhibitedFragment">
-			<div class="pull-right" style="padding-top:0px;margin-bottom:0px;padding-left:5px">
-				<span class="alert alert-error" style="padding: 6px 14px 6px 14px;vertical-align: middle;"><i class="icon-ban-circle"></i>&nbsp;<span class="hidden-phone" wicket:id="forksProhibited" style="font-weight:bold;">[forks prohibited]</span></span>
-			</div>
-		</wicket:fragment>
-		
-	</wicket:extend>
-</body>
-</html>
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/pages/RepositoryPage.java b/src/com/gitblit/wicket/pages/RepositoryPage.java
deleted file mode 100644
index a477b74..0000000
--- a/src/com/gitblit/wicket/pages/RepositoryPage.java
+++ /dev/null
@@ -1,608 +0,0 @@
-/*
- * 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.wicket.pages;
-
-import java.io.Serializable;
-import java.text.MessageFormat;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.LinkedHashMap;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import org.apache.wicket.Component;
-import org.apache.wicket.PageParameters;
-import org.apache.wicket.markup.html.basic.Label;
-import org.apache.wicket.markup.html.form.DropDownChoice;
-import org.apache.wicket.markup.html.form.TextField;
-import org.apache.wicket.markup.html.link.ExternalLink;
-import org.apache.wicket.markup.html.panel.Fragment;
-import org.apache.wicket.model.IModel;
-import org.apache.wicket.model.Model;
-import org.apache.wicket.protocol.http.RequestUtils;
-import org.apache.wicket.request.target.basic.RedirectRequestTarget;
-import org.eclipse.jgit.diff.DiffEntry.ChangeType;
-import org.eclipse.jgit.lib.PersonIdent;
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.revwalk.RevCommit;
-
-import com.gitblit.Constants;
-import com.gitblit.GitBlit;
-import com.gitblit.Keys;
-import com.gitblit.PagesServlet;
-import com.gitblit.SyndicationServlet;
-import com.gitblit.models.ProjectModel;
-import com.gitblit.models.RefModel;
-import com.gitblit.models.RepositoryModel;
-import com.gitblit.models.SubmoduleModel;
-import com.gitblit.models.UserModel;
-import com.gitblit.utils.ArrayUtils;
-import com.gitblit.utils.JGitUtils;
-import com.gitblit.utils.StringUtils;
-import com.gitblit.utils.TicgitUtils;
-import com.gitblit.wicket.GitBlitWebSession;
-import com.gitblit.wicket.PageRegistration;
-import com.gitblit.wicket.PageRegistration.OtherPageLink;
-import com.gitblit.wicket.SessionlessForm;
-import com.gitblit.wicket.WicketUtils;
-import com.gitblit.wicket.panels.LinkPanel;
-import com.gitblit.wicket.panels.NavigationPanel;
-import com.gitblit.wicket.panels.RefsPanel;
-
-public abstract class RepositoryPage extends BasePage {
-
-	protected final String projectName;
-	protected final String repositoryName;
-	protected final String objectId;
-	
-	private transient Repository r;
-
-	private RepositoryModel m;
-
-	private Map<String, SubmoduleModel> submodules;
-	
-	private final Map<String, PageRegistration> registeredPages;
-	private boolean showAdmin;
-	private boolean isOwner;
-	
-	public RepositoryPage(PageParameters params) {
-		super(params);
-		repositoryName = WicketUtils.getRepositoryName(params);
-		String root =StringUtils.getFirstPathElement(repositoryName);
-		if (StringUtils.isEmpty(root)) {
-			projectName = GitBlit.getString(Keys.web.repositoryRootGroupName, "main");
-		} else {
-			projectName = root;
-		}
-		objectId = WicketUtils.getObject(params);
-		
-		if (StringUtils.isEmpty(repositoryName)) {
-			error(MessageFormat.format(getString("gb.repositoryNotSpecifiedFor"), getPageName()), true);
-		}
-
-		if (!getRepositoryModel().hasCommits) {
-			setResponsePage(EmptyRepositoryPage.class, params);
-		}
-		
-		if (getRepositoryModel().isCollectingGarbage) {
-			error(MessageFormat.format(getString("gb.busyCollectingGarbage"), getRepositoryModel().name), true);
-		}
-
-		if (objectId != null) {
-			RefModel branch = null;
-			if ((branch = JGitUtils.getBranch(getRepository(), objectId)) != null) {
-				UserModel user = GitBlitWebSession.get().getUser();
-				if (user == null) {
-					// workaround until get().getUser() is reviewed throughout the app
-					user = UserModel.ANONYMOUS;
-				}
-				boolean canAccess = user.canView(getRepositoryModel(),
-								branch.reference.getName());
-				if (!canAccess) {
-					error(getString("gb.accessDenied"), true);
-				}
-			}
-		}
-
-		// register the available page links for this page and user
-		registeredPages = registerPages();
-
-		// standard page links
-		List<PageRegistration> pages = new ArrayList<PageRegistration>(registeredPages.values());
-		NavigationPanel navigationPanel = new NavigationPanel("navPanel", getClass(), pages);
-		add(navigationPanel);
-
-		add(new ExternalLink("syndication", SyndicationServlet.asLink(getRequest()
-				.getRelativePathPrefixToContextRoot(), repositoryName, null, 0)));
-
-		// add floating search form
-		SearchForm searchForm = new SearchForm("searchForm", repositoryName);
-		add(searchForm);
-		searchForm.setTranslatedAttributes();
-
-		// set stateless page preference
-		setStatelessHint(true);
-	}
-
-	private Map<String, PageRegistration> registerPages() {
-		PageParameters params = null;
-		if (!StringUtils.isEmpty(repositoryName)) {
-			params = WicketUtils.newRepositoryParameter(repositoryName);
-		}
-		Map<String, PageRegistration> pages = new LinkedHashMap<String, PageRegistration>();
-
-		// standard links
-		pages.put("repositories", new PageRegistration("gb.repositories", RepositoriesPage.class));
-		pages.put("summary", new PageRegistration("gb.summary", SummaryPage.class, params));
-		pages.put("log", new PageRegistration("gb.log", LogPage.class, params));
-		pages.put("branches", new PageRegistration("gb.branches", BranchesPage.class, params));
-		pages.put("tags", new PageRegistration("gb.tags", TagsPage.class, params));
-		pages.put("tree", new PageRegistration("gb.tree", TreePage.class, params));
-		if (GitBlit.getBoolean(Keys.web.allowForking, true)) {
-			pages.put("forks", new PageRegistration("gb.forks", ForksPage.class, params));
-		}
-
-		// conditional links
-		Repository r = getRepository();
-		RepositoryModel model = getRepositoryModel();
-
-		// per-repository extra page links
-		if (model.useTickets && TicgitUtils.getTicketsBranch(r) != null) {
-			pages.put("tickets", new PageRegistration("gb.tickets", TicketsPage.class, params));
-		}
-		if (model.useDocs) {
-			pages.put("docs", new PageRegistration("gb.docs", DocsPage.class, params));
-		}
-		if (JGitUtils.getPagesBranch(r) != null) {
-			OtherPageLink pagesLink = new OtherPageLink("gb.pages", PagesServlet.asLink(
-					getRequest().getRelativePathPrefixToContextRoot(), repositoryName, null));
-			pages.put("pages", pagesLink);
-		}
-
-		// Conditionally add edit link
-		showAdmin = false;
-		if (GitBlit.getBoolean(Keys.web.authenticateAdminPages, true)) {
-			boolean allowAdmin = GitBlit.getBoolean(Keys.web.allowAdministration, false);
-			showAdmin = allowAdmin && GitBlitWebSession.get().canAdmin();
-		} else {
-			showAdmin = GitBlit.getBoolean(Keys.web.allowAdministration, false);
-		}
-		isOwner = GitBlitWebSession.get().isLoggedIn()
-				&& (model.isOwner(GitBlitWebSession.get()
-						.getUsername()));
-		if (showAdmin || isOwner) {
-			pages.put("edit", new PageRegistration("gb.edit", EditRepositoryPage.class, params));
-		}
-		return pages;
-	}
-	
-	protected boolean allowForkControls() {
-		return GitBlit.getBoolean(Keys.web.allowForking, true);
-	}
-
-	@Override
-	protected void setupPage(String repositoryName, String pageName) {
-		String projectName = StringUtils.getFirstPathElement(repositoryName);
-		ProjectModel project = GitBlit.self().getProjectModel(projectName);
-		if (project.isUserProject()) {
-			// user-as-project
-			add(new LinkPanel("projectTitle", null, project.getDisplayName(),
-					UserPage.class, WicketUtils.newUsernameParameter(project.name.substring(1))));
-		} else {
-			// project
-			add(new LinkPanel("projectTitle", null, project.name,
-					ProjectPage.class, WicketUtils.newProjectParameter(project.name)));
-		}
-		
-		String name = StringUtils.stripDotGit(repositoryName);
-		if (!StringUtils.isEmpty(projectName) && name.startsWith(projectName)) {
-			name = name.substring(projectName.length() + 1);
-		}
-		add(new LinkPanel("repositoryName", null, name, SummaryPage.class,
-				WicketUtils.newRepositoryParameter(repositoryName)));
-		add(new Label("pageName", pageName).setRenderBodyOnly(true));
-		
-		UserModel user = GitBlitWebSession.get().getUser();
-		if (user == null) {
-			user = UserModel.ANONYMOUS;
-		}
-
-		// indicate origin repository
-		RepositoryModel model = getRepositoryModel();
-		if (StringUtils.isEmpty(model.originRepository)) {
-			add(new Label("originRepository").setVisible(false));
-		} else {
-			RepositoryModel origin = GitBlit.self().getRepositoryModel(model.originRepository);
-			if (origin == null) {
-				// no origin repository
-				add(new Label("originRepository").setVisible(false));
-			} else if (!user.canView(origin)) {
-				// show origin repository without link
-				Fragment forkFrag = new Fragment("originRepository", "originFragment", this);
-				forkFrag.add(new Label("originRepository", StringUtils.stripDotGit(model.originRepository)));
-				add(forkFrag);
-			} else {
-				// link to origin repository
-				Fragment forkFrag = new Fragment("originRepository", "originFragment", this);
-				forkFrag.add(new LinkPanel("originRepository", null, StringUtils.stripDotGit(model.originRepository), 
-						SummaryPage.class, WicketUtils.newRepositoryParameter(model.originRepository)));
-				add(forkFrag);
-			}
-		}
-		
-		// show sparkleshare folder icon
-		if (model.isSparkleshared()) {
-			add(WicketUtils.newImage("repositoryIcon", "folder_star_32x32.png",
-					getString("gb.isSparkleshared")));
-		} else {
-			add(WicketUtils.newClearPixel("repositoryIcon").setVisible(false));
-		}
-		
-		if (getRepositoryModel().isBare) {
-			add(new Label("workingCopyIndicator").setVisible(false));
-		} else {
-			Fragment wc = new Fragment("workingCopyIndicator", "workingCopyFragment", this);
-			Label lbl = new Label("workingCopy", getString("gb.workingCopy"));
-			WicketUtils.setHtmlTooltip(lbl,  getString("gb.workingCopyWarning"));
-			wc.add(lbl);
-			add(wc);
-		}
-
-		// fork controls
-		if (!allowForkControls() || user == null || !user.isAuthenticated) {
-			// must be logged-in to fork, hide all fork controls
-			add(new ExternalLink("forkLink", "").setVisible(false));
-			add(new ExternalLink("myForkLink", "").setVisible(false));
-			add(new Label("forksProhibitedIndicator").setVisible(false));
-		} else {
-			String fork = GitBlit.self().getFork(user.username, model.name);
-			boolean hasFork = fork != null;
-			boolean canFork = user.canFork(model);
-
-			if (hasFork || !canFork) {
-				// user not allowed to fork or fork already exists or repo forbids forking
-				add(new ExternalLink("forkLink", "").setVisible(false));
-				
-				if (user.canFork() && !model.allowForks) {
-					// show forks prohibited indicator
-					Fragment wc = new Fragment("forksProhibitedIndicator", "forksProhibitedFragment", this);
-					Label lbl = new Label("forksProhibited", getString("gb.forksProhibited"));
-					WicketUtils.setHtmlTooltip(lbl,  getString("gb.forksProhibitedWarning"));
-					wc.add(lbl);
-					add(wc);
-				} else {
-					// can not fork, no need for forks prohibited indicator
-					add(new Label("forksProhibitedIndicator").setVisible(false));
-				}
-				
-				if (hasFork && !fork.equals(model.name)) {
-					// user has fork, view my fork link
-					String url = getRequestCycle().urlFor(SummaryPage.class, WicketUtils.newRepositoryParameter(fork)).toString();
-					add(new ExternalLink("myForkLink", url));
-				} else {
-					// no fork, hide view my fork link
-					add(new ExternalLink("myForkLink", "").setVisible(false));
-				}
-			} else if (canFork) {
-				// can fork and we do not have one
-				add(new Label("forksProhibitedIndicator").setVisible(false));
-				add(new ExternalLink("myForkLink", "").setVisible(false));
-				String url = getRequestCycle().urlFor(ForkPage.class, WicketUtils.newRepositoryParameter(model.name)).toString();
-				add(new ExternalLink("forkLink", url));
-			}
-		}
-		
-		super.setupPage(repositoryName, pageName);
-	}
-
-	protected void addSyndicationDiscoveryLink() {
-		add(WicketUtils.syndicationDiscoveryLink(SyndicationServlet.getTitle(repositoryName,
-				objectId), SyndicationServlet.asLink(getRequest()
-				.getRelativePathPrefixToContextRoot(), repositoryName, objectId, 0)));
-	}
-
-	protected Repository getRepository() {
-		if (r == null) {
-			Repository r = GitBlit.self().getRepository(repositoryName);
-			if (r == null) {
-				error(getString("gb.canNotLoadRepository") + " " + repositoryName, true);
-				return null;
-			}
-			this.r = r;
-		}
-		return r;
-	}
-
-	protected RepositoryModel getRepositoryModel() {
-		if (m == null) {
-			RepositoryModel model = GitBlit.self().getRepositoryModel(
-					GitBlitWebSession.get().getUser(), repositoryName);
-			if (model == null) {
-				if (GitBlit.self().hasRepository(repositoryName, true)) {
-					// has repository, but unauthorized
-					authenticationError(getString("gb.unauthorizedAccessForRepository") + " " + repositoryName);
-				} else {
-					// does not have repository
-					error(getString("gb.canNotLoadRepository") + " " + repositoryName, true);
-				}
-				return null;
-			}
-			m = model;
-		}
-		return m;
-	}
-
-	protected RevCommit getCommit() {
-		RevCommit commit = JGitUtils.getCommit(r, objectId);
-		if (commit == null) {
-			error(MessageFormat.format(getString("gb.failedToFindCommit"),
-					objectId, repositoryName, getPageName()), true);
-		}
-		getSubmodules(commit);
-		return commit;
-	}
-	
-	private Map<String, SubmoduleModel> getSubmodules(RevCommit commit) {	
-		if (submodules == null) {
-			submodules = new HashMap<String, SubmoduleModel>();
-			for (SubmoduleModel model : JGitUtils.getSubmodules(r, commit.getTree())) {
-				submodules.put(model.path, model);
-			}
-		}
-		return submodules;
-	}
-	
-	protected SubmoduleModel getSubmodule(String path) {
-		SubmoduleModel model = submodules.get(path);
-		if (model == null) {
-			// undefined submodule?!
-			model = new SubmoduleModel(path.substring(path.lastIndexOf('/') + 1), path, path);
-			model.hasSubmodule = false;
-			model.gitblitPath = model.name;
-			return model;
-		} else {
-			// extract the repository name from the clone url
-			List<String> patterns = GitBlit.getStrings(Keys.git.submoduleUrlPatterns);
-			String submoduleName = StringUtils.extractRepositoryPath(model.url, patterns.toArray(new String[0]));
-			
-			// determine the current path for constructing paths relative
-			// to the current repository
-			String currentPath = "";
-			if (repositoryName.indexOf('/') > -1) {
-				currentPath = repositoryName.substring(0, repositoryName.lastIndexOf('/') + 1);
-			}
-
-			// try to locate the submodule repository
-			// prefer bare to non-bare names
-			List<String> candidates = new ArrayList<String>();
-
-			// relative
-			candidates.add(currentPath + StringUtils.stripDotGit(submoduleName));
-			candidates.add(candidates.get(candidates.size() - 1) + ".git");
-
-			// relative, no subfolder
-			if (submoduleName.lastIndexOf('/') > -1) {
-				String name = submoduleName.substring(submoduleName.lastIndexOf('/') + 1);
-				candidates.add(currentPath + StringUtils.stripDotGit(name));
-				candidates.add(currentPath + candidates.get(candidates.size() - 1) + ".git");
-			}
-
-			// absolute
-			candidates.add(StringUtils.stripDotGit(submoduleName));
-			candidates.add(candidates.get(candidates.size() - 1) + ".git");
-
-			// absolute, no subfolder
-			if (submoduleName.lastIndexOf('/') > -1) {
-				String name = submoduleName.substring(submoduleName.lastIndexOf('/') + 1);
-				candidates.add(StringUtils.stripDotGit(name));
-				candidates.add(candidates.get(candidates.size() - 1) + ".git");
-			}
-
-			// create a unique, ordered set of candidate paths
-			Set<String> paths = new LinkedHashSet<String>(candidates);
-			for (String candidate : paths) {
-				if (GitBlit.self().hasRepository(candidate)) {
-					model.hasSubmodule = true;
-					model.gitblitPath = candidate;
-					return model;
-				}
-			}
-			
-			// we do not have a copy of the submodule, but we need a path
-			model.gitblitPath = candidates.get(0);
-			return model;
-		}		
-	}
-
-	protected String getShortObjectId(String objectId) {
-		return objectId.substring(0, GitBlit.getInteger(Keys.web.shortCommitIdLength, 6));
-	}
-
-	protected void addRefs(Repository r, RevCommit c) {
-		add(new RefsPanel("refsPanel", repositoryName, c, JGitUtils.getAllRefs(r, getRepositoryModel().showRemoteBranches)));
-	}
-
-	protected void addFullText(String wicketId, String text, boolean substituteRegex) {
-		String html = StringUtils.escapeForHtml(text, true);
-		if (substituteRegex) {
-			html = GitBlit.self().processCommitMessage(repositoryName, text);
-		} else {
-			html = StringUtils.breakLinesForHtml(html);
-		}
-		add(new Label(wicketId, html).setEscapeModelStrings(false));
-	}
-
-	protected abstract String getPageName();
-
-	protected Component createPersonPanel(String wicketId, PersonIdent identity,
-			Constants.SearchType searchType) {
-		String name = identity == null ? "" : identity.getName();
-		String address = identity == null ? "" : identity.getEmailAddress();
-		name = StringUtils.removeNewlines(name);
-		address = StringUtils.removeNewlines(address);
-		boolean showEmail = GitBlit.getBoolean(Keys.web.showEmailAddresses, false);
-		if (!showEmail || StringUtils.isEmpty(name) || StringUtils.isEmpty(address)) {
-			String value = name;
-			if (StringUtils.isEmpty(value)) {
-				if (showEmail) {
-					value = address;
-				} else {
-					value = getString("gb.missingUsername");
-				}
-			}
-			Fragment partial = new Fragment(wicketId, "partialPersonIdent", this);
-			LinkPanel link = new LinkPanel("personName", "list", value, GitSearchPage.class,
-					WicketUtils.newSearchParameter(repositoryName, objectId, value, searchType));
-			setPersonSearchTooltip(link, value, searchType);
-			partial.add(link);
-			return partial;
-		} else {
-			Fragment fullPerson = new Fragment(wicketId, "fullPersonIdent", this);
-			LinkPanel nameLink = new LinkPanel("personName", "list", name, GitSearchPage.class,
-					WicketUtils.newSearchParameter(repositoryName, objectId, name, searchType));
-			setPersonSearchTooltip(nameLink, name, searchType);
-			fullPerson.add(nameLink);
-
-			LinkPanel addressLink = new LinkPanel("personAddress", "hidden-phone list", "<" + address + ">",
-					GitSearchPage.class, WicketUtils.newSearchParameter(repositoryName, objectId,
-							address, searchType));
-			setPersonSearchTooltip(addressLink, address, searchType);
-			fullPerson.add(addressLink);
-			return fullPerson;
-		}
-	}
-
-	protected void setPersonSearchTooltip(Component component, String value,
-			Constants.SearchType searchType) {
-		if (searchType.equals(Constants.SearchType.AUTHOR)) {
-			WicketUtils.setHtmlTooltip(component, getString("gb.searchForAuthor") + " " + value);
-		} else if (searchType.equals(Constants.SearchType.COMMITTER)) {
-			WicketUtils.setHtmlTooltip(component, getString("gb.searchForCommitter") + " " + value);
-		}
-	}
-
-	protected void setChangeTypeTooltip(Component container, ChangeType type) {
-		switch (type) {
-		case ADD:
-			WicketUtils.setHtmlTooltip(container, getString("gb.addition"));
-			break;
-		case COPY:
-		case RENAME:
-			WicketUtils.setHtmlTooltip(container, getString("gb.rename"));
-			break;
-		case DELETE:
-			WicketUtils.setHtmlTooltip(container, getString("gb.deletion"));
-			break;
-		case MODIFY:
-			WicketUtils.setHtmlTooltip(container, getString("gb.modification"));
-			break;
-		}
-	}
-
-	@Override
-	protected void onBeforeRender() {
-		// dispose of repository object
-		if (r != null) {
-			r.close();
-			r = null;
-		}
-		// setup page header and footer
-		setupPage(repositoryName, "/ " + getPageName());
-		super.onBeforeRender();
-	}
-
-	protected PageParameters newRepositoryParameter() {
-		return WicketUtils.newRepositoryParameter(repositoryName);
-	}
-
-	protected PageParameters newCommitParameter() {
-		return WicketUtils.newObjectParameter(repositoryName, objectId);
-	}
-
-	protected PageParameters newCommitParameter(String commitId) {
-		return WicketUtils.newObjectParameter(repositoryName, commitId);
-	}
-
-	public boolean isShowAdmin() {
-		return showAdmin;
-	}
-	
-	public boolean isOwner() {
-		return isOwner;
-	}
-	
-	private class SearchForm extends SessionlessForm<Void> implements Serializable {
-		private static final long serialVersionUID = 1L;
-
-		private final String repositoryName;
-
-		private final IModel<String> searchBoxModel = new Model<String>("");
-
-		private final IModel<Constants.SearchType> searchTypeModel = new Model<Constants.SearchType>(
-				Constants.SearchType.COMMIT);
-
-		public SearchForm(String id, String repositoryName) {
-			super(id, RepositoryPage.this.getClass(), RepositoryPage.this.getPageParameters());
-			this.repositoryName = repositoryName;
-			DropDownChoice<Constants.SearchType> searchType = new DropDownChoice<Constants.SearchType>(
-					"searchType", Arrays.asList(Constants.SearchType.values()));
-			searchType.setModel(searchTypeModel);
-			add(searchType.setVisible(GitBlit.getBoolean(Keys.web.showSearchTypeSelection, false)));
-			TextField<String> searchBox = new TextField<String>("searchBox", searchBoxModel);
-			add(searchBox);
-		}
-
-		void setTranslatedAttributes() {
-			WicketUtils.setHtmlTooltip(get("searchType"), getString("gb.searchTypeTooltip"));
-			WicketUtils.setHtmlTooltip(get("searchBox"),
-					MessageFormat.format(getString("gb.searchTooltip"), repositoryName));
-			WicketUtils.setInputPlaceholder(get("searchBox"), getString("gb.search"));
-		}
-
-		@Override
-		public void onSubmit() {
-			Constants.SearchType searchType = searchTypeModel.getObject();
-			String searchString = searchBoxModel.getObject();
-			if (searchString == null) {
-				return;
-			}
-			for (Constants.SearchType type : Constants.SearchType.values()) {
-				if (searchString.toLowerCase().startsWith(type.name().toLowerCase() + ":")) {
-					searchType = type;
-					searchString = searchString.substring(type.name().toLowerCase().length() + 1)
-							.trim();
-					break;
-				}
-			}
-			Class<? extends BasePage> searchPageClass = GitSearchPage.class;
-			RepositoryModel model = GitBlit.self().getRepositoryModel(repositoryName);
-			if (GitBlit.getBoolean(Keys.web.allowLuceneIndexing, true)
-					&& !ArrayUtils.isEmpty(model.indexedBranches)) {
-				// this repository is Lucene-indexed
-				searchPageClass = LuceneSearchPage.class;
-			}
-			// use an absolute url to workaround Wicket-Tomcat problems with
-			// mounted url parameters (issue-111)
-			PageParameters params = WicketUtils.newSearchParameter(repositoryName, null, searchString, searchType);
-			String relativeUrl = urlFor(searchPageClass, params).toString();
-			String absoluteUrl = RequestUtils.toAbsolutePath(relativeUrl);
-			getRequestCycle().setRequestTarget(new RedirectRequestTarget(absoluteUrl));
-		}
-	}
-}
diff --git a/src/com/gitblit/wicket/pages/RootPage.html b/src/com/gitblit/wicket/pages/RootPage.html
deleted file mode 100644
index b35b1b3..0000000
--- a/src/com/gitblit/wicket/pages/RootPage.html
+++ /dev/null
@@ -1,41 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml"  
-      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"  
-      xml:lang="en"  
-      lang="en"> 
-<body>
-<wicket:extend>
-	<div class="navbar navbar-fixed-top">
-		<div class="navbar-inner">
-			<div class="container">
-				<a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
-            			<span class="icon-bar"></span>
-            			<span class="icon-bar"></span>
-            			<span class="icon-bar"></span>
-          		</a>
-				<a class="brand" wicket:id="rootLink">
-					<img src="gitblt_25_white.png" width="79" height="25" alt="gitblit" class="logo"/>
-				</a>
-				
-				<div class="nav-collapse" wicket:id="navPanel"></div>
-				
-				<form class="pull-right" wicket:id="loginForm" style="padding-top:3px;">
-					<span class="form-search">
-						<input wicket:id="username" class="input-small" type="text" />
-						<input wicket:id="password" class="input-small" type="password" />
-						<button class="btn btn-primary" type="submit"><wicket:message key="gb.login"></wicket:message></button>
-					</span>
-				</form>
-			</div>
-		</div>
-	</div>
-				
-	<!-- subclass content -->
-	<div class="container">
-		<div style="text-align:center" wicket:id="feedback">[Feedback Panel]</div>
-		
-		<wicket:child/>
-	</div>
-</wicket:extend>
-</body>
-</html>
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/pages/RootPage.java b/src/com/gitblit/wicket/pages/RootPage.java
deleted file mode 100644
index adcd7b1..0000000
--- a/src/com/gitblit/wicket/pages/RootPage.java
+++ /dev/null
@@ -1,454 +0,0 @@
-/*
- * 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.wicket.pages;
-
-import java.text.MessageFormat;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Calendar;
-import java.util.Collections;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.regex.Pattern;
-
-import org.apache.wicket.PageParameters;
-import org.apache.wicket.markup.html.form.PasswordTextField;
-import org.apache.wicket.markup.html.form.TextField;
-import org.apache.wicket.model.IModel;
-import org.apache.wicket.model.Model;
-import org.apache.wicket.protocol.http.WebResponse;
-
-import com.gitblit.Constants;
-import com.gitblit.GitBlit;
-import com.gitblit.Keys;
-import com.gitblit.models.RepositoryModel;
-import com.gitblit.models.TeamModel;
-import com.gitblit.models.UserModel;
-import com.gitblit.utils.StringUtils;
-import com.gitblit.wicket.GitBlitWebSession;
-import com.gitblit.wicket.PageRegistration;
-import com.gitblit.wicket.PageRegistration.DropDownMenuItem;
-import com.gitblit.wicket.SessionlessForm;
-import com.gitblit.wicket.WicketUtils;
-import com.gitblit.wicket.panels.NavigationPanel;
-
-/**
- * Root page is a topbar, navigable page like Repositories, Users, or
- * Federation.
- * 
- * @author James Moger
- * 
- */
-public abstract class RootPage extends BasePage {
-
-	boolean showAdmin;
-
-	IModel<String> username = new Model<String>("");
-	IModel<String> password = new Model<String>("");
-	List<RepositoryModel> repositoryModels = new ArrayList<RepositoryModel>();
-
-	public RootPage() {
-		super();
-	}
-
-	public RootPage(PageParameters params) {
-		super(params);
-	}
-
-	@Override
-	protected void setupPage(String repositoryName, String pageName) {
-		boolean authenticateView = GitBlit.getBoolean(Keys.web.authenticateViewPages, false);
-		boolean authenticateAdmin = GitBlit.getBoolean(Keys.web.authenticateAdminPages, true);
-		boolean allowAdmin = GitBlit.getBoolean(Keys.web.allowAdministration, true);
-
-		if (authenticateAdmin) {
-			showAdmin = allowAdmin && GitBlitWebSession.get().canAdmin();
-			// authentication requires state and session
-			setStatelessHint(false);
-		} else {
-			showAdmin = allowAdmin;
-			if (authenticateView) {
-				// authentication requires state and session
-				setStatelessHint(false);
-			} else {
-				// no authentication required, no state and no session required
-				setStatelessHint(true);
-			}
-		}
-		boolean showRegistrations = GitBlit.canFederate()
-				&& GitBlit.getBoolean(Keys.web.showFederationRegistrations, false);
-
-		// navigation links
-		List<PageRegistration> pages = new ArrayList<PageRegistration>();
-		pages.add(new PageRegistration("gb.repositories", RepositoriesPage.class,
-				getRootPageParameters()));
-		pages.add(new PageRegistration("gb.activity", ActivityPage.class, getRootPageParameters()));
-		if (GitBlit.getBoolean(Keys.web.allowLuceneIndexing, true)) {
-			pages.add(new PageRegistration("gb.search", LuceneSearchPage.class));
-		}
-		if (showAdmin) {
-			pages.add(new PageRegistration("gb.users", UsersPage.class));
-		}
-		if (showAdmin || showRegistrations) {
-			pages.add(new PageRegistration("gb.federation", FederationPage.class));
-		}
-
-		if (!authenticateView || (authenticateView && GitBlitWebSession.get().isLoggedIn())) {
-			addDropDownMenus(pages);
-		}
-
-		NavigationPanel navPanel = new NavigationPanel("navPanel", getClass(), pages);
-		add(navPanel);
-
-		// login form
-		SessionlessForm<Void> loginForm = new SessionlessForm<Void>("loginForm", getClass(), getPageParameters()) {
-
-			private static final long serialVersionUID = 1L;
-
-			@Override
-			public void onSubmit() {
-				String username = RootPage.this.username.getObject();
-				char[] password = RootPage.this.password.getObject().toCharArray();
-
-				UserModel user = GitBlit.self().authenticate(username, password);
-				if (user == null) {
-					error(getString("gb.invalidUsernameOrPassword"));
-				} else if (user.username.equals(Constants.FEDERATION_USER)) {
-					// disallow the federation user from logging in via the
-					// web ui
-					error(getString("gb.invalidUsernameOrPassword"));
-					user = null;
-				} else {
-					loginUser(user);
-				}
-			}
-		};
-		TextField<String> unameField = new TextField<String>("username", username);
-		WicketUtils.setInputPlaceholder(unameField, getString("gb.username"));
-		loginForm.add(unameField);
-		PasswordTextField pwField = new PasswordTextField("password", password);
-		WicketUtils.setInputPlaceholder(pwField, getString("gb.password"));
-		loginForm.add(pwField);
-		add(loginForm);
-
-		if (authenticateView || authenticateAdmin) {
-			loginForm.setVisible(!GitBlitWebSession.get().isLoggedIn());
-		} else {
-			loginForm.setVisible(false);
-		}
-
-		// display an error message cached from a redirect
-		String cachedMessage = GitBlitWebSession.get().clearErrorMessage();
-		if (!StringUtils.isEmpty(cachedMessage)) {
-			error(cachedMessage);
-		} else if (showAdmin) {
-			int pendingProposals = GitBlit.self().getPendingFederationProposals().size();
-			if (pendingProposals == 1) {
-				info(getString("gb.OneProposalToReview"));
-			} else if (pendingProposals > 1) {
-				info(MessageFormat.format(getString("gb.nFederationProposalsToReview"),
-						pendingProposals));
-			}
-		}
-
-		super.setupPage(repositoryName, pageName);
-	}
-
-	private PageParameters getRootPageParameters() {
-		if (reusePageParameters()) {
-			PageParameters pp = getPageParameters();
-			if (pp != null) {
-				PageParameters params = new PageParameters(pp);
-				// remove named project parameter
-				params.remove("p");
-
-				// remove named repository parameter
-				params.remove("r");
-
-				// remove named user parameter
-				params.remove("user");
-
-				// remove days back parameter if it is the default value
-				if (params.containsKey("db")
-						&& params.getInt("db") == GitBlit.getInteger(Keys.web.activityDuration, 14)) {
-					params.remove("db");
-				}
-				return params;
-			}			
-		}
-		return null;
-	}
-
-	protected boolean reusePageParameters() {
-		return false;
-	}
-
-	private void loginUser(UserModel user) {
-		if (user != null) {
-			// Set the user into the session
-			GitBlitWebSession session = GitBlitWebSession.get();
-			// issue 62: fix session fixation vulnerability
-			session.replaceSession();
-			session.setUser(user);
-
-			// Set Cookie
-			if (GitBlit.getBoolean(Keys.web.allowCookieAuthentication, false)) {
-				WebResponse response = (WebResponse) getRequestCycle().getResponse();
-				GitBlit.self().setCookie(response, user);
-			}
-
-			if (!session.continueRequest()) {
-				PageParameters params = getPageParameters();
-				if (params == null) {
-					// redirect to this page
-					setResponsePage(getClass());
-				} else {
-					// Strip username and password and redirect to this page
-					params.remove("username");
-					params.remove("password");
-					setResponsePage(getClass(), params);
-				}
-			}
-		}
-	}
-	
-	protected List<RepositoryModel> getRepositoryModels() {
-		if (repositoryModels.isEmpty()) {
-			final UserModel user = GitBlitWebSession.get().getUser();
-			List<RepositoryModel> repositories = GitBlit.self().getRepositoryModels(user);
-			repositoryModels.addAll(repositories);
-			Collections.sort(repositoryModels);
-		}
-		return repositoryModels;
-	}
-
-	protected void addDropDownMenus(List<PageRegistration> pages) {
-
-	}
-
-	protected List<DropDownMenuItem> getRepositoryFilterItems(PageParameters params) {
-		final UserModel user = GitBlitWebSession.get().getUser();
-		Set<DropDownMenuItem> filters = new LinkedHashSet<DropDownMenuItem>();
-		List<RepositoryModel> repositories = getRepositoryModels();
-
-		// accessible repositories by federation set
-		Map<String, AtomicInteger> setMap = new HashMap<String, AtomicInteger>();
-		for (RepositoryModel repository : repositories) {
-			for (String set : repository.federationSets) {
-				String key = set.toLowerCase();
-				if (setMap.containsKey(key)) {
-					setMap.get(key).incrementAndGet();
-				} else {
-					setMap.put(key, new AtomicInteger(1));
-				}
-			}
-		}
-		if (setMap.size() > 0) {
-			List<String> sets = new ArrayList<String>(setMap.keySet());
-			Collections.sort(sets);
-			for (String set : sets) {
-				filters.add(new DropDownMenuItem(MessageFormat.format("{0} ({1})", set,
-						setMap.get(set).get()), "set", set, params));
-			}
-			// divider
-			filters.add(new DropDownMenuItem());
-		}
-
-		// user's team memberships
-		if (user != null && user.teams.size() > 0) {
-			List<TeamModel> teams = new ArrayList<TeamModel>(user.teams);
-			Collections.sort(teams);
-			for (TeamModel team : teams) {
-				filters.add(new DropDownMenuItem(MessageFormat.format("{0} ({1})", team.name,
-						team.repositories.size()), "team", team.name, params));
-			}
-			// divider
-			filters.add(new DropDownMenuItem());
-		}
-
-		// custom filters
-		String customFilters = GitBlit.getString(Keys.web.customFilters, null);
-		if (!StringUtils.isEmpty(customFilters)) {
-			boolean addedExpression = false;
-			List<String> expressions = StringUtils.getStringsFromValue(customFilters, "!!!");
-			for (String expression : expressions) {
-				if (!StringUtils.isEmpty(expression)) {
-					addedExpression = true;
-					filters.add(new DropDownMenuItem(null, "x", expression, params));
-				}
-			}
-			// if we added any custom expressions, add a divider
-			if (addedExpression) {
-				filters.add(new DropDownMenuItem());
-			}
-		}
-		return new ArrayList<DropDownMenuItem>(filters);
-	}
-
-	protected List<DropDownMenuItem> getTimeFilterItems(PageParameters params) {
-		// days back choices - additive parameters
-		int daysBack = GitBlit.getInteger(Keys.web.activityDuration, 14);
-		if (daysBack < 1) {
-			daysBack = 14;
-		}
-		List<DropDownMenuItem> items = new ArrayList<DropDownMenuItem>();
-		Set<Integer> choicesSet = new HashSet<Integer>(Arrays.asList(daysBack, 14, 28, 60, 90, 180));
-		List<Integer> choices = new ArrayList<Integer>(choicesSet);
-		Collections.sort(choices);
-		String lastDaysPattern = getString("gb.lastNDays");
-		for (Integer db : choices) {
-			String txt = MessageFormat.format(lastDaysPattern, db);
-			items.add(new DropDownMenuItem(txt, "db", db.toString(), params));
-		}
-		items.add(new DropDownMenuItem());
-		return items;
-	}
-
-	protected List<RepositoryModel> getRepositories(PageParameters params) {
-		if (params == null) {
-			return getRepositoryModels();
-		}
-
-		boolean hasParameter = false;
-		String projectName = WicketUtils.getProjectName(params);
-		String userName = WicketUtils.getUsername(params);
-		if (StringUtils.isEmpty(projectName)) {
-			if (!StringUtils.isEmpty(userName)) {
-				projectName = "~" + userName;
-			}
-		}
-		String repositoryName = WicketUtils.getRepositoryName(params);
-		String set = WicketUtils.getSet(params);
-		String regex = WicketUtils.getRegEx(params);
-		String team = WicketUtils.getTeam(params);
-		int daysBack = params.getInt("db", 0);
-
-		List<RepositoryModel> availableModels = getRepositoryModels();
-		Set<RepositoryModel> models = new HashSet<RepositoryModel>();
-
-		if (!StringUtils.isEmpty(repositoryName)) {
-			// try named repository
-			hasParameter = true;
-			for (RepositoryModel model : availableModels) {
-				if (model.name.equalsIgnoreCase(repositoryName)) {
-					models.add(model);
-					break;
-				}
-			}
-		}
-
-		if (!StringUtils.isEmpty(projectName)) {
-			// try named project
-			hasParameter = true;			
-			if (projectName.equalsIgnoreCase(GitBlit.getString(Keys.web.repositoryRootGroupName, "main"))) {
-				// root project/group
-				for (RepositoryModel model : availableModels) {
-					if (model.name.indexOf('/') == -1) {
-						models.add(model);
-					}
-				}
-			} else {
-				// named project/group
-				String group = projectName.toLowerCase() + "/";
-				for (RepositoryModel model : availableModels) {
-					if (model.name.toLowerCase().startsWith(group)) {
-						models.add(model);
-					}
-				}
-			}
-		}
-
-		if (!StringUtils.isEmpty(regex)) {
-			// filter the repositories by the regex
-			hasParameter = true;
-			Pattern pattern = Pattern.compile(regex);
-			for (RepositoryModel model : availableModels) {
-				if (pattern.matcher(model.name).find()) {
-					models.add(model);
-				}
-			}
-		}
-
-		if (!StringUtils.isEmpty(set)) {
-			// filter the repositories by the specified sets
-			hasParameter = true;
-			List<String> sets = StringUtils.getStringsFromValue(set, ",");
-			for (RepositoryModel model : availableModels) {
-				for (String curr : sets) {
-					if (model.federationSets.contains(curr)) {
-						models.add(model);
-					}
-				}
-			}
-		}
-
-		if (!StringUtils.isEmpty(team)) {
-			// filter the repositories by the specified teams
-			hasParameter = true;
-			List<String> teams = StringUtils.getStringsFromValue(team, ",");
-
-			// need TeamModels first
-			List<TeamModel> teamModels = new ArrayList<TeamModel>();
-			for (String name : teams) {
-				TeamModel teamModel = GitBlit.self().getTeamModel(name);
-				if (teamModel != null) {
-					teamModels.add(teamModel);
-				}
-			}
-
-			// brute-force our way through finding the matching models
-			for (RepositoryModel repositoryModel : availableModels) {
-				for (TeamModel teamModel : teamModels) {
-					if (teamModel.hasRepositoryPermission(repositoryModel.name)) {
-						models.add(repositoryModel);
-					}
-				}
-			}
-		}
-
-		if (!hasParameter) {
-			models.addAll(availableModels);
-		}
-
-		// time-filter the list
-		if (daysBack > 0) {
-			Calendar cal = Calendar.getInstance();
-			cal.set(Calendar.HOUR_OF_DAY, 0);
-			cal.set(Calendar.MINUTE, 0);
-			cal.set(Calendar.SECOND, 0);
-			cal.set(Calendar.MILLISECOND, 0);
-			cal.add(Calendar.DATE, -1 * daysBack);
-			Date threshold = cal.getTime();
-			Set<RepositoryModel> timeFiltered = new HashSet<RepositoryModel>();
-			for (RepositoryModel model : models) {
-				if (model.lastChange.after(threshold)) {
-					timeFiltered.add(model);
-				}
-			}
-			models = timeFiltered;
-		}
-		
-		List<RepositoryModel> list = new ArrayList<RepositoryModel>(models);
-		Collections.sort(list);
-		return list;
-	}
-}
diff --git a/src/com/gitblit/wicket/pages/RootSubPage.html b/src/com/gitblit/wicket/pages/RootSubPage.html
deleted file mode 100644
index 2b109f9..0000000
--- a/src/com/gitblit/wicket/pages/RootSubPage.html
+++ /dev/null
@@ -1,18 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml"  
-      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"  
-      xml:lang="en"  
-      lang="en"> 
-
-<body>
-<wicket:extend>
-	<!-- page header -->
-	<div class="pageTitle">
-		<h2><span wicket:id="pageName">[page name]</span> <small><span wicket:id="pageSubName">[sub name]</span></small></h2>
-	</div>
-	
-	<!-- Subclass Content -->
-	<wicket:child/>
-</wicket:extend>
-</body>
-</html>
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/pages/SendProposalPage.java b/src/com/gitblit/wicket/pages/SendProposalPage.java
deleted file mode 100644
index fc5f95b..0000000
--- a/src/com/gitblit/wicket/pages/SendProposalPage.java
+++ /dev/null
@@ -1,152 +0,0 @@
-/*
- * 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.wicket.pages;
-
-import java.text.MessageFormat;
-import java.util.ArrayList;
-import java.util.List;
-
-import org.apache.wicket.PageParameters;
-import org.apache.wicket.markup.html.basic.Label;
-import org.apache.wicket.markup.html.form.Button;
-import org.apache.wicket.markup.html.form.Form;
-import org.apache.wicket.markup.html.form.TextField;
-import org.apache.wicket.model.CompoundPropertyModel;
-
-import com.gitblit.Constants.FederationProposalResult;
-import com.gitblit.GitBlit;
-import com.gitblit.models.FederationProposal;
-import com.gitblit.models.RepositoryModel;
-import com.gitblit.utils.FederationUtils;
-import com.gitblit.utils.StringUtils;
-import com.gitblit.wicket.RequiresAdminRole;
-import com.gitblit.wicket.WicketUtils;
-import com.gitblit.wicket.panels.RepositoriesPanel;
-
-@RequiresAdminRole
-public class SendProposalPage extends RootSubPage {
-
-	public String myUrl;
-
-	public String destinationUrl;
-
-	public String message;
-
-	public SendProposalPage(PageParameters params) {
-		super(params);
-
-		setupPage(getString("gb.sendProposal"), "");
-		setStatelessHint(true);
-
-		final String token = WicketUtils.getToken(params);
-
-		myUrl = WicketUtils.getGitblitURL(getRequest());
-		destinationUrl = "https://";
-
-		// temporary proposal
-		FederationProposal proposal = GitBlit.self().createFederationProposal(myUrl, token);
-		if (proposal == null) {
-			error(getString("gb.couldNotCreateFederationProposal"), true);
-		}
-
-		CompoundPropertyModel<SendProposalPage> model = new CompoundPropertyModel<SendProposalPage>(
-				this);
-
-		Form<SendProposalPage> form = new Form<SendProposalPage>("editForm", model) {
-			private static final long serialVersionUID = 1L;
-
-			@Override
-			protected void onSubmit() {
-				// confirm a repository name was entered
-				if (StringUtils.isEmpty(myUrl)) {
-					error(getString("gb.pleaseSetGitblitUrl"));
-					return;
-				}
-				if (StringUtils.isEmpty(destinationUrl)) {
-					error(getString("gb.pleaseSetDestinationUrl"));
-					return;
-				}
-
-				// build new proposal
-				FederationProposal proposal = GitBlit.self().createFederationProposal(myUrl, token);
-				proposal.url = myUrl;
-				proposal.message = message;
-				try {
-					FederationProposalResult res = FederationUtils
-							.propose(destinationUrl, proposal);
-					switch (res) {
-					case ACCEPTED:
-						info(MessageFormat.format(getString("gb.proposalReceived"),
-								destinationUrl));
-						setResponsePage(RepositoriesPage.class);
-						break;
-					case NO_POKE:
-						error(MessageFormat.format(getString("noGitblitFound"),
-								destinationUrl, myUrl));
-						break;
-					case NO_PROPOSALS:
-						error(MessageFormat.format(getString("gb.noProposals"),
-								destinationUrl));
-						break;
-					case FEDERATION_DISABLED:
-						error(MessageFormat
-								.format(getString("gb.noFederation"),
-										destinationUrl));
-						break;
-					case MISSING_DATA:
-						error(MessageFormat.format(getString("gb.proposalFailed"),
-								destinationUrl));
-						break;
-					case ERROR:
-						error(MessageFormat.format(getString("gb.proposalError"),
-								destinationUrl));
-						break;
-					}
-				} catch (Exception e) {
-					if (!StringUtils.isEmpty(e.getMessage())) {
-						error(e.getMessage());
-					} else {
-						error(getString("gb.failedToSendProposal"));
-					}
-				}
-			}
-		};
-		form.add(new TextField<String>("myUrl"));
-		form.add(new TextField<String>("destinationUrl"));
-		form.add(new TextField<String>("message"));
-		form.add(new Label("tokenType", proposal.tokenType.name()));
-		form.add(new Label("token", proposal.token));
-
-		form.add(new Button("save"));
-		Button cancel = new Button("cancel") {
-			private static final long serialVersionUID = 1L;
-
-			@Override
-			public void onSubmit() {
-				setResponsePage(FederationPage.class);
-			}
-		};
-		cancel.setDefaultFormProcessing(false);
-		form.add(cancel);
-		add(form);
-
-		List<RepositoryModel> repositories = new ArrayList<RepositoryModel>(
-				proposal.repositories.values());
-		RepositoriesPanel repositoriesPanel = new RepositoriesPanel("repositoriesPanel", false,
-				false, repositories, false, getAccessRestrictions());
-		add(repositoriesPanel);
-	}
-}
diff --git a/src/com/gitblit/wicket/pages/SummaryPage.html b/src/com/gitblit/wicket/pages/SummaryPage.html
deleted file mode 100644
index 3e85df9..0000000
--- a/src/com/gitblit/wicket/pages/SummaryPage.html
+++ /dev/null
@@ -1,54 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml"  
-      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"  
-      xml:lang="en"  
-      lang="en"> 
-<body>
-<wicket:extend>
-	
-	<div style="clear:both;">
-		<!-- Repository Activity Chart -->	
-		<div class="hidden-phone" style="float:right;">
-			<img class="activityGraph" wicket:id="commitsChart" />
-		</div>	
-	
-		<!-- Repository info -->
-		<div class="hidden-phone" style="padding-bottom: 10px;"> 
-			<table class="plain">
-				<tr><th><wicket:message key="gb.description">[description]</wicket:message></th><td><span wicket:id="repositoryDescription">[repository description]</span></td></tr>
-				<tr><th><wicket:message key="gb.owners">[owner]</wicket:message></th><td><span wicket:id="repositoryOwners"><span wicket:id="owner"></span><span wicket:id="comma"></span></span></td></tr>
-				<tr><th><wicket:message key="gb.lastChange">[last change]</wicket:message></th><td><span wicket:id="repositoryLastChange">[repository last change]</span></td></tr>
-				<tr><th><wicket:message key="gb.stats">[stats]</wicket:message></th><td><span wicket:id="branchStats">[branch stats]</span> <span class="link"><a wicket:id="metrics"><wicket:message key="gb.metrics">[metrics]</wicket:message></a></span></td></tr>
-				<tr><th style="vertical-align:top;"><wicket:message key="gb.repositoryUrl">[URL]</wicket:message>&nbsp;<img style="vertical-align: top;padding-left:3px;" wicket:id="accessRestrictionIcon" /></th><td><span wicket:id="repositoryCloneUrl">[repository clone url]</span><div wicket:id="otherUrls"></div></td></tr>
-			</table>
-		</div>
-	</div>
-
-	<!-- commits -->
-	<div style="padding-bottom:15px;" wicket:id="commitsPanel">[commits panel]</div>	
-
-	<!-- tags -->
-	<div style="padding-bottom:15px;" wicket:id="tagsPanel">[tags panel]</div>
-
-	<!-- branches -->
-	<div style="padding-bottom:15px;" wicket:id="branchesPanel">[branches panel]</div>
-	
-	<!-- markdown readme -->
-	<div wicket:id="readme"></div>
-	
-	<wicket:fragment wicket:id="markdownPanel">
-		<div class="header" style="margin-top:0px;" >
-			<i style="vertical-align: middle;" class="icon-book"></i>
-			<span style="font-weight:bold;vertical-align:middle;" wicket:id="readmeFile"></span>
-		</div>
-		<div style="border:1px solid #ddd;border-radius: 0 0 3px 3px;padding: 20px;">
-			<div wicket:id="readmeContent" class="markdown"></div>
-		</div>
-	</wicket:fragment>
-	
-	<wicket:fragment wicket:id="ownersFragment">
-		
-	</wicket:fragment>
-</wicket:extend>	
-</body>
-</html>
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/pages/SummaryPage.java b/src/com/gitblit/wicket/pages/SummaryPage.java
deleted file mode 100644
index bd40a1b..0000000
--- a/src/com/gitblit/wicket/pages/SummaryPage.java
+++ /dev/null
@@ -1,238 +0,0 @@
-/*
- * 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.wicket.pages;
-
-import java.awt.Color;
-import java.awt.Dimension;
-import java.text.MessageFormat;
-import java.text.ParseException;
-import java.util.ArrayList;
-import java.util.List;
-
-import org.apache.wicket.Component;
-import org.apache.wicket.PageParameters;
-import org.apache.wicket.markup.html.basic.Label;
-import org.apache.wicket.markup.html.link.BookmarkablePageLink;
-import org.apache.wicket.markup.html.panel.Fragment;
-import org.apache.wicket.markup.repeater.Item;
-import org.apache.wicket.markup.repeater.data.DataView;
-import org.apache.wicket.markup.repeater.data.ListDataProvider;
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.revwalk.RevCommit;
-import org.wicketstuff.googlecharts.Chart;
-import org.wicketstuff.googlecharts.ChartAxis;
-import org.wicketstuff.googlecharts.ChartAxisType;
-import org.wicketstuff.googlecharts.ChartProvider;
-import org.wicketstuff.googlecharts.ChartType;
-import org.wicketstuff.googlecharts.IChartData;
-import org.wicketstuff.googlecharts.LineStyle;
-import org.wicketstuff.googlecharts.MarkerType;
-import org.wicketstuff.googlecharts.ShapeMarker;
-
-import com.gitblit.Constants.AccessRestrictionType;
-import com.gitblit.GitBlit;
-import com.gitblit.Keys;
-import com.gitblit.models.Metric;
-import com.gitblit.models.PathModel;
-import com.gitblit.models.RepositoryModel;
-import com.gitblit.models.UserModel;
-import com.gitblit.utils.ArrayUtils;
-import com.gitblit.utils.JGitUtils;
-import com.gitblit.utils.MarkdownUtils;
-import com.gitblit.utils.StringUtils;
-import com.gitblit.wicket.WicketUtils;
-import com.gitblit.wicket.panels.BranchesPanel;
-import com.gitblit.wicket.panels.LinkPanel;
-import com.gitblit.wicket.panels.LogPanel;
-import com.gitblit.wicket.panels.RepositoryUrlPanel;
-import com.gitblit.wicket.panels.TagsPanel;
-
-public class SummaryPage extends RepositoryPage {
-
-	public SummaryPage(PageParameters params) {
-		super(params);
-
-		int numberCommits = GitBlit.getInteger(Keys.web.summaryCommitCount, 20);
-		if (numberCommits <= 0) {
-			numberCommits = 20;
-		}
-		int numberRefs = GitBlit.getInteger(Keys.web.summaryRefsCount, 5);
-
-		Repository r = getRepository();
-		RepositoryModel model = getRepositoryModel();
-
-		List<Metric> metrics = null;
-		Metric metricsTotal = null;
-		if (!model.skipSummaryMetrics && GitBlit.getBoolean(Keys.web.generateActivityGraph, true)) {
-			metrics = GitBlit.self().getRepositoryDefaultMetrics(model, r);
-			metricsTotal = metrics.remove(0);
-		}
-
-		addSyndicationDiscoveryLink();
-
-		// repository description
-		add(new Label("repositoryDescription", getRepositoryModel().description));
-		
-		// owner links
-		final List<String> owners = new ArrayList<String>(getRepositoryModel().owners);
-		ListDataProvider<String> ownersDp = new ListDataProvider<String>(owners);
-		DataView<String> ownersView = new DataView<String>("repositoryOwners", ownersDp) {
-			private static final long serialVersionUID = 1L;
-			int counter = 0;
-			public void populateItem(final Item<String> item) {
-				UserModel ownerModel = GitBlit.self().getUserModel(item.getModelObject());
-				if (ownerModel != null) {
-					item.add(new LinkPanel("owner", null, ownerModel.getDisplayName(), UserPage.class,
-							WicketUtils.newUsernameParameter(ownerModel.username)).setRenderBodyOnly(true));
-				} else {
-					item.add(new Label("owner").setVisible(false));
-				}
-				counter++;
-				item.add(new Label("comma", ",").setVisible(counter < owners.size()));
-				item.setRenderBodyOnly(true);
-			}
-		};
-		ownersView.setRenderBodyOnly(true);
-		add(ownersView);
-		
-		add(WicketUtils.createTimestampLabel("repositoryLastChange",
-				JGitUtils.getLastChange(r), getTimeZone(), getTimeUtils()));
-		if (metricsTotal == null) {
-			add(new Label("branchStats", ""));
-		} else {
-			add(new Label("branchStats",
-					MessageFormat.format(getString("gb.branchStats"), metricsTotal.count,
-							metricsTotal.tag, getTimeUtils().duration(metricsTotal.duration))));
-		}
-		add(new BookmarkablePageLink<Void>("metrics", MetricsPage.class,
-				WicketUtils.newRepositoryParameter(repositoryName)));
-
-		List<String> repositoryUrls = new ArrayList<String>();
-
-		if (GitBlit.getBoolean(Keys.git.enableGitServlet, true)) {
-			AccessRestrictionType accessRestriction = getRepositoryModel().accessRestriction;
-			switch (accessRestriction) {
-			case NONE:
-				add(WicketUtils.newClearPixel("accessRestrictionIcon").setVisible(false));
-				break;
-			case PUSH:
-				add(WicketUtils.newImage("accessRestrictionIcon", "lock_go_16x16.png",
-						getAccessRestrictions().get(accessRestriction)));
-				break;
-			case CLONE:
-				add(WicketUtils.newImage("accessRestrictionIcon", "lock_pull_16x16.png",
-						getAccessRestrictions().get(accessRestriction)));
-				break;
-			case VIEW:
-				add(WicketUtils.newImage("accessRestrictionIcon", "shield_16x16.png",
-						getAccessRestrictions().get(accessRestriction)));
-				break;
-			default:
-				add(WicketUtils.newClearPixel("accessRestrictionIcon").setVisible(false));
-			}
-			// add the Gitblit repository url
-			repositoryUrls.add(getRepositoryUrl(getRepositoryModel()));
-		} else {
-			add(WicketUtils.newClearPixel("accessRestrictionIcon").setVisible(false));
-		}
-		repositoryUrls.addAll(GitBlit.self().getOtherCloneUrls(repositoryName));
-		
-		String primaryUrl = ArrayUtils.isEmpty(repositoryUrls) ? "" : repositoryUrls.remove(0);
-		add(new RepositoryUrlPanel("repositoryCloneUrl", primaryUrl));
-
-		add(new Label("otherUrls", StringUtils.flattenStrings(repositoryUrls, "<br/>"))
-		.setEscapeModelStrings(false));
-
-		add(new LogPanel("commitsPanel", repositoryName, getRepositoryModel().HEAD, r, numberCommits, 0, getRepositoryModel().showRemoteBranches));
-		add(new TagsPanel("tagsPanel", repositoryName, r, numberRefs).hideIfEmpty());
-		add(new BranchesPanel("branchesPanel", getRepositoryModel(), r, numberRefs, false).hideIfEmpty());
-
-		if (getRepositoryModel().showReadme) {
-			String htmlText = null;
-			String markdownText = null;
-			String readme = null;
-			try {
-				RevCommit head = JGitUtils.getCommit(r, null);
-				List<String> markdownExtensions = GitBlit.getStrings(Keys.web.markdownExtensions);
-				List<PathModel> paths = JGitUtils.getFilesInPath(r, null, head);				
-				for (PathModel path : paths) {
-					if (!path.isTree()) {
-						String name = path.name.toLowerCase();
-
-						if (name.startsWith("readme")) {
-							if (name.indexOf('.') > -1) {
-								String ext = name.substring(name.lastIndexOf('.') + 1);
-								if (markdownExtensions.contains(ext)) {
-									readme = path.name;
-									break;
-								}
-							}
-						}
-					}
-				}
-				if (!StringUtils.isEmpty(readme)) {
-					String [] encodings = GitBlit.getEncodings();
-					markdownText = JGitUtils.getStringContent(r, head.getTree(), readme, encodings);
-					htmlText = MarkdownUtils.transformMarkdown(markdownText);
-				}
-			} catch (ParseException p) {
-				markdownText = MessageFormat.format("<div class=\"alert alert-error\"><strong>{0}:</strong> {1}</div>{2}", getString("gb.error"), getString("gb.markdownFailure"), markdownText);
-				htmlText = StringUtils.breakLinesForHtml(markdownText);
-			}
-			Fragment fragment = new Fragment("readme", "markdownPanel");
-			fragment.add(new Label("readmeFile", readme));
-			// Add the html to the page
-			Component content = new Label("readmeContent", htmlText).setEscapeModelStrings(false);
-			fragment.add(content.setVisible(!StringUtils.isEmpty(htmlText)));
-			add(fragment);
-		} else {
-			add(new Label("readme").setVisible(false));
-		}
-
-		// Display an activity line graph
-		insertActivityGraph(metrics);
-	}
-
-	@Override
-	protected String getPageName() {
-		return getString("gb.summary");
-	}
-
-	private void insertActivityGraph(List<Metric> metrics) {
-		if ((metrics != null) && (metrics.size() > 0)
-				&& GitBlit.getBoolean(Keys.web.generateActivityGraph, true)) {
-			IChartData data = WicketUtils.getChartData(metrics);
-
-			ChartProvider provider = new ChartProvider(new Dimension(290, 100), ChartType.LINE,
-					data);
-			ChartAxis dateAxis = new ChartAxis(ChartAxisType.BOTTOM);
-			dateAxis.setLabels(new String[] { metrics.get(0).name,
-					metrics.get(metrics.size() / 2).name, metrics.get(metrics.size() - 1).name });
-			provider.addAxis(dateAxis);
-
-			ChartAxis commitAxis = new ChartAxis(ChartAxisType.LEFT);
-			commitAxis.setLabels(new String[] { "",
-					String.valueOf((int) WicketUtils.maxValue(metrics)) });
-			provider.addAxis(commitAxis);
-			provider.setLineStyles(new LineStyle[] { new LineStyle(2, 4, 0), new LineStyle(0, 4, 1) });
-			provider.addShapeMarker(new ShapeMarker(MarkerType.CIRCLE, Color.BLUE, 1, -1, 5));
-
-			add(new Chart("commitsChart", provider));
-		} else {
-			add(WicketUtils.newBlankImage("commitsChart"));
-		}
-	}
-}
diff --git a/src/com/gitblit/wicket/pages/TagPage.html b/src/com/gitblit/wicket/pages/TagPage.html
deleted file mode 100644
index 19f828e..0000000
--- a/src/com/gitblit/wicket/pages/TagPage.html
+++ /dev/null
@@ -1,38 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml"  
-      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"  
-      xml:lang="en"  
-      lang="en"> 
-
-<body>
-<wicket:extend>
-	
-	<!-- summary header -->
-	<div style="margin-top: 5px;" class="header" wicket:id="commit">[shortlog header]</div>
-	
-	<!-- Tagger Gravatar -->
-	<span style="float:right;vertical-align: top;" wicket:id="taggerAvatar" />
-	
-	<!-- commit info -->
-	<table class="plain">
-		<tr><th><wicket:message key="gb.name">[name]</wicket:message></th><td><span wicket:id="tagName">[tag name]</span></td></tr>
-		<tr><th><wicket:message key="gb.tag">[tag]</wicket:message></th><td><span class="sha1" wicket:id="tagId">[tag id]</span></td></tr>
-		<tr><th><wicket:message key="gb.object">[object]</wicket:message></th><td><span class="sha1" wicket:id="taggedObject">[tagged object]</span> <span class="link" wicket:id="taggedObjectType"></span></td></tr>
-		<tr><th><wicket:message key="gb.tagger">[tagger]</wicket:message></th><td><span class="sha1" wicket:id="tagger">[tagger]</span></td></tr>
-		<tr><th></th><td><span class="sha1" wicket:id="tagDate">[tag date]</span></td></tr>
-	</table>
-	
-	<!--  full message -->
-	<div style="border-bottom:0px;" class="commit_message" wicket:id="fullMessage">[tag full message]</div>
-	
-	<wicket:fragment wicket:id="fullPersonIdent">
-		<span wicket:id="personName"></span><span wicket:id="personAddress"></span>
-	</wicket:fragment>
-	
-	<wicket:fragment wicket:id="partialPersonIdent">
-		<span wicket:id="personName"></span>
-	</wicket:fragment>
-	
-</wicket:extend>
-</body>
-</html>
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/pages/TagPage.java b/src/com/gitblit/wicket/pages/TagPage.java
deleted file mode 100644
index 91c913d..0000000
--- a/src/com/gitblit/wicket/pages/TagPage.java
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * 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.wicket.pages;
-
-import java.text.MessageFormat;
-import java.util.Arrays;
-import java.util.Date;
-import java.util.List;
-
-import org.apache.wicket.PageParameters;
-import org.apache.wicket.markup.html.basic.Label;
-import org.eclipse.jgit.lib.Constants;
-import org.eclipse.jgit.lib.Repository;
-
-import com.gitblit.models.RefModel;
-import com.gitblit.utils.JGitUtils;
-import com.gitblit.wicket.WicketUtils;
-import com.gitblit.wicket.panels.GravatarImage;
-import com.gitblit.wicket.panels.LinkPanel;
-import com.gitblit.wicket.panels.RefsPanel;
-
-public class TagPage extends RepositoryPage {
-
-	public TagPage(PageParameters params) {
-		super(params);
-
-		Repository r = getRepository();
-
-		// Find tag in repository
-		List<RefModel> tags = JGitUtils.getTags(r, true, -1);
-		RefModel tagRef = null;
-		for (RefModel tag : tags) {
-			if (tag.getName().equals(objectId) || tag.getObjectId().getName().equals(objectId)) {
-				tagRef = tag;
-				break;
-			}
-		}
-
-		// Failed to find tag!
-		if (tagRef == null) {
-			error(MessageFormat.format(getString("gb.couldNotFindTag"), objectId), true);
-		}
-
-		// Display tag.
-		Class<? extends RepositoryPage> linkClass;
-		PageParameters linkParameters = newCommitParameter(tagRef.getReferencedObjectId().getName());
-		String typeKey;
-		switch (tagRef.getReferencedObjectType()) {
-		case Constants.OBJ_BLOB:
-			typeKey = "gb.blob";
-			linkClass = BlobPage.class;
-			break;
-		case Constants.OBJ_TREE:
-			typeKey = "gb.tree";
-			linkClass = TreePage.class;
-			break;
-		case Constants.OBJ_COMMIT:
-		default:
-			typeKey = "gb.commit";
-			linkClass = CommitPage.class;
-			break;
-		}
-		add(new LinkPanel("commit", "title", tagRef.displayName, linkClass, linkParameters));
-		add(new GravatarImage("taggerAvatar", tagRef.getAuthorIdent()));
-		
-		add(new RefsPanel("tagName", repositoryName, Arrays.asList(tagRef)));
-		add(new Label("tagId", tagRef.getObjectId().getName()));
-		add(new LinkPanel("taggedObject", "list", tagRef.getReferencedObjectId().getName(),
-				linkClass, linkParameters));
-		add(new Label("taggedObjectType", getString(typeKey)));
-
-		add(createPersonPanel("tagger", tagRef.getAuthorIdent(), com.gitblit.Constants.SearchType.AUTHOR));
-		Date when = new Date(0);
-		if (tagRef.getAuthorIdent() != null) {
-			when = tagRef.getAuthorIdent().getWhen();
-		}
-		add(WicketUtils.createTimestampLabel("tagDate", when, getTimeZone(), getTimeUtils()));
-
-		addFullText("fullMessage", tagRef.getFullMessage(), true);
-	}
-
-	@Override
-	protected String getPageName() {
-		return getString("gb.tag");
-	}
-}
diff --git a/src/com/gitblit/wicket/pages/TagsPage.java b/src/com/gitblit/wicket/pages/TagsPage.java
deleted file mode 100644
index 3ddbde9..0000000
--- a/src/com/gitblit/wicket/pages/TagsPage.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * 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.wicket.pages;
-
-import org.apache.wicket.PageParameters;
-
-import com.gitblit.wicket.panels.TagsPanel;
-
-public class TagsPage extends RepositoryPage {
-
-	public TagsPage(PageParameters params) {
-		super(params);
-
-		add(new TagsPanel("tagsPanel", repositoryName, getRepository(), -1));
-
-	}
-
-	@Override
-	protected String getPageName() {
-		return getString("gb.tags");
-	}
-}
diff --git a/src/com/gitblit/wicket/pages/TicketPage.java b/src/com/gitblit/wicket/pages/TicketPage.java
deleted file mode 100644
index 5723386..0000000
--- a/src/com/gitblit/wicket/pages/TicketPage.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * 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.wicket.pages;
-
-import org.apache.wicket.PageParameters;
-import org.apache.wicket.markup.html.basic.Label;
-import org.apache.wicket.markup.repeater.Item;
-import org.apache.wicket.markup.repeater.data.DataView;
-import org.apache.wicket.markup.repeater.data.ListDataProvider;
-import org.eclipse.jgit.lib.Repository;
-
-import com.gitblit.models.TicketModel;
-import com.gitblit.models.TicketModel.Comment;
-import com.gitblit.utils.StringUtils;
-import com.gitblit.utils.TicgitUtils;
-import com.gitblit.wicket.GitBlitWebSession;
-import com.gitblit.wicket.WicketUtils;
-
-public class TicketPage extends RepositoryPage {
-
-	public TicketPage(PageParameters params) {
-		super(params);
-
-		final String ticketFolder = WicketUtils.getPath(params);
-
-		Repository r = getRepository();
-		TicketModel t = TicgitUtils.getTicket(r, ticketFolder);
-
-		add(new Label("ticketTitle", t.title));
-		add(new Label("ticketId", t.id));
-		add(new Label("ticketHandler", t.handler.toLowerCase()));
-		add(WicketUtils.createTimestampLabel("ticketOpenDate", t.date, getTimeZone(), getTimeUtils()));
-		Label stateLabel = new Label("ticketState", t.state);
-		WicketUtils.setTicketCssClass(stateLabel, t.state);
-		add(stateLabel);
-		add(new Label("ticketTags", StringUtils.flattenStrings(t.tags)));
-
-		ListDataProvider<Comment> commentsDp = new ListDataProvider<Comment>(t.comments);
-		DataView<Comment> commentsView = new DataView<Comment>("comment", commentsDp) {
-			private static final long serialVersionUID = 1L;
-			int counter;
-
-			public void populateItem(final Item<Comment> item) {
-				final Comment entry = item.getModelObject();
-				item.add(WicketUtils.createDateLabel("commentDate", entry.date, GitBlitWebSession
-						.get().getTimezone(), getTimeUtils()));
-				item.add(new Label("commentAuthor", entry.author.toLowerCase()));
-				item.add(new Label("commentText", prepareComment(entry.text))
-						.setEscapeModelStrings(false));
-				WicketUtils.setAlternatingBackground(item, counter);
-				counter++;
-			}
-		};
-		add(commentsView);
-	}
-
-	@Override
-	protected String getPageName() {
-		return getString("gb.ticket");
-	}
-
-	private String prepareComment(String comment) {
-		String html = StringUtils.escapeForHtml(comment, false);
-		html = StringUtils.breakLinesForHtml(comment).trim();
-		return html.replaceAll("\\bcommit\\s*([A-Za-z0-9]*)\\b", "<a href=\"/commit/"
-				+ repositoryName + "/$1\">commit $1</a>");
-	}
-}
diff --git a/src/com/gitblit/wicket/pages/TicketsPage.html b/src/com/gitblit/wicket/pages/TicketsPage.html
deleted file mode 100644
index 0913dc2..0000000
--- a/src/com/gitblit/wicket/pages/TicketsPage.html
+++ /dev/null
@@ -1,27 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml"  
-      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"  
-      xml:lang="en"  
-      lang="en"> 
-
-<body>
-<wicket:extend>
-
-	<!-- header -->	
-	<div style="margin-top:5px;" class="header" wicket:id="header">[header]</div>
-	
-	<!-- tickets -->	
-	<table class="pretty">
-		<tbody>
-       		<tr wicket:id="ticket">
-         		<td style="padding:0; margin:0;"><div wicket:id="ticketState">[ticket state]</div></td>
-         		<td class="date"><span wicket:id="ticketDate">[ticket date]</span></td>
-         		<td class="author"><div wicket:id="ticketHandler">[ticket handler]</div></td>
-         		<td><div wicket:id="ticketTitle">[ticket title]</div></td>
-       		</tr>
-    	</tbody>
-	</table>	
-
-</wicket:extend>
-</body>
-</html>
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/pages/TicketsPage.java b/src/com/gitblit/wicket/pages/TicketsPage.java
deleted file mode 100644
index b68b7e4..0000000
--- a/src/com/gitblit/wicket/pages/TicketsPage.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * 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.wicket.pages;
-
-import java.util.List;
-
-import org.apache.wicket.PageParameters;
-import org.apache.wicket.markup.html.basic.Label;
-import org.apache.wicket.markup.repeater.Item;
-import org.apache.wicket.markup.repeater.data.DataView;
-import org.apache.wicket.markup.repeater.data.ListDataProvider;
-
-import com.gitblit.models.TicketModel;
-import com.gitblit.utils.StringUtils;
-import com.gitblit.utils.TicgitUtils;
-import com.gitblit.wicket.GitBlitWebSession;
-import com.gitblit.wicket.WicketUtils;
-import com.gitblit.wicket.panels.LinkPanel;
-
-public class TicketsPage extends RepositoryPage {
-
-	public TicketsPage(PageParameters params) {
-		super(params);
-
-		List<TicketModel> tickets = TicgitUtils.getTickets(getRepository());
-
-		// header
-		add(new LinkPanel("header", "title", repositoryName, SummaryPage.class,
-				newRepositoryParameter()));
-
-		ListDataProvider<TicketModel> ticketsDp = new ListDataProvider<TicketModel>(tickets);
-		DataView<TicketModel> ticketsView = new DataView<TicketModel>("ticket", ticketsDp) {
-			private static final long serialVersionUID = 1L;
-			int counter;
-
-			public void populateItem(final Item<TicketModel> item) {
-				final TicketModel entry = item.getModelObject();
-				Label stateLabel = new Label("ticketState", entry.state);
-				WicketUtils.setTicketCssClass(stateLabel, entry.state);
-				item.add(stateLabel);
-				item.add(WicketUtils.createDateLabel("ticketDate", entry.date, GitBlitWebSession
-						.get().getTimezone(), getTimeUtils()));
-				item.add(new Label("ticketHandler", StringUtils.trimString(
-						entry.handler.toLowerCase(), 30)));
-				item.add(new LinkPanel("ticketTitle", "list subject", StringUtils.trimString(
-						entry.title, 80), TicketPage.class, newPathParameter(entry.name)));
-
-				WicketUtils.setAlternatingBackground(item, counter);
-				counter++;
-			}
-		};
-		add(ticketsView);
-	}
-
-	protected PageParameters newPathParameter(String path) {
-		return WicketUtils.newPathParameter(repositoryName, objectId, path);
-	}
-
-	@Override
-	protected String getPageName() {
-		return getString("gb.tickets");
-	}
-}
diff --git a/src/com/gitblit/wicket/pages/TreePage.java b/src/com/gitblit/wicket/pages/TreePage.java
deleted file mode 100644
index bc27f0c..0000000
--- a/src/com/gitblit/wicket/pages/TreePage.java
+++ /dev/null
@@ -1,186 +0,0 @@
-/*
- * 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.wicket.pages;
-
-import java.util.List;
-
-import org.apache.wicket.PageParameters;
-import org.apache.wicket.markup.html.basic.Label;
-import org.apache.wicket.markup.html.link.BookmarkablePageLink;
-import org.apache.wicket.markup.html.panel.Fragment;
-import org.apache.wicket.markup.repeater.Item;
-import org.apache.wicket.markup.repeater.data.DataView;
-import org.apache.wicket.markup.repeater.data.ListDataProvider;
-import org.eclipse.jgit.lib.Constants;
-import org.eclipse.jgit.lib.FileMode;
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.revwalk.RevCommit;
-
-import com.gitblit.models.PathModel;
-import com.gitblit.models.SubmoduleModel;
-import com.gitblit.utils.ByteFormat;
-import com.gitblit.utils.JGitUtils;
-import com.gitblit.wicket.WicketUtils;
-import com.gitblit.wicket.panels.CommitHeaderPanel;
-import com.gitblit.wicket.panels.CompressedDownloadsPanel;
-import com.gitblit.wicket.panels.LinkPanel;
-import com.gitblit.wicket.panels.PathBreadcrumbsPanel;
-
-public class TreePage extends RepositoryPage {
-
-	public TreePage(PageParameters params) {
-		super(params);
-
-		final String path = WicketUtils.getPath(params);
-
-		Repository r = getRepository();
-		RevCommit commit = getCommit();
-		List<PathModel> paths = JGitUtils.getFilesInPath(r, path, commit);
-
-		// tree page links
-		add(new BookmarkablePageLink<Void>("historyLink", HistoryPage.class,
-				WicketUtils.newPathParameter(repositoryName, objectId, path)));
-		add(new BookmarkablePageLink<Void>("headLink", TreePage.class,
-				WicketUtils.newPathParameter(repositoryName, Constants.HEAD, path)));
-		add(new CompressedDownloadsPanel("compressedLinks", getRequest()
-				.getRelativePathPrefixToContextRoot(), repositoryName, objectId, path));
-
-		add(new CommitHeaderPanel("commitHeader", repositoryName, commit));
-
-		// breadcrumbs
-		add(new PathBreadcrumbsPanel("breadcrumbs", repositoryName, path, objectId));
-		if (path != null && path.trim().length() > 0) {
-			// add .. parent path entry
-			String parentPath = null;
-			if (path.lastIndexOf('/') > -1) {
-				parentPath = path.substring(0, path.lastIndexOf('/'));
-			}
-			PathModel model = new PathModel("..", parentPath, 0, FileMode.TREE.getBits(), null, objectId);
-			model.isParentPath = true;
-			paths.add(0, model);
-		}
-
-		final ByteFormat byteFormat = new ByteFormat();
-
-		final String baseUrl = WicketUtils.getGitblitURL(getRequest());
-
-		// changed paths list
-		ListDataProvider<PathModel> pathsDp = new ListDataProvider<PathModel>(paths);
-		DataView<PathModel> pathsView = new DataView<PathModel>("changedPath", pathsDp) {
-			private static final long serialVersionUID = 1L;
-			int counter;
-
-			public void populateItem(final Item<PathModel> item) {
-				PathModel entry = item.getModelObject();
-				item.add(new Label("pathPermissions", JGitUtils.getPermissionsFromMode(entry.mode)));
-				if (entry.isParentPath) {
-					// parent .. path
-					item.add(WicketUtils.newBlankImage("pathIcon"));
-					item.add(new Label("pathSize", ""));
-					item.add(new LinkPanel("pathName", null, entry.name, TreePage.class,
-							WicketUtils
-									.newPathParameter(repositoryName, entry.commitId, entry.path)));
-					item.add(new Label("pathLinks", ""));
-				} else {
-					if (entry.isTree()) {
-						// folder/tree link
-						item.add(WicketUtils.newImage("pathIcon", "folder_16x16.png"));
-						item.add(new Label("pathSize", ""));
-						item.add(new LinkPanel("pathName", "list", entry.name, TreePage.class,
-								WicketUtils.newPathParameter(repositoryName, entry.commitId,
-										entry.path)));
-
-						// links
-						Fragment links = new Fragment("pathLinks", "treeLinks", this);
-						links.add(new BookmarkablePageLink<Void>("tree", TreePage.class,
-								WicketUtils.newPathParameter(repositoryName, entry.commitId,
-										entry.path)));
-						links.add(new BookmarkablePageLink<Void>("history", HistoryPage.class,
-								WicketUtils.newPathParameter(repositoryName, entry.commitId,
-										entry.path)));						
-						links.add(new CompressedDownloadsPanel("compressedLinks", baseUrl,
-								repositoryName, objectId, entry.path));
-
-						item.add(links);
-					} else if (entry.isSubmodule()) {
-						// submodule
-						String submoduleId = entry.objectId;						
-						String submodulePath;
-						boolean hasSubmodule = false;
-						SubmoduleModel submodule = getSubmodule(entry.path);
-						submodulePath = submodule.gitblitPath;
-						hasSubmodule = submodule.hasSubmodule;
-						
-						item.add(WicketUtils.newImage("pathIcon", "git-orange-16x16.png"));
-						item.add(new Label("pathSize", ""));
-						item.add(new LinkPanel("pathName", "list", entry.name + " @ " + 
-								getShortObjectId(submoduleId), TreePage.class,
-								WicketUtils.newPathParameter(submodulePath, submoduleId, "")).setEnabled(hasSubmodule));
-						
-						Fragment links = new Fragment("pathLinks", "submoduleLinks", this);
-						links.add(new BookmarkablePageLink<Void>("view", SummaryPage.class,
-								WicketUtils.newRepositoryParameter(submodulePath)).setEnabled(hasSubmodule));
-						links.add(new BookmarkablePageLink<Void>("tree", TreePage.class,
-								WicketUtils.newPathParameter(submodulePath, submoduleId,
-										"")).setEnabled(hasSubmodule));
-						links.add(new BookmarkablePageLink<Void>("history", HistoryPage.class,
-								WicketUtils.newPathParameter(repositoryName, entry.commitId,
-										entry.path)));
-						links.add(new CompressedDownloadsPanel("compressedLinks", baseUrl,
-								submodulePath, submoduleId, "").setEnabled(hasSubmodule));
-						item.add(links);						
-					} else {
-						// blob link
-						String displayPath = entry.name;
-						String path = entry.path;
-						if (entry.isSymlink()) {
-							path = JGitUtils.getStringContent(getRepository(), getCommit().getTree(), path);
-							displayPath = entry.name + " -> " + path;
-						}
-						item.add(WicketUtils.getFileImage("pathIcon", entry.name));
-						item.add(new Label("pathSize", byteFormat.format(entry.size)));
-						item.add(new LinkPanel("pathName", "list", displayPath, BlobPage.class,
-								WicketUtils.newPathParameter(repositoryName, entry.commitId,
-										path)));
-
-						// links
-						Fragment links = new Fragment("pathLinks", "blobLinks", this);
-						links.add(new BookmarkablePageLink<Void>("view", BlobPage.class,
-								WicketUtils.newPathParameter(repositoryName, entry.commitId,
-										path)));
-						links.add(new BookmarkablePageLink<Void>("raw", RawPage.class, WicketUtils
-								.newPathParameter(repositoryName, entry.commitId, path)));
-						links.add(new BookmarkablePageLink<Void>("blame", BlamePage.class,
-								WicketUtils.newPathParameter(repositoryName, entry.commitId,
-										path)));
-						links.add(new BookmarkablePageLink<Void>("history", HistoryPage.class,
-								WicketUtils.newPathParameter(repositoryName, entry.commitId,
-										path)));
-						item.add(links);
-					}
-				}
-				WicketUtils.setAlternatingBackground(item, counter);
-				counter++;
-			}
-		};
-		add(pathsView);
-	}
-
-	@Override
-	protected String getPageName() {
-		return getString("gb.tree");
-	}
-}
diff --git a/src/com/gitblit/wicket/pages/UserPage.html b/src/com/gitblit/wicket/pages/UserPage.html
deleted file mode 100644
index c7131c0..0000000
--- a/src/com/gitblit/wicket/pages/UserPage.html
+++ /dev/null
@@ -1,50 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml"  
-      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"  
-      xml:lang="en"  
-      lang="en"> 
-
-<body>
-<wicket:extend>
-
-	<div class="row">
-		<div class="span4">
-			<div wicket:id="gravatar"></div>
-			<div style="text-align: left;">
-				<h2><span wicket:id="userDisplayName"></span></h2>
-				<div><i class="icon-user"></i> <span wicket:id="userUsername"></span></div>
-				<div><i class="icon-envelope"></i><span wicket:id="userEmail"></span></div>
-			</div>
-		</div>
-		
-		<div class="span8">
-			<div class="pull-right">
-				<a class="btn-small" wicket:id="newRepository" style="padding-right:0px;">
-					<i class="icon icon-plus-sign"></i>
-					<wicket:message key="gb.newRepository"></wicket:message>
-				</a>
-			</div>
-			<div class="tabbable">
-				<!-- tab titles -->
-				<ul class="nav nav-tabs">
-					<li class="active"><a href="#repositories" data-toggle="tab"><wicket:message key="gb.repositories"></wicket:message></a></li>
-				</ul>
-	
-				<!-- tab content -->
-				<div class="tab-content">
-
-					<!-- repositories tab -->
-					<div class="tab-pane active" id="repositories">
-						<table width="100%">
-							<tbody>
-								<tr wicket:id="repositoryList"><td style="border-bottom:1px solid #eee;"><span wicket:id="repository"></span></td></tr>
-							</tbody>
-						</table>
-					</div>
-				</div>
-			</div>
-		</div>
-	</div>
-</wicket:extend>
-</body>
-</html>
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/pages/UsersPage.html b/src/com/gitblit/wicket/pages/UsersPage.html
deleted file mode 100644
index edb85f7..0000000
--- a/src/com/gitblit/wicket/pages/UsersPage.html
+++ /dev/null
@@ -1,13 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml"  
-      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"  
-      xml:lang="en"  
-      lang="en"> 
-<body>
-<wicket:extend>
-	<div wicket:id="teamsPanel">[teams panel]</div>
-
-	<div wicket:id="usersPanel">[users panel]</div>
-</wicket:extend>
-</body>
-</html>
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/pages/prettify/lang-apollo.js b/src/com/gitblit/wicket/pages/prettify/lang-apollo.js
deleted file mode 100644
index bfc0014..0000000
--- a/src/com/gitblit/wicket/pages/prettify/lang-apollo.js
+++ /dev/null
@@ -1,2 +0,0 @@
-PR.registerLangHandler(PR.createSimpleLexer([["com",/^#[^\r\n]*/,null,"#"],["pln",/^[\t\n\r \xA0]+/,null,"\t\n\r \u00a0"],["str",/^\"(?:[^\"\\]|\\[\s\S])*(?:\"|$)/,null,'"']],[["kwd",/^(?:ADS|AD|AUG|BZF|BZMF|CAE|CAF|CA|CCS|COM|CS|DAS|DCA|DCOM|DCS|DDOUBL|DIM|DOUBLE|DTCB|DTCF|DV|DXCH|EDRUPT|EXTEND|INCR|INDEX|NDX|INHINT|LXCH|MASK|MSK|MP|MSU|NOOP|OVSK|QXCH|RAND|READ|RELINT|RESUME|RETURN|ROR|RXOR|SQUARE|SU|TCR|TCAA|OVSK|TCF|TC|TS|WAND|WOR|WRITE|XCH|XLQ|XXALQ|ZL|ZQ|ADD|ADZ|SUB|SUZ|MPY|MPR|MPZ|DVP|COM|ABS|CLA|CLZ|LDQ|STO|STQ|ALS|LLS|LRS|TRA|TSQ|TMI|TOV|AXT|TIX|DLY|INP|OUT)\s/,
-null],["typ",/^(?:-?GENADR|=MINUS|2BCADR|VN|BOF|MM|-?2CADR|-?[1-6]DNADR|ADRES|BBCON|[SE]?BANK\=?|BLOCK|BNKSUM|E?CADR|COUNT\*?|2?DEC\*?|-?DNCHAN|-?DNPTR|EQUALS|ERASE|MEMORY|2?OCT|REMADR|SETLOC|SUBRO|ORG|BSS|BES|SYN|EQU|DEFINE|END)\s/,null],["lit",/^\'(?:-*(?:\w|\\[\x21-\x7e])(?:[\w-]*|\\[\x21-\x7e])[=!?]?)?/],["pln",/^-*(?:[!-z_]|\\[\x21-\x7e])(?:[\w-]*|\\[\x21-\x7e])[=!?]?/i],["pun",/^[^\w\t\n\r \xA0()\"\\\';]+/]]),["apollo","agc","aea"])
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/pages/prettify/lang-css.js b/src/com/gitblit/wicket/pages/prettify/lang-css.js
deleted file mode 100644
index 61157f3..0000000
--- a/src/com/gitblit/wicket/pages/prettify/lang-css.js
+++ /dev/null
@@ -1,2 +0,0 @@
-PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[ \t\r\n\f]+/,null," \t\r\n\u000c"]],[["str",/^\"(?:[^\n\r\f\\\"]|\\(?:\r\n?|\n|\f)|\\[\s\S])*\"/,null],["str",/^\'(?:[^\n\r\f\\\']|\\(?:\r\n?|\n|\f)|\\[\s\S])*\'/,null],["lang-css-str",/^url\(([^\)\"\']*)\)/i],["kwd",/^(?:url|rgb|\!important|@import|@page|@media|@charset|inherit)(?=[^\-\w]|$)/i,null],["lang-css-kw",/^(-?(?:[_a-z]|(?:\\[0-9a-f]+ ?))(?:[_a-z0-9\-]|\\(?:\\[0-9a-f]+ ?))*)\s*:/i],["com",/^\/\*[^*]*\*+(?:[^\/*][^*]*\*+)*\//],
-["com",/^(?:<!--|--\>)/],["lit",/^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i],["lit",/^#(?:[0-9a-f]{3}){1,2}/i],["pln",/^-?(?:[_a-z]|(?:\\[\da-f]+ ?))(?:[_a-z\d\-]|\\(?:\\[\da-f]+ ?))*/i],["pun",/^[^\s\w\'\"]+/]]),["css"]);PR.registerLangHandler(PR.createSimpleLexer([],[["kwd",/^-?(?:[_a-z]|(?:\\[\da-f]+ ?))(?:[_a-z\d\-]|\\(?:\\[\da-f]+ ?))*/i]]),["css-kw"]);PR.registerLangHandler(PR.createSimpleLexer([],[["str",/^[^\)\"\']+/]]),["css-str"])
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/pages/prettify/lang-hs.js b/src/com/gitblit/wicket/pages/prettify/lang-hs.js
deleted file mode 100644
index 00cea7c..0000000
--- a/src/com/gitblit/wicket/pages/prettify/lang-hs.js
+++ /dev/null
@@ -1,2 +0,0 @@
-PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\x0B\x0C\r ]+/,null,"\t\n\u000b\u000c\r "],["str",/^\"(?:[^\"\\\n\x0C\r]|\\[\s\S])*(?:\"|$)/,null,'"'],["str",/^\'(?:[^\'\\\n\x0C\r]|\\[^&])\'?/,null,"'"],["lit",/^(?:0o[0-7]+|0x[\da-f]+|\d+(?:\.\d+)?(?:e[+\-]?\d+)?)/i,null,"0123456789"]],[["com",/^(?:(?:--+(?:[^\r\n\x0C]*)?)|(?:\{-(?:[^-]|-+[^-\}])*-\}))/],["kwd",/^(?:case|class|data|default|deriving|do|else|if|import|in|infix|infixl|infixr|instance|let|module|newtype|of|then|type|where|_)(?=[^a-zA-Z0-9\']|$)/,
-null],["pln",/^(?:[A-Z][\w\']*\.)*[a-zA-Z][\w\']*/],["pun",/^[^\t\n\x0B\x0C\r a-zA-Z0-9\'\"]+/]]),["hs"])
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/pages/prettify/lang-lisp.js b/src/com/gitblit/wicket/pages/prettify/lang-lisp.js
deleted file mode 100644
index fab992b..0000000
--- a/src/com/gitblit/wicket/pages/prettify/lang-lisp.js
+++ /dev/null
@@ -1,2 +0,0 @@
-PR.registerLangHandler(PR.createSimpleLexer([["opn",/^\(/,null,"("],["clo",/^\)/,null,")"],["com",/^;[^\r\n]*/,null,";"],["pln",/^[\t\n\r \xA0]+/,null,"\t\n\r \u00a0"],["str",/^\"(?:[^\"\\]|\\[\s\S])*(?:\"|$)/,null,'"']],[["kwd",/^(?:block|c[ad]+r|catch|con[ds]|def(?:ine|un)|do|eq|eql|equal|equalp|eval-when|flet|format|go|if|labels|lambda|let|load-time-value|locally|macrolet|multiple-value-call|nil|progn|progv|quote|require|return-from|setq|symbol-macrolet|t|tagbody|the|throw|unwind)\b/,
-null],["lit",/^[+\-]?(?:0x[0-9a-f]+|\d+\/\d+|(?:\.\d+|\d+(?:\.\d*)?)(?:[ed][+\-]?\d+)?)/i],["lit",/^\'(?:-*(?:\w|\\[\x21-\x7e])(?:[\w-]*|\\[\x21-\x7e])[=!?]?)?/],["pln",/^-*(?:[a-z_]|\\[\x21-\x7e])(?:[\w-]*|\\[\x21-\x7e])[=!?]?/i],["pun",/^[^\w\t\n\r \xA0()\"\\\';]+/]]),["cl","el","lisp","scm"])
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/pages/prettify/lang-lua.js b/src/com/gitblit/wicket/pages/prettify/lang-lua.js
deleted file mode 100644
index 45d0ba2..0000000
--- a/src/com/gitblit/wicket/pages/prettify/lang-lua.js
+++ /dev/null
@@ -1,2 +0,0 @@
-PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\r \xA0]+/,null,"\t\n\r \u00a0"],["str",/^(?:\"(?:[^\"\\]|\\[\s\S])*(?:\"|$)|\'(?:[^\'\\]|\\[\s\S])*(?:\'|$))/,null,"\"'"]],[["com",/^--(?:\[(=*)\[[\s\S]*?(?:\]\1\]|$)|[^\r\n]*)/],["str",/^\[(=*)\[[\s\S]*?(?:\]\1\]|$)/],["kwd",/^(?:and|break|do|else|elseif|end|false|for|function|if|in|local|nil|not|or|repeat|return|then|true|until|while)\b/,null],["lit",/^[+-]?(?:0x[\da-f]+|(?:(?:\.\d+|\d+(?:\.\d*)?)(?:e[+\-]?\d+)?))/i],
-["pln",/^[a-z_]\w*/i],["pun",/^[^\w\t\n\r \xA0][^\w\t\n\r \xA0\"\'\-\+=]*/]]),["lua"])
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/pages/prettify/lang-ml.js b/src/com/gitblit/wicket/pages/prettify/lang-ml.js
deleted file mode 100644
index 5879726..0000000
--- a/src/com/gitblit/wicket/pages/prettify/lang-ml.js
+++ /dev/null
@@ -1,2 +0,0 @@
-PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\r \xA0]+/,null,"\t\n\r \u00a0"],["com",/^#(?:if[\t\n\r \xA0]+(?:[a-z_$][\w\']*|``[^\r\n\t`]*(?:``|$))|else|endif|light)/i,null,"#"],["str",/^(?:\"(?:[^\"\\]|\\[\s\S])*(?:\"|$)|\'(?:[^\'\\]|\\[\s\S])*(?:\'|$))/,null,"\"'"]],[["com",/^(?:\/\/[^\r\n]*|\(\*[\s\S]*?\*\))/],["kwd",/^(?:abstract|and|as|assert|begin|class|default|delegate|do|done|downcast|downto|elif|else|end|exception|extern|false|finally|for|fun|function|if|in|inherit|inline|interface|internal|lazy|let|match|member|module|mutable|namespace|new|null|of|open|or|override|private|public|rec|return|static|struct|then|to|true|try|type|upcast|use|val|void|when|while|with|yield|asr|land|lor|lsl|lsr|lxor|mod|sig|atomic|break|checked|component|const|constraint|constructor|continue|eager|event|external|fixed|functor|global|include|method|mixin|object|parallel|process|protected|pure|sealed|trait|virtual|volatile)\b/],
-["lit",/^[+\-]?(?:0x[\da-f]+|(?:(?:\.\d+|\d+(?:\.\d*)?)(?:e[+\-]?\d+)?))/i],["pln",/^(?:[a-z_]\w*[!?#]?|``[^\r\n\t`]*(?:``|$))/i],["pun",/^[^\t\n\r \xA0\"\'\w]+/]]),["fs","ml"])
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/pages/prettify/lang-proto.js b/src/com/gitblit/wicket/pages/prettify/lang-proto.js
deleted file mode 100644
index f713420..0000000
--- a/src/com/gitblit/wicket/pages/prettify/lang-proto.js
+++ /dev/null
@@ -1 +0,0 @@
-PR.registerLangHandler(PR.sourceDecorator({keywords:"bool bytes default double enum extend extensions false fixed32 fixed64 float group import int32 int64 max message option optional package repeated required returns rpc service sfixed32 sfixed64 sint32 sint64 string syntax to true uint32 uint64",cStyleComments:true}),["proto"])
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/pages/prettify/lang-scala.js b/src/com/gitblit/wicket/pages/prettify/lang-scala.js
deleted file mode 100644
index 00f4e0c..0000000
--- a/src/com/gitblit/wicket/pages/prettify/lang-scala.js
+++ /dev/null
@@ -1,2 +0,0 @@
-PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\r \xA0]+/,null,"\t\n\r \u00a0"],["str",/^(?:"(?:(?:""(?:""?(?!")|[^\\"]|\\.)*"{0,3})|(?:[^"\r\n\\]|\\.)*"?))/,null,'"'],["lit",/^`(?:[^\r\n\\`]|\\.)*`?/,null,"`"],["pun",/^[!#%&()*+,\-:;<=>?@\[\\\]^{|}~]+/,null,"!#%&()*+,-:;<=>?@[\\]^{|}~"]],[["str",/^'(?:[^\r\n\\']|\\(?:'|[^\r\n']+))'/],["lit",/^'[a-zA-Z_$][\w$]*(?!['$\w])/],["kwd",/^(?:abstract|case|catch|class|def|do|else|extends|final|finally|for|forSome|if|implicit|import|lazy|match|new|object|override|package|private|protected|requires|return|sealed|super|throw|trait|try|type|val|var|while|with|yield)\b/],
-["lit",/^(?:true|false|null|this)\b/],["lit",/^(?:(?:0(?:[0-7]+|X[0-9A-F]+))L?|(?:(?:0|[1-9][0-9]*)(?:(?:\.[0-9]+)?(?:E[+\-]?[0-9]+)?F?|L?))|\\.[0-9]+(?:E[+\-]?[0-9]+)?F?)/i],["typ",/^[$_]*[A-Z][_$A-Z0-9]*[a-z][\w$]*/],["pln",/^[$a-zA-Z_][\w$]*/],["com",/^\/(?:\/.*|\*(?:\/|\**[^*/])*(?:\*+\/?)?)/],["pun",/^(?:\.+|\/)/]]),["scala"])
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/pages/prettify/lang-sql.js b/src/com/gitblit/wicket/pages/prettify/lang-sql.js
deleted file mode 100644
index 800b13e..0000000
--- a/src/com/gitblit/wicket/pages/prettify/lang-sql.js
+++ /dev/null
@@ -1,2 +0,0 @@
-PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\r \xA0]+/,null,"\t\n\r \u00a0"],["str",/^(?:"(?:[^\"\\]|\\.)*"|'(?:[^\'\\]|\\.)*')/,null,"\"'"]],[["com",/^(?:--[^\r\n]*|\/\*[\s\S]*?(?:\*\/|$))/],["kwd",/^(?:ADD|ALL|ALTER|AND|ANY|AS|ASC|AUTHORIZATION|BACKUP|BEGIN|BETWEEN|BREAK|BROWSE|BULK|BY|CASCADE|CASE|CHECK|CHECKPOINT|CLOSE|CLUSTERED|COALESCE|COLLATE|COLUMN|COMMIT|COMPUTE|CONSTRAINT|CONTAINS|CONTAINSTABLE|CONTINUE|CONVERT|CREATE|CROSS|CURRENT|CURRENT_DATE|CURRENT_TIME|CURRENT_TIMESTAMP|CURRENT_USER|CURSOR|DATABASE|DBCC|DEALLOCATE|DECLARE|DEFAULT|DELETE|DENY|DESC|DISK|DISTINCT|DISTRIBUTED|DOUBLE|DROP|DUMMY|DUMP|ELSE|END|ERRLVL|ESCAPE|EXCEPT|EXEC|EXECUTE|EXISTS|EXIT|FETCH|FILE|FILLFACTOR|FOR|FOREIGN|FREETEXT|FREETEXTTABLE|FROM|FULL|FUNCTION|GOTO|GRANT|GROUP|HAVING|HOLDLOCK|IDENTITY|IDENTITYCOL|IDENTITY_INSERT|IF|IN|INDEX|INNER|INSERT|INTERSECT|INTO|IS|JOIN|KEY|KILL|LEFT|LIKE|LINENO|LOAD|NATIONAL|NOCHECK|NONCLUSTERED|NOT|NULL|NULLIF|OF|OFF|OFFSETS|ON|OPEN|OPENDATASOURCE|OPENQUERY|OPENROWSET|OPENXML|OPTION|OR|ORDER|OUTER|OVER|PERCENT|PLAN|PRECISION|PRIMARY|PRINT|PROC|PROCEDURE|PUBLIC|RAISERROR|READ|READTEXT|RECONFIGURE|REFERENCES|REPLICATION|RESTORE|RESTRICT|RETURN|REVOKE|RIGHT|ROLLBACK|ROWCOUNT|ROWGUIDCOL|RULE|SAVE|SCHEMA|SELECT|SESSION_USER|SET|SETUSER|SHUTDOWN|SOME|STATISTICS|SYSTEM_USER|TABLE|TEXTSIZE|THEN|TO|TOP|TRAN|TRANSACTION|TRIGGER|TRUNCATE|TSEQUAL|UNION|UNIQUE|UPDATE|UPDATETEXT|USE|USER|VALUES|VARYING|VIEW|WAITFOR|WHEN|WHERE|WHILE|WITH|WRITETEXT)(?=[^\w-]|$)/i,
-null],["lit",/^[+-]?(?:0x[\da-f]+|(?:(?:\.\d+|\d+(?:\.\d*)?)(?:e[+\-]?\d+)?))/i],["pln",/^[a-z_][\w-]*/i],["pun",/^[^\w\t\n\r \xA0\"\'][^\w\t\n\r \xA0+\-\"\']*/]]),["sql"])
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/pages/prettify/lang-vb.js b/src/com/gitblit/wicket/pages/prettify/lang-vb.js
deleted file mode 100644
index c479c11..0000000
--- a/src/com/gitblit/wicket/pages/prettify/lang-vb.js
+++ /dev/null
@@ -1,2 +0,0 @@
-PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\r \xA0\u2028\u2029]+/,null,"\t\n\r \u00a0\u2028\u2029"],["str",/^(?:[\"\u201C\u201D](?:[^\"\u201C\u201D]|[\"\u201C\u201D]{2})(?:[\"\u201C\u201D]c|$)|[\"\u201C\u201D](?:[^\"\u201C\u201D]|[\"\u201C\u201D]{2})*(?:[\"\u201C\u201D]|$))/i,null,'"\u201c\u201d'],["com",/^[\'\u2018\u2019][^\r\n\u2028\u2029]*/,null,"'\u2018\u2019"]],[["kwd",/^(?:AddHandler|AddressOf|Alias|And|AndAlso|Ansi|As|Assembly|Auto|Boolean|ByRef|Byte|ByVal|Call|Case|Catch|CBool|CByte|CChar|CDate|CDbl|CDec|Char|CInt|Class|CLng|CObj|Const|CShort|CSng|CStr|CType|Date|Decimal|Declare|Default|Delegate|Dim|DirectCast|Do|Double|Each|Else|ElseIf|End|EndIf|Enum|Erase|Error|Event|Exit|Finally|For|Friend|Function|Get|GetType|GoSub|GoTo|Handles|If|Implements|Imports|In|Inherits|Integer|Interface|Is|Let|Lib|Like|Long|Loop|Me|Mod|Module|MustInherit|MustOverride|MyBase|MyClass|Namespace|New|Next|Not|NotInheritable|NotOverridable|Object|On|Option|Optional|Or|OrElse|Overloads|Overridable|Overrides|ParamArray|Preserve|Private|Property|Protected|Public|RaiseEvent|ReadOnly|ReDim|RemoveHandler|Resume|Return|Select|Set|Shadows|Shared|Short|Single|Static|Step|Stop|String|Structure|Sub|SyncLock|Then|Throw|To|Try|TypeOf|Unicode|Until|Variant|Wend|When|While|With|WithEvents|WriteOnly|Xor|EndIf|GoSub|Let|Variant|Wend)\b/i,
-null],["com",/^REM[^\r\n\u2028\u2029]*/i],["lit",/^(?:True\b|False\b|Nothing\b|\d+(?:E[+\-]?\d+[FRD]?|[FRDSIL])?|(?:&H[0-9A-F]+|&O[0-7]+)[SIL]?|\d*\.\d+(?:E[+\-]?\d+)?[FRD]?|#\s+(?:\d+[\-\/]\d+[\-\/]\d+(?:\s+\d+:\d+(?::\d+)?(\s*(?:AM|PM))?)?|\d+:\d+(?::\d+)?(\s*(?:AM|PM))?)\s+#)/i],["pln",/^(?:(?:[a-z]|_\w)\w*|\[(?:[a-z]|_\w)\w*\])/i],["pun",/^[^\w\t\n\r \"\'\[\]\xA0\u2018\u2019\u201C\u201D\u2028\u2029]+/],["pun",/^(?:\[|\])/]]),["vb","vbs"])
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/pages/prettify/lang-vhdl.js b/src/com/gitblit/wicket/pages/prettify/lang-vhdl.js
deleted file mode 100644
index dc81a3f..0000000
--- a/src/com/gitblit/wicket/pages/prettify/lang-vhdl.js
+++ /dev/null
@@ -1,3 +0,0 @@
-PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\r \xA0]+/,null,"\t\n\r \u00a0"]],[["str",/^(?:[BOX]?"(?:[^\"]|"")*"|'.')/i],["com",/^--[^\r\n]*/],["kwd",/^(?:abs|access|after|alias|all|and|architecture|array|assert|attribute|begin|block|body|buffer|bus|case|component|configuration|constant|disconnect|downto|else|elsif|end|entity|exit|file|for|function|generate|generic|group|guarded|if|impure|in|inertial|inout|is|label|library|linkage|literal|loop|map|mod|nand|new|next|nor|not|null|of|on|open|or|others|out|package|port|postponed|procedure|process|pure|range|record|register|reject|rem|report|return|rol|ror|select|severity|shared|signal|sla|sll|sra|srl|subtype|then|to|transport|type|unaffected|units|until|use|variable|wait|when|while|with|xnor|xor)(?=[^\w-]|$)/i,
-null],["typ",/^(?:bit|bit_vector|character|boolean|integer|real|time|string|severity_level|positive|natural|signed|unsigned|line|text|std_u?logic(?:_vector)?)(?=[^\w-]|$)/i,null],["typ",/^\'(?:ACTIVE|ASCENDING|BASE|DELAYED|DRIVING|DRIVING_VALUE|EVENT|HIGH|IMAGE|INSTANCE_NAME|LAST_ACTIVE|LAST_EVENT|LAST_VALUE|LEFT|LEFTOF|LENGTH|LOW|PATH_NAME|POS|PRED|QUIET|RANGE|REVERSE_RANGE|RIGHT|RIGHTOF|SIMPLE_NAME|STABLE|SUCC|TRANSACTION|VAL|VALUE)(?=[^\w-]|$)/i,null],["lit",/^\d+(?:_\d+)*(?:#[\w\\.]+#(?:[+\-]?\d+(?:_\d+)*)?|(?:\.\d+(?:_\d+)*)?(?:E[+\-]?\d+(?:_\d+)*)?)/i],
-["pln",/^(?:[a-z]\w*|\\[^\\]*\\)/i],["pun",/^[^\w\t\n\r \xA0\"\'][^\w\t\n\r \xA0\-\"\']*/]]),["vhdl","vhd"])
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/pages/prettify/lang-wiki.js b/src/com/gitblit/wicket/pages/prettify/lang-wiki.js
deleted file mode 100644
index 3b8fb50..0000000
--- a/src/com/gitblit/wicket/pages/prettify/lang-wiki.js
+++ /dev/null
@@ -1,2 +0,0 @@
-PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t \xA0a-gi-z0-9]+/,null,"\t \u00a0abcdefgijklmnopqrstuvwxyz0123456789"],["pun",/^[=*~\^\[\]]+/,null,"=*~^[]"]],[["lang-wiki.meta",/(?:^^|\r\n?|\n)(#[a-z]+)\b/],["lit",/^(?:[A-Z][a-z][a-z0-9]+[A-Z][a-z][a-zA-Z0-9]+)\b/],["lang-",/^\{\{\{([\s\S]+?)\}\}\}/],["lang-",/^`([^\r\n`]+)`/],["str",/^https?:\/\/[^\/?#\s]*(?:\/[^?#\s]*)?(?:\?[^#\s]*)?(?:#\S*)?/i],["pln",/^(?:\r\n|[\s\S])[^#=*~^A-Zh\{`\[\r\n]*/]]),["wiki"]);
-PR.registerLangHandler(PR.createSimpleLexer([["kwd",/^#[a-z]+/i,null,"#"]],[]),["wiki.meta"])
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/pages/prettify/lang-yaml.js b/src/com/gitblit/wicket/pages/prettify/lang-yaml.js
deleted file mode 100644
index f2f3607..0000000
--- a/src/com/gitblit/wicket/pages/prettify/lang-yaml.js
+++ /dev/null
@@ -1,2 +0,0 @@
-PR.registerLangHandler(PR.createSimpleLexer([["pun",/^[:|>?]+/,null,":|>?"],["dec",/^%(?:YAML|TAG)[^#\r\n]+/,null,"%"],["typ",/^[&]\S+/,null,"&"],["typ",/^!\S*/,null,"!"],["str",/^"(?:[^\\"]|\\.)*(?:"|$)/,null,'"'],["str",/^'(?:[^']|'')*(?:'|$)/,null,"'"],["com",/^#[^\r\n]*/,null,"#"],["pln",/^\s+/,null," \t\r\n"]],[["dec",/^(?:---|\.\.\.)(?:[\r\n]|$)/],["pun",/^-/],["kwd",/^\w+:[ \r\n]/],["pln",/^\w+/]]),
-["yaml","yml"])
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/pages/prettify/prettify.css b/src/com/gitblit/wicket/pages/prettify/prettify.css
deleted file mode 100644
index 2925d13..0000000
--- a/src/com/gitblit/wicket/pages/prettify/prettify.css
+++ /dev/null
@@ -1 +0,0 @@
-.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun{color:#660}.pln{color:#000}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec{color:#606}pre.prettyprint{padding:2px;border:1px solid #888}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style:none}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee}@media print{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun{color:#440}.pln{color:#000}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/pages/prettify/prettify.js b/src/com/gitblit/wicket/pages/prettify/prettify.js
deleted file mode 100644
index c9161da..0000000
--- a/src/com/gitblit/wicket/pages/prettify/prettify.js
+++ /dev/null
@@ -1,33 +0,0 @@
-window.PR_SHOULD_USE_CONTINUATION=true;window.PR_TAB_WIDTH=8;window.PR_normalizedHtml=window.PR=window.prettyPrintOne=window.prettyPrint=void 0;window._pr_isIE6=function(){var y=navigator&&navigator.userAgent&&navigator.userAgent.match(/\bMSIE ([678])\./);y=y?+y[1]:false;window._pr_isIE6=function(){return y};return y};
-(function(){function y(b){return b.replace(L,"&amp;").replace(M,"&lt;").replace(N,"&gt;")}function H(b,f,i){switch(b.nodeType){case 1:var o=b.tagName.toLowerCase();f.push("<",o);var l=b.attributes,n=l.length;if(n){if(i){for(var r=[],j=n;--j>=0;)r[j]=l[j];r.sort(function(q,m){return q.name<m.name?-1:q.name===m.name?0:1});l=r}for(j=0;j<n;++j){r=l[j];r.specified&&f.push(" ",r.name.toLowerCase(),'="',r.value.replace(L,"&amp;").replace(M,"&lt;").replace(N,"&gt;").replace(X,"&quot;"),'"')}}f.push(">");
-for(l=b.firstChild;l;l=l.nextSibling)H(l,f,i);if(b.firstChild||!/^(?:br|link|img)$/.test(o))f.push("</",o,">");break;case 3:case 4:f.push(y(b.nodeValue));break}}function O(b){function f(c){if(c.charAt(0)!=="\\")return c.charCodeAt(0);switch(c.charAt(1)){case "b":return 8;case "t":return 9;case "n":return 10;case "v":return 11;case "f":return 12;case "r":return 13;case "u":case "x":return parseInt(c.substring(2),16)||c.charCodeAt(1);case "0":case "1":case "2":case "3":case "4":case "5":case "6":case "7":return parseInt(c.substring(1),
-8);default:return c.charCodeAt(1)}}function i(c){if(c<32)return(c<16?"\\x0":"\\x")+c.toString(16);c=String.fromCharCode(c);if(c==="\\"||c==="-"||c==="["||c==="]")c="\\"+c;return c}function o(c){var d=c.substring(1,c.length-1).match(RegExp("\\\\u[0-9A-Fa-f]{4}|\\\\x[0-9A-Fa-f]{2}|\\\\[0-3][0-7]{0,2}|\\\\[0-7]{1,2}|\\\\[\\s\\S]|-|[^-\\\\]","g"));c=[];for(var a=[],k=d[0]==="^",e=k?1:0,h=d.length;e<h;++e){var g=d[e];switch(g){case "\\B":case "\\b":case "\\D":case "\\d":case "\\S":case "\\s":case "\\W":case "\\w":c.push(g);
-continue}g=f(g);var s;if(e+2<h&&"-"===d[e+1]){s=f(d[e+2]);e+=2}else s=g;a.push([g,s]);if(!(s<65||g>122)){s<65||g>90||a.push([Math.max(65,g)|32,Math.min(s,90)|32]);s<97||g>122||a.push([Math.max(97,g)&-33,Math.min(s,122)&-33])}}a.sort(function(v,w){return v[0]-w[0]||w[1]-v[1]});d=[];g=[NaN,NaN];for(e=0;e<a.length;++e){h=a[e];if(h[0]<=g[1]+1)g[1]=Math.max(g[1],h[1]);else d.push(g=h)}a=["["];k&&a.push("^");a.push.apply(a,c);for(e=0;e<d.length;++e){h=d[e];a.push(i(h[0]));if(h[1]>h[0]){h[1]+1>h[0]&&a.push("-");
-a.push(i(h[1]))}}a.push("]");return a.join("")}function l(c){for(var d=c.source.match(RegExp("(?:\\[(?:[^\\x5C\\x5D]|\\\\[\\s\\S])*\\]|\\\\u[A-Fa-f0-9]{4}|\\\\x[A-Fa-f0-9]{2}|\\\\[0-9]+|\\\\[^ux0-9]|\\(\\?[:!=]|[\\(\\)\\^]|[^\\x5B\\x5C\\(\\)\\^]+)","g")),a=d.length,k=[],e=0,h=0;e<a;++e){var g=d[e];if(g==="(")++h;else if("\\"===g.charAt(0))if((g=+g.substring(1))&&g<=h)k[g]=-1}for(e=1;e<k.length;++e)if(-1===k[e])k[e]=++n;for(h=e=0;e<a;++e){g=d[e];if(g==="("){++h;if(k[h]===undefined)d[e]="(?:"}else if("\\"===
-g.charAt(0))if((g=+g.substring(1))&&g<=h)d[e]="\\"+k[h]}for(h=e=0;e<a;++e)if("^"===d[e]&&"^"!==d[e+1])d[e]="";if(c.ignoreCase&&r)for(e=0;e<a;++e){g=d[e];c=g.charAt(0);if(g.length>=2&&c==="[")d[e]=o(g);else if(c!=="\\")d[e]=g.replace(/[a-zA-Z]/g,function(s){s=s.charCodeAt(0);return"["+String.fromCharCode(s&-33,s|32)+"]"})}return d.join("")}for(var n=0,r=false,j=false,q=0,m=b.length;q<m;++q){var t=b[q];if(t.ignoreCase)j=true;else if(/[a-z]/i.test(t.source.replace(/\\u[0-9a-f]{4}|\\x[0-9a-f]{2}|\\[^ux]/gi,
-""))){r=true;j=false;break}}var p=[];q=0;for(m=b.length;q<m;++q){t=b[q];if(t.global||t.multiline)throw Error(""+t);p.push("(?:"+l(t)+")")}return RegExp(p.join("|"),j?"gi":"g")}function Y(b){var f=0;return function(i){for(var o=null,l=0,n=0,r=i.length;n<r;++n)switch(i.charAt(n)){case "\t":o||(o=[]);o.push(i.substring(l,n));l=b-f%b;for(f+=l;l>=0;l-=16)o.push("                ".substring(0,l));l=n+1;break;case "\n":f=0;break;default:++f}if(!o)return i;o.push(i.substring(l));return o.join("")}}function I(b,
-f,i,o){if(f){b={source:f,c:b};i(b);o.push.apply(o,b.d)}}function B(b,f){var i={},o;(function(){for(var r=b.concat(f),j=[],q={},m=0,t=r.length;m<t;++m){var p=r[m],c=p[3];if(c)for(var d=c.length;--d>=0;)i[c.charAt(d)]=p;p=p[1];c=""+p;if(!q.hasOwnProperty(c)){j.push(p);q[c]=null}}j.push(/[\0-\uffff]/);o=O(j)})();var l=f.length;function n(r){for(var j=r.c,q=[j,z],m=0,t=r.source.match(o)||[],p={},c=0,d=t.length;c<d;++c){var a=t[c],k=p[a],e=void 0,h;if(typeof k==="string")h=false;else{var g=i[a.charAt(0)];
-if(g){e=a.match(g[1]);k=g[0]}else{for(h=0;h<l;++h){g=f[h];if(e=a.match(g[1])){k=g[0];break}}e||(k=z)}if((h=k.length>=5&&"lang-"===k.substring(0,5))&&!(e&&typeof e[1]==="string")){h=false;k=P}h||(p[a]=k)}g=m;m+=a.length;if(h){h=e[1];var s=a.indexOf(h),v=s+h.length;if(e[2]){v=a.length-e[2].length;s=v-h.length}k=k.substring(5);I(j+g,a.substring(0,s),n,q);I(j+g+s,h,Q(k,h),q);I(j+g+v,a.substring(v),n,q)}else q.push(j+g,k)}r.d=q}return n}function x(b){var f=[],i=[];if(b.tripleQuotedStrings)f.push([A,/^(?:\'\'\'(?:[^\'\\]|\\[\s\S]|\'{1,2}(?=[^\']))*(?:\'\'\'|$)|\"\"\"(?:[^\"\\]|\\[\s\S]|\"{1,2}(?=[^\"]))*(?:\"\"\"|$)|\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$))/,
-null,"'\""]);else b.multiLineStrings?f.push([A,/^(?:\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$)|\`(?:[^\\\`]|\\[\s\S])*(?:\`|$))/,null,"'\"`"]):f.push([A,/^(?:\'(?:[^\\\'\r\n]|\\.)*(?:\'|$)|\"(?:[^\\\"\r\n]|\\.)*(?:\"|$))/,null,"\"'"]);b.verbatimStrings&&i.push([A,/^@\"(?:[^\"]|\"\")*(?:\"|$)/,null]);if(b.hashComments)if(b.cStyleComments){f.push([C,/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\r\n]*)/,null,"#"]);i.push([A,/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,
-null])}else f.push([C,/^#[^\r\n]*/,null,"#"]);if(b.cStyleComments){i.push([C,/^\/\/[^\r\n]*/,null]);i.push([C,/^\/\*[\s\S]*?(?:\*\/|$)/,null])}b.regexLiterals&&i.push(["lang-regex",RegExp("^"+Z+"(/(?=[^/*])(?:[^/\\x5B\\x5C]|\\x5C[\\s\\S]|\\x5B(?:[^\\x5C\\x5D]|\\x5C[\\s\\S])*(?:\\x5D|$))+/)")]);b=b.keywords.replace(/^\s+|\s+$/g,"");b.length&&i.push([R,RegExp("^(?:"+b.replace(/\s+/g,"|")+")\\b"),null]);f.push([z,/^\s+/,null," \r\n\t\u00a0"]);i.push([J,/^@[a-z_$][a-z_$@0-9]*/i,null],[S,/^@?[A-Z]+[a-z][A-Za-z_$@0-9]*/,
-null],[z,/^[a-z_$][a-z_$@0-9]*/i,null],[J,/^(?:0x[a-f0-9]+|(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d\+)(?:e[+\-]?\d+)?)[a-z]*/i,null,"0123456789"],[E,/^.[^\s\w\.$@\'\"\`\/\#]*/,null]);return B(f,i)}function $(b){function f(D){if(D>r){if(j&&j!==q){n.push("</span>");j=null}if(!j&&q){j=q;n.push('<span class="',j,'">')}var T=y(p(i.substring(r,D))).replace(e?d:c,"$1&#160;");e=k.test(T);n.push(T.replace(a,s));r=D}}var i=b.source,o=b.g,l=b.d,n=[],r=0,j=null,q=null,m=0,t=0,p=Y(window.PR_TAB_WIDTH),c=/([\r\n ]) /g,
-d=/(^| ) /gm,a=/\r\n?|\n/g,k=/[ \r\n]$/,e=true,h=window._pr_isIE6();h=h?b.b.tagName==="PRE"?h===6?"&#160;\r\n":h===7?"&#160;<br>\r":"&#160;\r":"&#160;<br />":"<br />";var g=b.b.className.match(/\blinenums\b(?::(\d+))?/),s;if(g){for(var v=[],w=0;w<10;++w)v[w]=h+'</li><li class="L'+w+'">';var F=g[1]&&g[1].length?g[1]-1:0;n.push('<ol class="linenums"><li class="L',F%10,'"');F&&n.push(' value="',F+1,'"');n.push(">");s=function(){var D=v[++F%10];return j?"</span>"+D+'<span class="'+j+'">':D}}else s=h;
-for(;;)if(m<o.length?t<l.length?o[m]<=l[t]:true:false){f(o[m]);if(j){n.push("</span>");j=null}n.push(o[m+1]);m+=2}else if(t<l.length){f(l[t]);q=l[t+1];t+=2}else break;f(i.length);j&&n.push("</span>");g&&n.push("</li></ol>");b.a=n.join("")}function u(b,f){for(var i=f.length;--i>=0;){var o=f[i];if(G.hasOwnProperty(o))"console"in window&&console.warn("cannot override language handler %s",o);else G[o]=b}}function Q(b,f){b&&G.hasOwnProperty(b)||(b=/^\s*</.test(f)?"default-markup":"default-code");return G[b]}
-function U(b){var f=b.f,i=b.e;b.a=f;try{var o,l=f.match(aa);f=[];var n=0,r=[];if(l)for(var j=0,q=l.length;j<q;++j){var m=l[j];if(m.length>1&&m.charAt(0)==="<"){if(!ba.test(m))if(ca.test(m)){f.push(m.substring(9,m.length-3));n+=m.length-12}else if(da.test(m)){f.push("\n");++n}else if(m.indexOf(V)>=0&&m.replace(/\s(\w+)\s*=\s*(?:\"([^\"]*)\"|'([^\']*)'|(\S+))/g,' $1="$2$3$4"').match(/[cC][lL][aA][sS][sS]=\"[^\"]*\bnocode\b/)){var t=m.match(W)[2],p=1,c;c=j+1;a:for(;c<q;++c){var d=l[c].match(W);if(d&&
-d[2]===t)if(d[1]==="/"){if(--p===0)break a}else++p}if(c<q){r.push(n,l.slice(j,c+1).join(""));j=c}else r.push(n,m)}else r.push(n,m)}else{var a;p=m;var k=p.indexOf("&");if(k<0)a=p;else{for(--k;(k=p.indexOf("&#",k+1))>=0;){var e=p.indexOf(";",k);if(e>=0){var h=p.substring(k+3,e),g=10;if(h&&h.charAt(0)==="x"){h=h.substring(1);g=16}var s=parseInt(h,g);isNaN(s)||(p=p.substring(0,k)+String.fromCharCode(s)+p.substring(e+1))}}a=p.replace(ea,"<").replace(fa,">").replace(ga,"'").replace(ha,'"').replace(ia," ").replace(ja,
-"&")}f.push(a);n+=a.length}}o={source:f.join(""),h:r};var v=o.source;b.source=v;b.c=0;b.g=o.h;Q(i,v)(b);$(b)}catch(w){if("console"in window)console.log(w&&w.stack?w.stack:w)}}var A="str",R="kwd",C="com",S="typ",J="lit",E="pun",z="pln",P="src",V="nocode",Z=function(){for(var b=["!","!=","!==","#","%","%=","&","&&","&&=","&=","(","*","*=","+=",",","-=","->","/","/=",":","::",";","<","<<","<<=","<=","=","==","===",">",">=",">>",">>=",">>>",">>>=","?","@","[","^","^=","^^","^^=","{","|","|=","||","||=",
-"~","break","case","continue","delete","do","else","finally","instanceof","return","throw","try","typeof"],f="(?:^^|[+-]",i=0;i<b.length;++i)f+="|"+b[i].replace(/([^=<>:&a-z])/g,"\\$1");f+=")\\s*";return f}(),L=/&/g,M=/</g,N=/>/g,X=/\"/g,ea=/&lt;/g,fa=/&gt;/g,ga=/&apos;/g,ha=/&quot;/g,ja=/&amp;/g,ia=/&nbsp;/g,ka=/[\r\n]/g,K=null,aa=RegExp("[^<]+|<!--[\\s\\S]*?--\>|<!\\[CDATA\\[[\\s\\S]*?\\]\\]>|</?[a-zA-Z](?:[^>\"']|'[^']*'|\"[^\"]*\")*>|<","g"),ba=/^<\!--/,ca=/^<!\[CDATA\[/,da=/^<br\b/i,W=/^<(\/?)([a-zA-Z][a-zA-Z0-9]*)/,
-la=x({keywords:"break continue do else for if return while auto case char const default double enum extern float goto int long register short signed sizeof static struct switch typedef union unsigned void volatile catch class delete false import new operator private protected public this throw true try typeof alignof align_union asm axiom bool concept concept_map const_cast constexpr decltype dynamic_cast explicit export friend inline late_check mutable namespace nullptr reinterpret_cast static_assert static_cast template typeid typename using virtual wchar_t where break continue do else for if return while auto case char const default double enum extern float goto int long register short signed sizeof static struct switch typedef union unsigned void volatile catch class delete false import new operator private protected public this throw true try typeof abstract boolean byte extends final finally implements import instanceof null native package strictfp super synchronized throws transient as base by checked decimal delegate descending event fixed foreach from group implicit in interface internal into is lock object out override orderby params partial readonly ref sbyte sealed stackalloc string select uint ulong unchecked unsafe ushort var break continue do else for if return while auto case char const default double enum extern float goto int long register short signed sizeof static struct switch typedef union unsigned void volatile catch class delete false import new operator private protected public this throw true try typeof debugger eval export function get null set undefined var with Infinity NaN caller delete die do dump elsif eval exit foreach for goto if import last local my next no our print package redo require sub undef unless until use wantarray while BEGIN END break continue do else for if return while and as assert class def del elif except exec finally from global import in is lambda nonlocal not or pass print raise try with yield False True None break continue do else for if return while alias and begin case class def defined elsif end ensure false in module next nil not or redo rescue retry self super then true undef unless until when yield BEGIN END break continue do else for if return while case done elif esac eval fi function in local set then until ",
-hashComments:true,cStyleComments:true,multiLineStrings:true,regexLiterals:true}),G={};u(la,["default-code"]);u(B([],[[z,/^[^<?]+/],["dec",/^<!\w[^>]*(?:>|$)/],[C,/^<\!--[\s\S]*?(?:-\->|$)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],[E,/^(?:<[%?]|[%?]>)/],["lang-",/^<xmp\b[^>]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-js",/^<script\b[^>]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-css",/^<style\b[^>]*>([\s\S]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]),["default-markup",
-"htm","html","mxml","xhtml","xml","xsl"]);u(B([[z,/^[\s]+/,null," \t\r\n"],["atv",/^(?:\"[^\"]*\"?|\'[^\']*\'?)/,null,"\"'"]],[["tag",/^^<\/?[a-z](?:[\w.:-]*\w)?|\/?>$/i],["atn",/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^>\'\"\s]*(?:[^>\'\"\s\/]|\/(?=\s)))/],[E,/^[=<>\/]+/],["lang-js",/^on\w+\s*=\s*\"([^\"]+)\"/i],["lang-js",/^on\w+\s*=\s*\'([^\']+)\'/i],["lang-js",/^on\w+\s*=\s*([^\"\'>\s]+)/i],["lang-css",/^style\s*=\s*\"([^\"]+)\"/i],["lang-css",/^style\s*=\s*\'([^\']+)\'/i],
-["lang-css",/^style\s*=\s*([^\"\'>\s]+)/i]]),["in.tag"]);u(B([],[["atv",/^[\s\S]+/]]),["uq.val"]);u(x({keywords:"break continue do else for if return while auto case char const default double enum extern float goto int long register short signed sizeof static struct switch typedef union unsigned void volatile catch class delete false import new operator private protected public this throw true try typeof alignof align_union asm axiom bool concept concept_map const_cast constexpr decltype dynamic_cast explicit export friend inline late_check mutable namespace nullptr reinterpret_cast static_assert static_cast template typeid typename using virtual wchar_t where ",
-hashComments:true,cStyleComments:true}),["c","cc","cpp","cxx","cyc","m"]);u(x({keywords:"null true false"}),["json"]);u(x({keywords:"break continue do else for if return while auto case char const default double enum extern float goto int long register short signed sizeof static struct switch typedef union unsigned void volatile catch class delete false import new operator private protected public this throw true try typeof abstract boolean byte extends final finally implements import instanceof null native package strictfp super synchronized throws transient as base by checked decimal delegate descending event fixed foreach from group implicit in interface internal into is lock object out override orderby params partial readonly ref sbyte sealed stackalloc string select uint ulong unchecked unsafe ushort var ",
-hashComments:true,cStyleComments:true,verbatimStrings:true}),["cs"]);u(x({keywords:"break continue do else for if return while auto case char const default double enum extern float goto int long register short signed sizeof static struct switch typedef union unsigned void volatile catch class delete false import new operator private protected public this throw true try typeof abstract boolean byte extends final finally implements import instanceof null native package strictfp super synchronized throws transient ",
-cStyleComments:true}),["java"]);u(x({keywords:"break continue do else for if return while case done elif esac eval fi function in local set then until ",hashComments:true,multiLineStrings:true}),["bsh","csh","sh"]);u(x({keywords:"break continue do else for if return while and as assert class def del elif except exec finally from global import in is lambda nonlocal not or pass print raise try with yield False True None ",hashComments:true,multiLineStrings:true,tripleQuotedStrings:true}),["cv","py"]);
-u(x({keywords:"caller delete die do dump elsif eval exit foreach for goto if import last local my next no our print package redo require sub undef unless until use wantarray while BEGIN END ",hashComments:true,multiLineStrings:true,regexLiterals:true}),["perl","pl","pm"]);u(x({keywords:"break continue do else for if return while alias and begin case class def defined elsif end ensure false in module next nil not or redo rescue retry self super then true undef unless until when yield BEGIN END ",hashComments:true,
-multiLineStrings:true,regexLiterals:true}),["rb"]);u(x({keywords:"break continue do else for if return while auto case char const default double enum extern float goto int long register short signed sizeof static struct switch typedef union unsigned void volatile catch class delete false import new operator private protected public this throw true try typeof debugger eval export function get null set undefined var with Infinity NaN ",cStyleComments:true,regexLiterals:true}),["js"]);u(B([],[[A,/^[\s\S]+/]]),
-["regex"]);window.PR_normalizedHtml=H;window.prettyPrintOne=function(b,f){var i={f:b,e:f};U(i);return i.a};window.prettyPrint=function(b){function f(){for(var t=window.PR_SHOULD_USE_CONTINUATION?j.now()+250:Infinity;q<o.length&&j.now()<t;q++){var p=o[q];if(p.className&&p.className.indexOf("prettyprint")>=0){var c=p.className.match(/\blang-(\w+)\b/);if(c)c=c[1];for(var d=false,a=p.parentNode;a;a=a.parentNode)if((a.tagName==="pre"||a.tagName==="code"||a.tagName==="xmp")&&a.className&&a.className.indexOf("prettyprint")>=
-0){d=true;break}if(!d){a=p;if(null===K){d=document.createElement("PRE");d.appendChild(document.createTextNode('<!DOCTYPE foo PUBLIC "foo bar">\n<foo />'));K=!/</.test(d.innerHTML)}if(K){d=a.innerHTML;if("XMP"===a.tagName)d=y(d);else{a=a;if("PRE"===a.tagName)a=true;else if(ka.test(d)){var k="";if(a.currentStyle)k=a.currentStyle.whiteSpace;else if(window.getComputedStyle)k=window.getComputedStyle(a,null).whiteSpace;a=!k||k==="pre"}else a=true;a||(d=d.replace(/(<br\s*\/?>)[\r\n]+/g,"$1").replace(/(?:[\r\n]+[ \t]*)+/g,
-" "))}d=d}else{d=[];for(a=a.firstChild;a;a=a.nextSibling)H(a,d);d=d.join("")}d=d.replace(/(?:\r\n?|\n)$/,"");m={f:d,e:c,b:p};U(m);if(p=m.a){c=m.b;if("XMP"===c.tagName){d=document.createElement("PRE");for(a=0;a<c.attributes.length;++a){k=c.attributes[a];if(k.specified)if(k.name.toLowerCase()==="class")d.className=k.value;else d.setAttribute(k.name,k.value)}d.innerHTML=p;c.parentNode.replaceChild(d,c)}else c.innerHTML=p}}}}if(q<o.length)setTimeout(f,250);else b&&b()}for(var i=[document.getElementsByTagName("pre"),
-document.getElementsByTagName("code"),document.getElementsByTagName("xmp")],o=[],l=0;l<i.length;++l)for(var n=0,r=i[l].length;n<r;++n)o.push(i[l][n]);i=null;var j=Date;j.now||(j={now:function(){return(new Date).getTime()}});var q=0,m;f()};window.PR={combinePrefixPatterns:O,createSimpleLexer:B,registerLangHandler:u,sourceDecorator:x,PR_ATTRIB_NAME:"atn",PR_ATTRIB_VALUE:"atv",PR_COMMENT:C,PR_DECLARATION:"dec",PR_KEYWORD:R,PR_LITERAL:J,PR_NOCODE:V,PR_PLAIN:z,PR_PUNCTUATION:E,PR_SOURCE:P,PR_STRING:A,
-PR_TAG:"tag",PR_TYPE:S}})()
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/panels/ActivityPanel.html b/src/com/gitblit/wicket/panels/ActivityPanel.html
deleted file mode 100644
index b818e94..0000000
--- a/src/com/gitblit/wicket/panels/ActivityPanel.html
+++ /dev/null
@@ -1,37 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml"  
-      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"  
-      xml:lang="en"  
-      lang="en"> 
-
-<body>
-<wicket:panel>
-
-	<div wicket:id="activity" style="padding-bottom:10px;">
-		<div class="header"><i class="icon-refresh" style="vertical-align: middle;"></i> <span style="font-weight:bold;" wicket:id="title">[title]</span></div>
-		<table class="activity">
-			<tr wicket:id="commit">
-				<td class="hidden-phone date" style="width:60px; vertical-align: middle;text-align: right;padding-right:10px;" ><span wicket:id="time">[time of day]</span></td>
-				<td style="width:10em;text-align:left;vertical-align: middle;">
-					<span wicket:id="repository" class="repositorySwatch">[repository link]</span>
-				</td>
-				<td class="hidden-phone hidden-tablet" style="width:30px;vertical-align: middle;"><span wicket:id="avatar" style="vertical-align: middle;"></span></td>
-				<td style="vertical-align: middle;padding-left:15px;">
-					<img class="hidden-phone hidden-tablet" wicket:id="commitIcon" style="vertical-align: top;"></img>
-					<span wicket:id="message">[shortlog commit link]</span><br/>
-					<span wicket:id="author">[author link]</span> <span class="hidden-phone"><wicket:message key="gb.authored"></wicket:message> <span wicket:id="commitid">[commit id]</span></span> on <span wicket:id="branch"></span>
-				</td>
-				<td class="hidden-phone" style="text-align:right;vertical-align: middle;">
-					<div wicket:id="commitRefs">[commit refs]</div>
-				</td>
-				<td class="hidden-phone rightAlign" style="width:7em;vertical-align: middle;">
-        			<span class="link">
-						<a wicket:id="diff" target="_blank"><wicket:message key="gb.diff"></wicket:message></a> | <a wicket:id="tree" target="_blank"><wicket:message key="gb.tree"></wicket:message></a>
-					</span>
-				</td>		
-			</tr>		
-		</table>	
-	</div>
-</wicket:panel>
-</body>
-</html>
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/panels/ActivityPanel.java b/src/com/gitblit/wicket/panels/ActivityPanel.java
deleted file mode 100644
index 669c36b..0000000
--- a/src/com/gitblit/wicket/panels/ActivityPanel.java
+++ /dev/null
@@ -1,148 +0,0 @@
-/*
- * 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.wicket.panels;
-
-import java.util.Collections;
-import java.util.List;
-
-import org.apache.wicket.markup.html.link.BookmarkablePageLink;
-import org.apache.wicket.markup.repeater.Item;
-import org.apache.wicket.markup.repeater.data.DataView;
-import org.apache.wicket.markup.repeater.data.ListDataProvider;
-
-import com.gitblit.Constants;
-import com.gitblit.GitBlit;
-import com.gitblit.Keys;
-import com.gitblit.models.Activity;
-import com.gitblit.models.RepositoryCommit;
-import com.gitblit.utils.StringUtils;
-import com.gitblit.wicket.WicketUtils;
-import com.gitblit.wicket.pages.CommitDiffPage;
-import com.gitblit.wicket.pages.CommitPage;
-import com.gitblit.wicket.pages.GitSearchPage;
-import com.gitblit.wicket.pages.LogPage;
-import com.gitblit.wicket.pages.SummaryPage;
-import com.gitblit.wicket.pages.TreePage;
-
-/**
- * Renders activity in day-blocks in reverse-chronological order.
- * 
- * @author James Moger
- * 
- */
-public class ActivityPanel extends BasePanel {
-
-	private static final long serialVersionUID = 1L;
-
-	public ActivityPanel(String wicketId, List<Activity> recentActivity) {
-		super(wicketId);
-
-		Collections.sort(recentActivity);
-		
-		final int shortHashLen = GitBlit.getInteger(Keys.web.shortCommitIdLength, 6);
-		DataView<Activity> activityView = new DataView<Activity>("activity",
-				new ListDataProvider<Activity>(recentActivity)) {
-			private static final long serialVersionUID = 1L;
-
-			public void populateItem(final Item<Activity> activityItem) {
-				final Activity entry = activityItem.getModelObject();
-				activityItem.add(WicketUtils.createDatestampLabel("title", entry.startDate, getTimeZone(), getTimeUtils()));
-
-				// display the commits in chronological order
-				DataView<RepositoryCommit> commits = new DataView<RepositoryCommit>("commit",
-						new ListDataProvider<RepositoryCommit>(entry.getCommits())) {
-					private static final long serialVersionUID = 1L;
-
-					public void populateItem(final Item<RepositoryCommit> commitItem) {
-						final RepositoryCommit commit = commitItem.getModelObject();
-
-						// commit time of day
-						commitItem.add(WicketUtils.createTimeLabel("time", commit.getCommitterIdent()
-								.getWhen(), getTimeZone(), getTimeUtils()));
-
-						// avatar
-						commitItem.add(new GravatarImage("avatar", commit.getAuthorIdent(), 40));
-
-						// merge icon
-						if (commit.getParentCount() > 1) {
-							commitItem.add(WicketUtils.newImage("commitIcon",
-									"commit_merge_16x16.png"));
-						} else {
-							commitItem.add(WicketUtils.newBlankImage("commitIcon").setVisible(false));
-						}
-
-						// author search link
-						String author = commit.getAuthorIdent().getName();
-						LinkPanel authorLink = new LinkPanel("author", "list", author,
-								GitSearchPage.class, WicketUtils.newSearchParameter(commit.repository,
-										commit.getName(), author, Constants.SearchType.AUTHOR), true);
-						setPersonSearchTooltip(authorLink, author, Constants.SearchType.AUTHOR);
-						commitItem.add(authorLink);
-
-						// repository
-						String repoName = StringUtils.stripDotGit(commit.repository);
-						LinkPanel repositoryLink = new LinkPanel("repository", null,
-								repoName, SummaryPage.class,
-								WicketUtils.newRepositoryParameter(commit.repository), true);
-						WicketUtils.setCssBackground(repositoryLink, repoName);
-						commitItem.add(repositoryLink);
-
-						// repository branch
-						LinkPanel branchLink = new LinkPanel("branch", "list", commit.branch,
-								LogPage.class, WicketUtils.newObjectParameter(commit.repository,
-										commit.branch), true);
-						WicketUtils.setCssStyle(branchLink, "color: #008000;");
-						commitItem.add(branchLink);
-
-						LinkPanel commitid = new LinkPanel("commitid", "list subject",
-								commit.getName().substring(0,  shortHashLen), CommitPage.class,
-								WicketUtils.newObjectParameter(commit.repository, commit.getName()), true);
-						commitItem.add(commitid);
-
-						// message/commit link
-						String shortMessage = commit.getShortMessage();
-						String trimmedMessage = shortMessage;
-						if (commit.getRefs() != null && commit.getRefs().size() > 0) {
-							trimmedMessage = StringUtils.trimString(shortMessage, Constants.LEN_SHORTLOG_REFS);
-						} else {
-							trimmedMessage = StringUtils.trimString(shortMessage, Constants.LEN_SHORTLOG);
-						}
-						LinkPanel shortlog = new LinkPanel("message", "list subject",
-								trimmedMessage, CommitPage.class, WicketUtils.newObjectParameter(
-										commit.repository, commit.getName()), true);
-						if (!shortMessage.equals(trimmedMessage)) {
-							WicketUtils.setHtmlTooltip(shortlog, shortMessage);
-						}
-						commitItem.add(shortlog);
-
-						// refs
-						commitItem.add(new RefsPanel("commitRefs", commit.repository, commit
-								.getRefs()));
-
-						// diff, tree links
-						commitItem.add(new BookmarkablePageLink<Void>("diff", CommitDiffPage.class,
-								WicketUtils.newObjectParameter(commit.repository, commit.getName()))
-								.setEnabled(commit.getParentCount() > 0));
-						commitItem.add(new BookmarkablePageLink<Void>("tree", TreePage.class,
-								WicketUtils.newObjectParameter(commit.repository, commit.getName())));						
-					}
-				};
-				activityItem.add(commits);
-			}
-		};
-		add(activityView);
-	}
-}
diff --git a/src/com/gitblit/wicket/panels/BasePanel.java b/src/com/gitblit/wicket/panels/BasePanel.java
deleted file mode 100644
index ec87917..0000000
--- a/src/com/gitblit/wicket/panels/BasePanel.java
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * 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.wicket.panels;
-
-import java.util.ResourceBundle;
-import java.util.TimeZone;
-
-import org.apache.wicket.AttributeModifier;
-import org.apache.wicket.Component;
-import org.apache.wicket.markup.html.panel.Panel;
-import org.apache.wicket.model.Model;
-
-import com.gitblit.Constants;
-import com.gitblit.GitBlit;
-import com.gitblit.Keys;
-import com.gitblit.utils.TimeUtils;
-import com.gitblit.wicket.GitBlitWebSession;
-import com.gitblit.wicket.WicketUtils;
-
-public abstract class BasePanel extends Panel {
-
-	private static final long serialVersionUID = 1L;
-	
-	private transient TimeUtils timeUtils;
-
-	public BasePanel(String wicketId) {
-		super(wicketId);
-	}
-
-	protected TimeZone getTimeZone() {
-		return GitBlit.getBoolean(Keys.web.useClientTimezone, false) ? GitBlitWebSession.get()
-				.getTimezone() : GitBlit.getTimezone();
-	}
-	
-	protected TimeUtils getTimeUtils() {
-		if (timeUtils == null) {
-			ResourceBundle bundle;		
-			try {
-				bundle = ResourceBundle.getBundle("com.gitblit.wicket.GitBlitWebApp", GitBlitWebSession.get().getLocale());
-			} catch (Throwable t) {
-				bundle = ResourceBundle.getBundle("com.gitblit.wicket.GitBlitWebApp");
-			}
-			timeUtils = new TimeUtils(bundle);
-		}
-		return timeUtils;
-	}
-
-	protected void setPersonSearchTooltip(Component component, String value, Constants.SearchType searchType) {
-		if (searchType.equals(Constants.SearchType.AUTHOR)) {
-			WicketUtils.setHtmlTooltip(component, getString("gb.searchForAuthor") + " " + value);
-		} else if (searchType.equals(Constants.SearchType.COMMITTER)) {
-			WicketUtils.setHtmlTooltip(component, getString("gb.searchForCommitter") + " " + value);
-		}
-	}
-
-	public static class JavascriptEventConfirmation extends AttributeModifier {
-
-		private static final long serialVersionUID = 1L;
-
-		public JavascriptEventConfirmation(String event, String msg) {
-			super(event, true, new Model<String>(msg));
-		}
-
-		protected String newValue(final String currentValue, final String replacementValue) {
-			String prefix = "var conf = confirm('" + replacementValue + "'); "
-					+ "if (!conf) return false; ";
-			String result = prefix;
-			if (currentValue != null) {
-				result = prefix + currentValue;
-			}
-			return result;
-		}
-	}
-
-	public static class JavascriptTextPrompt extends AttributeModifier {
-
-		private static final long serialVersionUID = 1L;
-
-		private String initialValue = "";
-		
-		public JavascriptTextPrompt(String event, String msg, String value) {
-			super(event, true, new Model<String>(msg));
-			initialValue = value;
-		}
-
-		protected String newValue(final String currentValue, final String message) {
-			String result = "var userText = prompt('" + message + "','"
-					+ (initialValue == null ? "" : initialValue) + "'); " + "return userText; ";
-			return result;
-		}
-	}
-}
diff --git a/src/com/gitblit/wicket/panels/BranchesPanel.html b/src/com/gitblit/wicket/panels/BranchesPanel.html
deleted file mode 100644
index 58c86a4..0000000
--- a/src/com/gitblit/wicket/panels/BranchesPanel.html
+++ /dev/null
@@ -1,52 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml"  
-      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"  
-      xml:lang="en"  
-      lang="en"> 
-
-<body>
-<wicket:panel>
-
-	<!-- header -->
-	<div class="header"><i class="icon-random" style="vertical-align: middle;"></i> <b><span wicket:id="branches">[branches header]</span></b></div>	
-	
-	<table class="pretty">
-		<tbody>
-       		<tr wicket:id="branch">
-         		<td class="date"><span wicket:id="branchDate">[branch date]</span></td>
-         		<td><span wicket:id="branchName">[branch name]</span></td>
-         		<td class="hidden-phone hidden-tablet author"><span wicket:id="branchAuthor">[branch author]</span></td>
-         		<td class="hidden-phone"><span wicket:id="branchLog">[branch log]</span></td>
-         		<td class="hidden-phone rightAlign">
-         			<span wicket:id="branchLinks"></span>
-				</td>
-       		</tr>
-    	</tbody>
-	</table>	
-
-	<div wicket:id="allBranches">[all branches]</div>	
-
-	<!-- branch page links -->
-	<wicket:fragment wicket:id="branchPageLinks">
-		<span class="link">
-			<a wicket:id="log"><wicket:message key="gb.log"></wicket:message></a> | <a wicket:id="tree"><wicket:message key="gb.tree"></wicket:message></a> | <a wicket:id="metrics"><wicket:message key="gb.metrics"></wicket:message></a> | <a wicket:id="syndication"><wicket:message key="gb.feed"></wicket:message></a>
-		</span>
-	</wicket:fragment>
-
-	<!-- branch page admin links -->
-	<wicket:fragment wicket:id="branchPageAdminLinks">
-		<span class="link">
-			<a wicket:id="log"><wicket:message key="gb.log"></wicket:message></a> | <a wicket:id="tree"><wicket:message key="gb.tree"></wicket:message></a> | <a wicket:id="metrics"><wicket:message key="gb.metrics"></wicket:message></a> | <a wicket:id="syndication"><wicket:message key="gb.feed"></wicket:message></a> | <a wicket:id="deleteBranch"><wicket:message key="gb.delete"></wicket:message></a>
-		</span>
-	</wicket:fragment>
-
-	<!-- branch panel links -->
-	<wicket:fragment wicket:id="branchPanelLinks">
-		<span class="link">
-			<a wicket:id="log"><wicket:message key="gb.log"></wicket:message></a> | <a wicket:id="tree"><wicket:message key="gb.tree"></wicket:message></a>
-		</span>
-	</wicket:fragment>
-			
-</wicket:panel>
-</body>
-</html>
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/panels/CommitHeaderPanel.java b/src/com/gitblit/wicket/panels/CommitHeaderPanel.java
deleted file mode 100644
index bb960cc..0000000
--- a/src/com/gitblit/wicket/panels/CommitHeaderPanel.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * 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.wicket.panels;
-
-import org.apache.wicket.markup.html.basic.Label;
-import org.eclipse.jgit.revwalk.RevCommit;
-
-import com.gitblit.Constants;
-import com.gitblit.utils.StringUtils;
-import com.gitblit.wicket.WicketUtils;
-import com.gitblit.wicket.pages.CommitPage;
-
-public class CommitHeaderPanel extends BasePanel {
-
-	private static final long serialVersionUID = 1L;
-
-	public CommitHeaderPanel(String id, String title) {
-		super(id);
-		add(new Label("shortmessage", title));
-		add(new Label("commitid"));
-		add(new Label("author"));
-		add(new Label("date"));
-	}
-
-	public CommitHeaderPanel(String id, String repositoryName, RevCommit c) {
-		super(id);
-		add(new LinkPanel("shortmessage", "title", StringUtils.trimString(c.getShortMessage(),
-				Constants.LEN_SHORTLOG), CommitPage.class,
-				WicketUtils.newObjectParameter(repositoryName, c.getName())));
-		add(new Label("commitid", c.getName()));
-		add(new Label("author", c.getAuthorIdent().getName()));
-		add(WicketUtils.createDateLabel("date", c.getAuthorIdent().getWhen(), getTimeZone(), getTimeUtils()));
-		add(new GravatarImage("authorAvatar", c.getAuthorIdent()));
-	}
-}
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/panels/CompressedDownloadsPanel.java b/src/com/gitblit/wicket/panels/CompressedDownloadsPanel.java
deleted file mode 100644
index b22c758..0000000
--- a/src/com/gitblit/wicket/panels/CompressedDownloadsPanel.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * 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.wicket.panels;
-
-import java.util.List;
-
-import org.apache.wicket.Component;
-import org.apache.wicket.markup.html.basic.Label;
-import org.apache.wicket.markup.html.panel.Panel;
-import org.apache.wicket.markup.repeater.Item;
-import org.apache.wicket.markup.repeater.data.DataView;
-import org.apache.wicket.markup.repeater.data.ListDataProvider;
-
-import com.gitblit.DownloadZipServlet;
-import com.gitblit.DownloadZipServlet.Format;
-import com.gitblit.GitBlit;
-import com.gitblit.Keys;
-
-public class CompressedDownloadsPanel extends Panel {
-
-	private static final long serialVersionUID = 1L;
-
-	public CompressedDownloadsPanel(String id, final String baseUrl, final String repositoryName, final String objectId, final String path) {
-		super(id);
-		
-		List<String> types = GitBlit.getStrings(Keys.web.compressedDownloads);
-		if (types.isEmpty()) {
-			types.add(Format.zip.name());
-			types.add(Format.gz.name());
-		}
-		
-		ListDataProvider<String> refsDp = new ListDataProvider<String>(types);
-		DataView<String> refsView = new DataView<String>("compressedLinks", refsDp) {
-			private static final long serialVersionUID = 1L;
-			int counter;
-
-			@Override
-			protected void onBeforeRender() {
-				super.onBeforeRender();
-				counter = 0;
-			}
-			
-			@Override
-			public void populateItem(final Item<String> item) {
-				String compressionType = item.getModelObject();
-				Format format = Format.fromName(compressionType);
-				
-				String href = DownloadZipServlet.asLink(baseUrl, repositoryName,
-						objectId, path, format);
-				Component c = new LinkPanel("compressedLink", null, format.name(), href);
-				item.add(c);
-				Label lb = new Label("linkSep", "|");
-				lb.setVisible(counter > 0);
-				lb.setRenderBodyOnly(true);
-				item.add(lb.setEscapeModelStrings(false));
-				item.setRenderBodyOnly(true);
-				counter++;
-			}
-		};
-		add(refsView);
-		
-		setVisible(GitBlit.getBoolean(Keys.web.allowZipDownloads, true));
-	}
-}
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/panels/GravatarImage.java b/src/com/gitblit/wicket/panels/GravatarImage.java
deleted file mode 100644
index 7f1874f..0000000
--- a/src/com/gitblit/wicket/panels/GravatarImage.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * 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.wicket.panels;
-
-import java.text.MessageFormat;
-
-import org.apache.wicket.behavior.SimpleAttributeModifier;
-import org.apache.wicket.markup.html.link.BookmarkablePageLink;
-import org.apache.wicket.markup.html.link.Link;
-import org.apache.wicket.markup.html.panel.Panel;
-import org.eclipse.jgit.lib.PersonIdent;
-
-import com.gitblit.GitBlit;
-import com.gitblit.Keys;
-import com.gitblit.utils.ActivityUtils;
-import com.gitblit.utils.StringUtils;
-import com.gitblit.wicket.ExternalImage;
-import com.gitblit.wicket.WicketUtils;
-import com.gitblit.wicket.pages.GravatarProfilePage;
-
-/**
- * Represents a Gravatar image and links to the Gravatar profile page.
- * 
- * @author James Moger
- * 
- */
-public class GravatarImage extends Panel {
-
-	private static final long serialVersionUID = 1L;
-
-	public GravatarImage(String id, PersonIdent person) {
-		this(id, person, 0);
-	}
-	
-	public GravatarImage(String id, PersonIdent person, int width) {
-		this(id, person, width, true);
-	}
-
-	public GravatarImage(String id, PersonIdent person, int width, boolean linked) {
-		super(id);
-
-		String email = person.getEmailAddress() == null ? person.getName().toLowerCase() : person.getEmailAddress().toLowerCase();
-		String hash = StringUtils.getMD5(email);
-		Link<Void> link = new BookmarkablePageLink<Void>("link", GravatarProfilePage.class,
-				WicketUtils.newObjectParameter(hash));
-		link.add(new SimpleAttributeModifier("target", "_blank"));
-		String url = ActivityUtils.getGravatarThumbnailUrl(email, width);
-		ExternalImage image = new ExternalImage("image", url);
-		WicketUtils.setCssClass(image, "gravatar");
-		link.add(image);
-		if (linked) {
-			WicketUtils.setHtmlTooltip(link,
-				MessageFormat.format("View Gravatar profile for {0}", person.getName()));
-		} else {
-			WicketUtils.setHtmlTooltip(link, person.getName());
-		}
-		add(link.setEnabled(linked));
-		setVisible(GitBlit.getBoolean(Keys.web.allowGravatar, true));
-	}
-}
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/panels/HistoryPanel.java b/src/com/gitblit/wicket/panels/HistoryPanel.java
deleted file mode 100644
index e587863..0000000
--- a/src/com/gitblit/wicket/panels/HistoryPanel.java
+++ /dev/null
@@ -1,335 +0,0 @@
-/*
- * 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.wicket.panels;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import org.apache.wicket.markup.html.basic.Label;
-import org.apache.wicket.markup.html.link.BookmarkablePageLink;
-import org.apache.wicket.markup.html.panel.Fragment;
-import org.apache.wicket.markup.repeater.Item;
-import org.apache.wicket.markup.repeater.data.DataView;
-import org.apache.wicket.markup.repeater.data.ListDataProvider;
-import org.apache.wicket.model.StringResourceModel;
-import org.eclipse.jgit.diff.DiffEntry.ChangeType;
-import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.revwalk.RevCommit;
-import org.eclipse.jgit.treewalk.TreeWalk;
-import org.eclipse.jgit.treewalk.filter.PathFilterGroup;
-
-import com.gitblit.Constants;
-import com.gitblit.GitBlit;
-import com.gitblit.Keys;
-import com.gitblit.models.PathModel;
-import com.gitblit.models.SubmoduleModel;
-import com.gitblit.models.PathModel.PathChangeModel;
-import com.gitblit.models.RefModel;
-import com.gitblit.utils.JGitUtils;
-import com.gitblit.utils.StringUtils;
-import com.gitblit.wicket.WicketUtils;
-import com.gitblit.wicket.pages.BlobDiffPage;
-import com.gitblit.wicket.pages.BlobPage;
-import com.gitblit.wicket.pages.CommitDiffPage;
-import com.gitblit.wicket.pages.CommitPage;
-import com.gitblit.wicket.pages.GitSearchPage;
-import com.gitblit.wicket.pages.HistoryPage;
-import com.gitblit.wicket.pages.TreePage;
-
-public class HistoryPanel extends BasePanel {
-
-	private static final long serialVersionUID = 1L;
-
-	private boolean hasMore;
-
-	public HistoryPanel(String wicketId, final String repositoryName, final String objectId,
-			final String path, Repository r, int limit, int pageOffset, boolean showRemoteRefs) {
-		super(wicketId);
-		boolean pageResults = limit <= 0;
-		int itemsPerPage = GitBlit.getInteger(Keys.web.itemsPerPage, 50);
-		if (itemsPerPage <= 1) {
-			itemsPerPage = 50;
-		}
-
-		RevCommit commit = JGitUtils.getCommit(r, objectId);
-		List<PathChangeModel> paths = JGitUtils.getFilesInCommit(r, commit);
-
-		Map<String, SubmoduleModel> submodules = new HashMap<String, SubmoduleModel>();
-		for (SubmoduleModel model : JGitUtils.getSubmodules(r, commit.getTree())) {
-			submodules.put(model.path, model);
-		}
-
-		PathModel matchingPath = null;
-		for (PathModel p : paths) {
-			if (p.path.equals(path)) {
-				matchingPath = p;
-				break;
-			}
-		}
-		if (matchingPath == null) {
-			// path not in commit
-			// manually locate path in tree
-			TreeWalk tw = new TreeWalk(r);
-			tw.reset();
-			tw.setRecursive(true);
-			try {
-				tw.addTree(commit.getTree());
-				tw.setFilter(PathFilterGroup.createFromStrings(Collections.singleton(path)));
-				while (tw.next()) {
-					if (tw.getPathString().equals(path)) {
-						matchingPath = new PathChangeModel(tw.getPathString(), tw.getPathString(), 0, tw
-							.getRawMode(0), tw.getObjectId(0).getName(), commit.getId().getName(),
-							ChangeType.MODIFY);
-					}
-				}
-			} catch (Exception e) {
-			} finally {
-				tw.release();
-			}
-		}
-		
-		final boolean isTree = matchingPath == null ? true : matchingPath.isTree();
-		final boolean isSubmodule = matchingPath == null ? true : matchingPath.isSubmodule();
-
-		// submodule
-		SubmoduleModel submodule = getSubmodule(submodules, repositoryName, matchingPath.path);
-		final String submodulePath;
-		final boolean hasSubmodule; 
-		if (submodule != null) {
-			submodulePath = submodule.gitblitPath;
-			hasSubmodule = submodule.hasSubmodule;
-		} else {
-			submodulePath = "";
-			hasSubmodule = false;
-		}
-		
-		final Map<ObjectId, List<RefModel>> allRefs = JGitUtils.getAllRefs(r, showRemoteRefs);
-		List<RevCommit> commits;
-		if (pageResults) {
-			// Paging result set
-			commits = JGitUtils.getRevLog(r, objectId, path, pageOffset * itemsPerPage,
-					itemsPerPage);
-		} else {
-			// Fixed size result set
-			commits = JGitUtils.getRevLog(r, objectId, path, 0, limit);
-		}
-
-		// inaccurate way to determine if there are more commits.
-		// works unless commits.size() represents the exact end.
-		hasMore = commits.size() >= itemsPerPage;
-
-		add(new CommitHeaderPanel("commitHeader", repositoryName, commit));
-
-		// breadcrumbs
-		add(new PathBreadcrumbsPanel("breadcrumbs", repositoryName, path, objectId));
-
-		final int hashLen = GitBlit.getInteger(Keys.web.shortCommitIdLength, 6);
-		ListDataProvider<RevCommit> dp = new ListDataProvider<RevCommit>(commits);
-		DataView<RevCommit> logView = new DataView<RevCommit>("commit", dp) {
-			private static final long serialVersionUID = 1L;
-			int counter;
-
-			public void populateItem(final Item<RevCommit> item) {
-				final RevCommit entry = item.getModelObject();
-				final Date date = JGitUtils.getCommitDate(entry);
-
-				item.add(WicketUtils.createDateLabel("commitDate", date, getTimeZone(), getTimeUtils()));
-
-				// author search link
-				String author = entry.getAuthorIdent().getName();
-				LinkPanel authorLink = new LinkPanel("commitAuthor", "list", author,
-						GitSearchPage.class,
-						WicketUtils.newSearchParameter(repositoryName, objectId,
-								author, Constants.SearchType.AUTHOR));
-				setPersonSearchTooltip(authorLink, author, Constants.SearchType.AUTHOR);
-				item.add(authorLink);
-
-				// merge icon
-				if (entry.getParentCount() > 1) {
-					item.add(WicketUtils.newImage("commitIcon", "commit_merge_16x16.png"));
-				} else {
-					item.add(WicketUtils.newBlankImage("commitIcon"));
-				}
-
-				String shortMessage = entry.getShortMessage();
-				String trimmedMessage = shortMessage;
-				if (allRefs.containsKey(entry.getId())) {
-					trimmedMessage = StringUtils.trimString(shortMessage, Constants.LEN_SHORTLOG_REFS);
-				} else {
-					trimmedMessage = StringUtils.trimString(shortMessage, Constants.LEN_SHORTLOG);
-				}
-				LinkPanel shortlog = new LinkPanel("commitShortMessage", "list subject",
-						trimmedMessage, CommitPage.class, WicketUtils.newObjectParameter(
-								repositoryName, entry.getName()));
-				if (!shortMessage.equals(trimmedMessage)) {
-					WicketUtils.setHtmlTooltip(shortlog, shortMessage);
-				}
-				item.add(shortlog);
-
-				item.add(new RefsPanel("commitRefs", repositoryName, entry, allRefs));
-
-				if (isTree) {
-					// tree
-					item.add(new Label("hashLabel", getString("gb.tree") + "@"));
-					LinkPanel commitHash = new LinkPanel("hashLink", null, entry.getName().substring(0, hashLen),
-							TreePage.class, WicketUtils.newObjectParameter(
-									repositoryName, entry.getName()));
-					WicketUtils.setCssClass(commitHash, "shortsha1");
-					WicketUtils.setHtmlTooltip(commitHash, entry.getName());					
-					item.add(commitHash);
-					
-					Fragment links = new Fragment("historyLinks", "treeLinks", this);
-					links.add(new BookmarkablePageLink<Void>("commitdiff", CommitDiffPage.class,
-							WicketUtils.newObjectParameter(repositoryName, entry.getName())));
-					item.add(links);
-				} else if (isSubmodule) {
-					// submodule
-					item.add(new Label("hashLabel", submodulePath + "@"));
-					Repository repository = GitBlit.self().getRepository(repositoryName);
-					String submoduleId = JGitUtils.getSubmoduleCommitId(repository, path, entry);
-					repository.close();
-					LinkPanel commitHash = new LinkPanel("hashLink", null, submoduleId.substring(0, hashLen),
-							TreePage.class, WicketUtils.newObjectParameter(
-									submodulePath, submoduleId));
-					WicketUtils.setCssClass(commitHash, "shortsha1");
-					WicketUtils.setHtmlTooltip(commitHash, submoduleId);					
-					item.add(commitHash.setEnabled(hasSubmodule));
-					
-					Fragment links = new Fragment("historyLinks", "treeLinks", this);
-					links.add(new BookmarkablePageLink<Void>("commitdiff", CommitDiffPage.class,
-							WicketUtils.newObjectParameter(repositoryName, entry.getName())));
-					item.add(links);
-				} else {					
-					// commit
-					item.add(new Label("hashLabel", getString("gb.blob") + "@"));
-					LinkPanel commitHash = new LinkPanel("hashLink", null, entry.getName().substring(0, hashLen),
-							BlobPage.class, WicketUtils.newPathParameter(
-									repositoryName, entry.getName(), path));
-					WicketUtils.setCssClass(commitHash, "sha1");
-					WicketUtils.setHtmlTooltip(commitHash, entry.getName());
-					item.add(commitHash);
-					
-					Fragment links = new Fragment("historyLinks", "blobLinks", this);
-					links.add(new BookmarkablePageLink<Void>("commitdiff", CommitDiffPage.class,
-							WicketUtils.newObjectParameter(repositoryName, entry.getName())));
-					links.add(new BookmarkablePageLink<Void>("difftocurrent", BlobDiffPage.class,
-							WicketUtils.newBlobDiffParameter(repositoryName, entry.getName(),
-									objectId, path)).setEnabled(counter > 0));
-					item.add(links);
-				}
-
-				WicketUtils.setAlternatingBackground(item, counter);
-				counter++;
-			}
-		};
-		add(logView);
-
-		// determine to show pager, more, or neither
-		if (limit <= 0) {
-			// no display limit
-			add(new Label("moreHistory", "").setVisible(false));
-		} else {
-			if (pageResults) {
-				// paging
-				add(new Label("moreHistory", "").setVisible(false));
-			} else {
-				// more
-				if (commits.size() == limit) {
-					// show more
-					add(new LinkPanel("moreHistory", "link", new StringResourceModel(
-							"gb.moreHistory", this, null), HistoryPage.class,
-							WicketUtils.newPathParameter(repositoryName, objectId, path)));
-				} else {
-					// no more
-					add(new Label("moreHistory", "").setVisible(false));
-				}
-			}
-		}
-	}
-
-	public boolean hasMore() {
-		return hasMore;
-	}
-	
-	protected SubmoduleModel getSubmodule(Map<String, SubmoduleModel> submodules, String repositoryName, String path) {
-		SubmoduleModel model = submodules.get(path);
-		if (model == null) {
-			// undefined submodule?!
-			model = new SubmoduleModel(path.substring(path.lastIndexOf('/') + 1), path, path);
-			model.hasSubmodule = false;
-			model.gitblitPath = model.name;
-			return model;
-		} else {
-			// extract the repository name from the clone url
-			List<String> patterns = GitBlit.getStrings(Keys.git.submoduleUrlPatterns);
-			String submoduleName = StringUtils.extractRepositoryPath(model.url, patterns.toArray(new String[0]));
-			
-			// determine the current path for constructing paths relative
-			// to the current repository
-			String currentPath = "";
-			if (repositoryName.indexOf('/') > -1) {
-				currentPath = repositoryName.substring(0, repositoryName.lastIndexOf('/') + 1);
-			}
-
-			// try to locate the submodule repository
-			// prefer bare to non-bare names
-			List<String> candidates = new ArrayList<String>();
-
-			// relative
-			candidates.add(currentPath + StringUtils.stripDotGit(submoduleName));
-			candidates.add(candidates.get(candidates.size() - 1) + ".git");
-
-			// relative, no subfolder
-			if (submoduleName.lastIndexOf('/') > -1) {
-				String name = submoduleName.substring(submoduleName.lastIndexOf('/') + 1);
-				candidates.add(currentPath + StringUtils.stripDotGit(name));
-				candidates.add(currentPath + candidates.get(candidates.size() - 1) + ".git");
-			}
-
-			// absolute
-			candidates.add(StringUtils.stripDotGit(submoduleName));
-			candidates.add(candidates.get(candidates.size() - 1) + ".git");
-
-			// absolute, no subfolder
-			if (submoduleName.lastIndexOf('/') > -1) {
-				String name = submoduleName.substring(submoduleName.lastIndexOf('/') + 1);
-				candidates.add(StringUtils.stripDotGit(name));
-				candidates.add(candidates.get(candidates.size() - 1) + ".git");
-			}
-
-			// create a unique, ordered set of candidate paths
-			Set<String> paths = new LinkedHashSet<String>(candidates);
-			for (String candidate : paths) {
-				if (GitBlit.self().hasRepository(candidate)) {
-					model.hasSubmodule = true;
-					model.gitblitPath = candidate;
-					return model;
-				}
-			}
-			
-			// we do not have a copy of the submodule, but we need a path
-			model.gitblitPath = candidates.get(0);
-			return model;
-		}		
-	}
-}
diff --git a/src/com/gitblit/wicket/panels/LinkPanel.java b/src/com/gitblit/wicket/panels/LinkPanel.java
deleted file mode 100644
index 688a957..0000000
--- a/src/com/gitblit/wicket/panels/LinkPanel.java
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * 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.wicket.panels;
-
-import org.apache.wicket.PageParameters;
-import org.apache.wicket.behavior.SimpleAttributeModifier;
-import org.apache.wicket.markup.html.WebPage;
-import org.apache.wicket.markup.html.basic.Label;
-import org.apache.wicket.markup.html.link.BookmarkablePageLink;
-import org.apache.wicket.markup.html.link.ExternalLink;
-import org.apache.wicket.markup.html.link.Link;
-import org.apache.wicket.markup.html.panel.Panel;
-import org.apache.wicket.model.IModel;
-import org.apache.wicket.model.Model;
-
-import com.gitblit.utils.StringUtils;
-import com.gitblit.wicket.WicketUtils;
-
-public class LinkPanel extends Panel {
-
-	private static final long serialVersionUID = 1L;
-
-	private final IModel<String> labelModel;
-
-	public LinkPanel(String wicketId, String linkCssClass, String label,
-			Class<? extends WebPage> clazz) {
-		this(wicketId, null, linkCssClass, new Model<String>(label), clazz, null, false);
-	}
-
-	public LinkPanel(String wicketId, String linkCssClass, String label,
-			Class<? extends WebPage> clazz, PageParameters parameters) {
-		this(wicketId, null, linkCssClass, new Model<String>(label), clazz, parameters, false);
-	}
-
-	public LinkPanel(String wicketId, String linkCssClass, String label,
-			Class<? extends WebPage> clazz, PageParameters parameters, boolean newWindow) {
-		this(wicketId, null, linkCssClass, new Model<String>(label), clazz, parameters, newWindow);
-	}
-
-	public LinkPanel(String wicketId, String bootstrapIcon, String linkCssClass, String label,
-			Class<? extends WebPage> clazz, PageParameters parameters, boolean newWindow) {
-		this(wicketId, bootstrapIcon, linkCssClass, new Model<String>(label), clazz, parameters, newWindow);
-	}
-
-	public LinkPanel(String wicketId, String linkCssClass, IModel<String> model,
-			Class<? extends WebPage> clazz, PageParameters parameters) {
-		this(wicketId, null, linkCssClass, model, clazz, parameters, false);
-	}
-
-	public LinkPanel(String wicketId, String bootstrapIcon, String linkCssClass, IModel<String> model,
-			Class<? extends WebPage> clazz, PageParameters parameters, boolean newWindow) {
-		super(wicketId);
-		this.labelModel = model;
-		Link<Void> link = null;
-		if (parameters == null) {
-			link = new BookmarkablePageLink<Void>("link", clazz);
-		} else {
-			link = new BookmarkablePageLink<Void>("link", clazz, parameters);
-		}
-		if (newWindow) {
-			link.add(new SimpleAttributeModifier("target", "_blank"));
-		}
-		if (linkCssClass != null) {
-			link.add(new SimpleAttributeModifier("class", linkCssClass));
-		}
-		Label icon = new Label("icon");
-		if (StringUtils.isEmpty(bootstrapIcon)) {
-			link.add(icon.setVisible(false));
-		} else {
-			WicketUtils.setCssClass(icon, bootstrapIcon);
-			link.add(icon);
-		}
-		link.add(new Label("label", labelModel).setRenderBodyOnly(true));
-		add(link);
-	}
-
-	public LinkPanel(String wicketId, String linkCssClass, String label, String href) {
-		this(wicketId, linkCssClass, label, href, false);
-	}
-
-	public LinkPanel(String wicketId, String linkCssClass, String label, String href,
-			boolean newWindow) {
-		super(wicketId);
-		this.labelModel = new Model<String>(label);
-		ExternalLink link = new ExternalLink("link", href);
-		if (newWindow) {
-			link.add(new SimpleAttributeModifier("target", "_blank"));
-		}
-		if (linkCssClass != null) {
-			link.add(new SimpleAttributeModifier("class", linkCssClass));
-		}
-		link.add(new Label("icon").setVisible(false));
-		link.add(new Label("label", labelModel));
-		add(link);
-	}
-
-}
diff --git a/src/com/gitblit/wicket/panels/LogPanel.html b/src/com/gitblit/wicket/panels/LogPanel.html
deleted file mode 100644
index 2b2605a..0000000
--- a/src/com/gitblit/wicket/panels/LogPanel.html
+++ /dev/null
@@ -1,32 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml"  
-      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"  
-      xml:lang="en"  
-      lang="en"> 
-
-<body>
-<wicket:panel>
-
-	<!-- header -->	
-	<div class="header"><i class="icon-refresh" style="vertical-align: middle;"></i> <b><span wicket:id="header">[log header]</span></b></div>
-	<table class="pretty">
-		<tbody>
-       		<tr wicket:id="commit">
-         		<td class="date" style="width:6em;"><span wicket:id="commitDate">[commit date]</span></td>
-         		<td class="hidden-phone author"><span wicket:id="commitAuthor">[commit author]</span></td>
-         		<td class="hidden-phone icon"><img wicket:id="commitIcon" /></td>
-         		<td class="message"><table class="nestedTable"><tr><td><span style="vertical-align:middle;" wicket:id="commitShortMessage">[commit short message]</span></td><td><div style="text-align:right;" wicket:id="commitRefs">[commit refs]</div></td></tr></table></td>
-         		<td class="hidden-phone hidden-tablet rightAlign"><span wicket:id="hashLink">[hash link]</span></td>
-         		<td class="hidden-phone hidden-tablet rightAlign">
-         			<span class="link">
-						<a wicket:id="diff"><wicket:message key="gb.diff"></wicket:message></a> | <a wicket:id="tree"><wicket:message key="gb.tree"></wicket:message></a>
-					</span>
-				</td>
-       		</tr>
-    	</tbody>
-	</table>	
-	<div wicket:id="moreLogs">[more...]</div>
-	
-</wicket:panel>
-</body>
-</html>
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/panels/LogPanel.java b/src/com/gitblit/wicket/panels/LogPanel.java
deleted file mode 100644
index 0539764..0000000
--- a/src/com/gitblit/wicket/panels/LogPanel.java
+++ /dev/null
@@ -1,176 +0,0 @@
-/*
- * 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.wicket.panels;
-
-import java.util.Date;
-import java.util.List;
-import java.util.Map;
-
-import org.apache.wicket.markup.html.basic.Label;
-import org.apache.wicket.markup.html.link.BookmarkablePageLink;
-import org.apache.wicket.markup.repeater.Item;
-import org.apache.wicket.markup.repeater.data.DataView;
-import org.apache.wicket.markup.repeater.data.ListDataProvider;
-import org.apache.wicket.model.StringResourceModel;
-import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.revwalk.RevCommit;
-
-import com.gitblit.Constants;
-import com.gitblit.GitBlit;
-import com.gitblit.Keys;
-import com.gitblit.models.RefModel;
-import com.gitblit.utils.JGitUtils;
-import com.gitblit.utils.StringUtils;
-import com.gitblit.wicket.WicketUtils;
-import com.gitblit.wicket.pages.CommitDiffPage;
-import com.gitblit.wicket.pages.CommitPage;
-import com.gitblit.wicket.pages.LogPage;
-import com.gitblit.wicket.pages.GitSearchPage;
-import com.gitblit.wicket.pages.TreePage;
-
-public class LogPanel extends BasePanel {
-
-	private static final long serialVersionUID = 1L;
-
-	private boolean hasMore;
-
-	public LogPanel(String wicketId, final String repositoryName, final String objectId,
-			Repository r, int limit, int pageOffset, boolean showRemoteRefs) {
-		super(wicketId);
-		boolean pageResults = limit <= 0;
-		int itemsPerPage = GitBlit.getInteger(Keys.web.itemsPerPage, 50);
-		if (itemsPerPage <= 1) {
-			itemsPerPage = 50;
-		}
-
-		final Map<ObjectId, List<RefModel>> allRefs = JGitUtils.getAllRefs(r, showRemoteRefs);
-		List<RevCommit> commits;
-		if (pageResults) {
-			// Paging result set
-			commits = JGitUtils.getRevLog(r, objectId, pageOffset * itemsPerPage, itemsPerPage);
-		} else {
-			// Fixed size result set
-			commits = JGitUtils.getRevLog(r, objectId, 0, limit);
-		}
-
-		// inaccurate way to determine if there are more commits.
-		// works unless commits.size() represents the exact end.
-		hasMore = commits.size() >= itemsPerPage;
-
-		// header
-		if (pageResults) {
-			// shortlog page
-			add(new Label("header", objectId));
-		} else {
-			// summary page
-			// show shortlog page link
-			add(new LinkPanel("header", "title", objectId, LogPage.class,
-					WicketUtils.newRepositoryParameter(repositoryName)));
-		}
-
-		final int hashLen = GitBlit.getInteger(Keys.web.shortCommitIdLength, 6);
-		ListDataProvider<RevCommit> dp = new ListDataProvider<RevCommit>(commits);
-		DataView<RevCommit> logView = new DataView<RevCommit>("commit", dp) {
-			private static final long serialVersionUID = 1L;
-			int counter;
-
-			public void populateItem(final Item<RevCommit> item) {
-				final RevCommit entry = item.getModelObject();
-				final Date date = JGitUtils.getCommitDate(entry);
-
-				item.add(WicketUtils.createDateLabel("commitDate", date, getTimeZone(), getTimeUtils()));
-
-				// author search link
-				String author = entry.getAuthorIdent().getName();
-				LinkPanel authorLink = new LinkPanel("commitAuthor", "list", author,
-						GitSearchPage.class, WicketUtils.newSearchParameter(repositoryName,
-								objectId, author, Constants.SearchType.AUTHOR));
-				setPersonSearchTooltip(authorLink, author, Constants.SearchType.AUTHOR);
-				item.add(authorLink);
-				
-				// merge icon
-				if (entry.getParentCount() > 1) {
-					item.add(WicketUtils.newImage("commitIcon", "commit_merge_16x16.png"));
-				} else {
-					item.add(WicketUtils.newBlankImage("commitIcon"));
-				}
-
-				// short message
-				String shortMessage = entry.getShortMessage();
-				String trimmedMessage = shortMessage;
-				if (allRefs.containsKey(entry.getId())) {
-					trimmedMessage = StringUtils.trimString(shortMessage, Constants.LEN_SHORTLOG_REFS);
-				} else {
-					trimmedMessage = StringUtils.trimString(shortMessage, Constants.LEN_SHORTLOG);
-				}
-				LinkPanel shortlog = new LinkPanel("commitShortMessage", "list subject",
-						trimmedMessage, CommitPage.class, WicketUtils.newObjectParameter(
-								repositoryName, entry.getName()));
-				if (!shortMessage.equals(trimmedMessage)) {
-					WicketUtils.setHtmlTooltip(shortlog, shortMessage);
-				}
-				item.add(shortlog);
-
-				item.add(new RefsPanel("commitRefs", repositoryName, entry, allRefs));
-
-				// commit hash link
-				LinkPanel commitHash = new LinkPanel("hashLink", null, entry.getName().substring(0, hashLen),
-						CommitPage.class, WicketUtils.newObjectParameter(
-								repositoryName, entry.getName()));
-				WicketUtils.setCssClass(commitHash, "shortsha1");
-				WicketUtils.setHtmlTooltip(commitHash, entry.getName());
-				item.add(commitHash);
-				
-				item.add(new BookmarkablePageLink<Void>("diff", CommitDiffPage.class, WicketUtils
-						.newObjectParameter(repositoryName, entry.getName())).setEnabled(entry
-						.getParentCount() > 0));
-				item.add(new BookmarkablePageLink<Void>("tree", TreePage.class, WicketUtils
-						.newObjectParameter(repositoryName, entry.getName())));
-
-				WicketUtils.setAlternatingBackground(item, counter);
-				counter++;
-			}
-		};
-		add(logView);
-
-		// determine to show pager, more, or neither
-		if (limit <= 0) {
-			// no display limit
-			add(new Label("moreLogs", "").setVisible(false));
-		} else {
-			if (pageResults) {
-				// paging
-				add(new Label("moreLogs", "").setVisible(false));
-			} else {
-				// more
-				if (commits.size() == limit) {
-					// show more
-					add(new LinkPanel("moreLogs", "link", new StringResourceModel("gb.moreLogs",
-							this, null), LogPage.class,
-							WicketUtils.newRepositoryParameter(repositoryName)));
-				} else {
-					// no more
-					add(new Label("moreLogs", "").setVisible(false));
-				}
-			}
-		}
-	}
-
-	public boolean hasMore() {
-		return hasMore;
-	}
-}
diff --git a/src/com/gitblit/wicket/panels/NavigationPanel.java b/src/com/gitblit/wicket/panels/NavigationPanel.java
deleted file mode 100644
index 558cc71..0000000
--- a/src/com/gitblit/wicket/panels/NavigationPanel.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * 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.wicket.panels;
-
-import java.util.List;
-
-import org.apache.wicket.Component;
-import org.apache.wicket.markup.html.panel.Panel;
-import org.apache.wicket.markup.repeater.Item;
-import org.apache.wicket.markup.repeater.data.DataView;
-import org.apache.wicket.markup.repeater.data.ListDataProvider;
-
-import com.gitblit.wicket.PageRegistration;
-import com.gitblit.wicket.PageRegistration.DropDownMenuRegistration;
-import com.gitblit.wicket.PageRegistration.OtherPageLink;
-import com.gitblit.wicket.WicketUtils;
-import com.gitblit.wicket.pages.BasePage;
-
-public class NavigationPanel extends Panel {
-
-	private static final long serialVersionUID = 1L;
-
-	public NavigationPanel(String id, final Class<? extends BasePage> pageClass,
-			List<PageRegistration> registeredPages) {
-		super(id);
-
-		ListDataProvider<PageRegistration> refsDp = new ListDataProvider<PageRegistration>(
-				registeredPages);
-		DataView<PageRegistration> refsView = new DataView<PageRegistration>("navLink", refsDp) {
-			private static final long serialVersionUID = 1L;
-
-			public void populateItem(final Item<PageRegistration> item) {
-				PageRegistration entry = item.getModelObject();
-				if (entry instanceof OtherPageLink) {
-					// other link
-					OtherPageLink link = (OtherPageLink) entry;
-					Component c = new LinkPanel("link", null, getString(entry.translationKey), link.url);
-					c.setRenderBodyOnly(true);
-					item.add(c);
-				} else if (entry instanceof DropDownMenuRegistration) {
-					// drop down menu
-					DropDownMenuRegistration reg = (DropDownMenuRegistration) entry;
-					Component c = new DropDownMenu("link", getString(entry.translationKey), reg);
-					c.setRenderBodyOnly(true);
-					item.add(c);
-					WicketUtils.setCssClass(item, "dropdown");
-				} else {
-					// standard page link
-					Component c = new LinkPanel("link", null, getString(entry.translationKey),
-							entry.pageClass, entry.params);
-					c.setRenderBodyOnly(true);
-					if (entry.pageClass.equals(pageClass)) {
-						WicketUtils.setCssClass(item, "active");
-					}
-					item.add(c);
-				}
-			}
-		};
-		add(refsView);
-	}
-}
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/panels/ProjectRepositoryPanel.html b/src/com/gitblit/wicket/panels/ProjectRepositoryPanel.html
deleted file mode 100644
index 9b621d5..0000000
--- a/src/com/gitblit/wicket/panels/ProjectRepositoryPanel.html
+++ /dev/null
@@ -1,80 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml"  
-      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"  
-      xml:lang="en"  
-      lang="en"> 
-
-<wicket:panel>
-	<wicket:fragment wicket:id="repositoryAdminLinks">
-		<span class="link">
-			<a wicket:id="tree"><wicket:message key="gb.tree"></wicket:message></a>
-			| <a wicket:id="log"><wicket:message key="gb.log"></wicket:message></a>
-			| <a wicket:id="editRepository"><wicket:message key="gb.edit">[edit]</wicket:message></a>
-			| <a wicket:id="deleteRepository"><wicket:message key="gb.delete">[delete]</wicket:message></a>
-		</span>
-	</wicket:fragment>
-
-	<wicket:fragment wicket:id="repositoryOwnerLinks">
-		<span class="link">
-			<a wicket:id="tree"><wicket:message key="gb.tree"></wicket:message></a>
-			| <a wicket:id="log"><wicket:message key="gb.log"></wicket:message></a>
-			| <a wicket:id="editRepository"><wicket:message key="gb.edit">[edit]</wicket:message></a>
-		</span>
-	</wicket:fragment>
-
-	<wicket:fragment wicket:id="repositoryUserLinks">
-		<span class="link">
-			<a wicket:id="tree"><wicket:message key="gb.tree"></wicket:message></a>
-			| <a wicket:id="log"><wicket:message key="gb.log"></wicket:message></a>
-		</span>
-	</wicket:fragment>
-
-	<wicket:fragment wicket:id="originFragment">
-		<p class="originRepository" style="margin-left:20px;" ><wicket:message key="gb.forkedFrom">[forked from]</wicket:message> <span wicket:id="originRepository">[origin repository]</span></p>
-	</wicket:fragment>
-
-	<div>
-		<div style="padding-top:15px;padding-bottom:15px;margin-right:15px;">
-			<div class="pull-right" style="text-align:right;padding-right:15px;">
-				<span wicket:id="repositoryLinks"></span>
-				<div>
-					<img class="inlineIcon" wicket:id="sparkleshareIcon" />
-					<img class="inlineIcon" wicket:id="frozenIcon" />
-					<img class="inlineIcon" wicket:id="federatedIcon" />
-        						
-					<a style="text-decoration: none;" wicket:id="tickets" wicket:message="title:gb.tickets">
-						<img style="border:0px;vertical-align:middle;" src="bug_16x16.png"></img>
-					</a>
-					<a style="text-decoration: none;" wicket:id="docs" wicket:message="title:gb.docs">
-						<img style="border:0px;vertical-align:middle;" src="book_16x16.png"></img>
-					</a>
-					<a style="text-decoration: none;" wicket:id="syndication" wicket:message="title:gb.feed">
-						<img style="border:0px;vertical-align:middle;" src="feed_16x16.png"></img>
-					</a>
-				</div>
-				<span style="color: #999;font-style:italic;font-size:0.8em;" wicket:id="repositoryOwner">[owner]</span>
-			</div>	
-			
-			<div class="pageTitle" style="border:0px;">
-				<div>
-					<span class="repositorySwatch" wicket:id="repositorySwatch"></span>
-					<span class="repository" style="padding-left:3px;color:black;" wicket:id="repositoryName">[repository name]</span>
-					<img class="inlineIcon" style="vertical-align:baseline" wicket:id="accessRestrictionIcon" />
-				</div>
-				<span wicket:id="originRepository">[origin repository]</span>
-			</div>
-			
-			<div style="padding-left:20px;">
-				<div style="padding-bottom:10px" wicket:id="repositoryDescription">[repository description]</div>
-
-    			<div style="color: #999;">
-					<wicket:message key="gb.lastChange">[last change]</wicket:message> <span wicket:id="repositoryLastChange">[last change]</span>,
-					<span style="font-size:0.8em;" wicket:id="repositorySize">[repository size]</span>
-				</div>
-        
-				<div class="hidden-phone hidden-tablet" wicket:id="repositoryCloneUrl">[repository clone url]</div>
-			</div>
-		</div>
-	</div>
-</wicket:panel>
-</html>
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/panels/ProjectRepositoryPanel.java b/src/com/gitblit/wicket/panels/ProjectRepositoryPanel.java
deleted file mode 100644
index 7b4ee9f..0000000
--- a/src/com/gitblit/wicket/panels/ProjectRepositoryPanel.java
+++ /dev/null
@@ -1,229 +0,0 @@
-/*
- * 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.wicket.panels;
-
-import java.text.MessageFormat;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-
-import org.apache.wicket.Component;
-import org.apache.wicket.Localizer;
-import org.apache.wicket.PageParameters;
-import org.apache.wicket.markup.html.basic.Label;
-import org.apache.wicket.markup.html.link.BookmarkablePageLink;
-import org.apache.wicket.markup.html.link.ExternalLink;
-import org.apache.wicket.markup.html.link.Link;
-import org.apache.wicket.markup.html.panel.Fragment;
-
-import com.gitblit.Constants.AccessRestrictionType;
-import com.gitblit.GitBlit;
-import com.gitblit.Keys;
-import com.gitblit.SyndicationServlet;
-import com.gitblit.models.RepositoryModel;
-import com.gitblit.models.UserModel;
-import com.gitblit.utils.ArrayUtils;
-import com.gitblit.utils.StringUtils;
-import com.gitblit.wicket.GitBlitWebSession;
-import com.gitblit.wicket.WicketUtils;
-import com.gitblit.wicket.pages.BasePage;
-import com.gitblit.wicket.pages.DocsPage;
-import com.gitblit.wicket.pages.EditRepositoryPage;
-import com.gitblit.wicket.pages.LogPage;
-import com.gitblit.wicket.pages.SummaryPage;
-import com.gitblit.wicket.pages.TicketsPage;
-import com.gitblit.wicket.pages.TreePage;
-
-public class ProjectRepositoryPanel extends BasePanel {
-
-	private static final long serialVersionUID = 1L;
-
-	public ProjectRepositoryPanel(String wicketId, Localizer localizer, Component parent,
-			final boolean isAdmin, final RepositoryModel entry,
-			final Map<AccessRestrictionType, String> accessRestrictions) {
-		super(wicketId);
-
-		final boolean showSwatch = GitBlit.getBoolean(Keys.web.repositoryListSwatches, true);
-		final boolean gitServlet = GitBlit.getBoolean(Keys.git.enableGitServlet, true);
-		final boolean showSize = GitBlit.getBoolean(Keys.web.showRepositorySizes, true);
-
-		// repository swatch
-		Component swatch;
-		if (entry.isBare) {
-			swatch = new Label("repositorySwatch", "&nbsp;").setEscapeModelStrings(false);
-		} else {
-			swatch = new Label("repositorySwatch", "!");
-			WicketUtils.setHtmlTooltip(swatch, localizer.getString("gb.workingCopyWarning", parent));
-		}
-		WicketUtils.setCssBackground(swatch, entry.toString());
-		add(swatch);
-		swatch.setVisible(showSwatch);
-
-		PageParameters pp = WicketUtils.newRepositoryParameter(entry.name);
-		add(new LinkPanel("repositoryName", "list", StringUtils.getRelativePath(entry.projectPath,
-				StringUtils.stripDotGit(entry.name)), SummaryPage.class, pp));
-		add(new Label("repositoryDescription", entry.description).setVisible(!StringUtils
-				.isEmpty(entry.description)));
-
-		if (StringUtils.isEmpty(entry.originRepository)) {
-			add(new Label("originRepository").setVisible(false));
-		} else {
-			Fragment forkFrag = new Fragment("originRepository", "originFragment", this);
-			forkFrag.add(new LinkPanel("originRepository", null, StringUtils.stripDotGit(entry.originRepository), 
-					SummaryPage.class, WicketUtils.newRepositoryParameter(entry.originRepository)));
-			add(forkFrag);
-		}
-
-		if (entry.isSparkleshared()) {
-			add(WicketUtils.newImage("sparkleshareIcon", "star_16x16.png", localizer.getString("gb.isSparkleshared", parent)));
-		} else {
-			add(WicketUtils.newClearPixel("sparkleshareIcon").setVisible(false));
-		}
-
-		add(new BookmarkablePageLink<Void>("tickets", TicketsPage.class, pp).setVisible(entry.useTickets));
-		add(new BookmarkablePageLink<Void>("docs", DocsPage.class, pp).setVisible(entry.useDocs));
-
-		if (entry.isFrozen) {
-			add(WicketUtils.newImage("frozenIcon", "cold_16x16.png", localizer.getString("gb.isFrozen", parent)));
-		} else {
-			add(WicketUtils.newClearPixel("frozenIcon").setVisible(false));
-		}
-
-		if (entry.isFederated) {
-			add(WicketUtils.newImage("federatedIcon", "federated_16x16.png", localizer.getString("gb.isFederated", parent)));
-		} else {
-			add(WicketUtils.newClearPixel("federatedIcon").setVisible(false));
-		}
-		switch (entry.accessRestriction) {
-		case NONE:
-			add(WicketUtils.newBlankImage("accessRestrictionIcon").setVisible(false));
-			break;
-		case PUSH:
-			add(WicketUtils.newImage("accessRestrictionIcon", "lock_go_16x16.png",
-					accessRestrictions.get(entry.accessRestriction)));
-			break;
-		case CLONE:
-			add(WicketUtils.newImage("accessRestrictionIcon", "lock_pull_16x16.png",
-					accessRestrictions.get(entry.accessRestriction)));
-			break;
-		case VIEW:
-			add(WicketUtils.newImage("accessRestrictionIcon", "shield_16x16.png",
-					accessRestrictions.get(entry.accessRestriction)));
-			break;
-		default:
-			add(WicketUtils.newBlankImage("accessRestrictionIcon"));
-		}
-
-		if (ArrayUtils.isEmpty(entry.owners)) {
-			add(new Label("repositoryOwner").setVisible(false));
-		} else {
-			String owner = "";
-			for (String username : entry.owners) {
-				UserModel ownerModel = GitBlit.self().getUserModel(username);
-			
-				if (ownerModel != null) {
-					owner = ownerModel.getDisplayName();
-				}				
-			}
-			if (entry.owners.size() > 1) {
-				owner += ", ...";
-			}
-			Label ownerLabel = (new Label("repositoryOwner", owner + " (" +
-					localizer.getString("gb.owner", parent) + ")"));
-			WicketUtils.setHtmlTooltip(ownerLabel, ArrayUtils.toString(entry.owners));
-			add(ownerLabel);
-		}
-
-		UserModel user = GitBlitWebSession.get().getUser();
-		if (user == null) {
-			user = UserModel.ANONYMOUS;
-		}
-		Fragment repositoryLinks;
-		boolean showOwner = entry.isOwner(user.username);
-		// owner of personal repository gets admin powers
-		boolean showAdmin = isAdmin || entry.isUsersPersonalRepository(user.username);
-
-		if (showAdmin || showOwner) {
-			repositoryLinks = new Fragment("repositoryLinks", showAdmin ? "repositoryAdminLinks"
-					: "repositoryOwnerLinks", this);
-			repositoryLinks.add(new BookmarkablePageLink<Void>("editRepository", EditRepositoryPage.class,
-					WicketUtils.newRepositoryParameter(entry.name)));
-			if (showAdmin) {
-				Link<Void> deleteLink = new Link<Void>("deleteRepository") {
-
-					private static final long serialVersionUID = 1L;
-
-					@Override
-					public void onClick() {
-						if (GitBlit.self().deleteRepositoryModel(entry)) {
-							// redirect to the owning page
-							if (entry.isPersonalRepository()) {
-								setResponsePage(getPage().getClass(), WicketUtils.newUsernameParameter(entry.projectPath.substring(1)));
-							} else {
-								setResponsePage(getPage().getClass(), WicketUtils.newProjectParameter(entry.projectPath));
-							}
-						} else {
-							error(MessageFormat.format(getString("gb.repositoryDeleteFailed"), entry));
-						}
-					}
-				};
-				deleteLink.add(new JavascriptEventConfirmation("onclick", MessageFormat.format(
-						localizer.getString("gb.deleteRepository", parent), entry)));
-				repositoryLinks.add(deleteLink);
-			}
-		} else {
-			repositoryLinks = new Fragment("repositoryLinks", "repositoryUserLinks", this);
-		}
-
-		repositoryLinks.add(new BookmarkablePageLink<Void>("tree", TreePage.class, WicketUtils
-				.newRepositoryParameter(entry.name)).setEnabled(entry.hasCommits));
-
-		repositoryLinks.add(new BookmarkablePageLink<Void>("log", LogPage.class, WicketUtils
-				.newRepositoryParameter(entry.name)).setEnabled(entry.hasCommits));
-
-		add(repositoryLinks);
-
-		String lastChange;
-		if (entry.lastChange.getTime() == 0) {
-			lastChange = "--";
-		} else {
-			lastChange = getTimeUtils().timeAgo(entry.lastChange);
-		}
-		Label lastChangeLabel = new Label("repositoryLastChange", lastChange);
-		add(lastChangeLabel);
-		WicketUtils.setCssClass(lastChangeLabel, getTimeUtils().timeAgoCss(entry.lastChange));
-
-		if (entry.hasCommits) {
-			// Existing repository
-			add(new Label("repositorySize", entry.size).setVisible(showSize));
-		} else {
-			// New repository
-			add(new Label("repositorySize", localizer.getString("gb.empty", parent)).setEscapeModelStrings(false));
-		}
-
-		add(new ExternalLink("syndication", SyndicationServlet.asLink("", entry.name, null, 0)));
-
-		List<String> repositoryUrls = new ArrayList<String>();
-		if (gitServlet) {
-			// add the Gitblit repository url
-			repositoryUrls.add(BasePage.getRepositoryUrl(entry));
-		}
-		repositoryUrls.addAll(GitBlit.self().getOtherCloneUrls(entry.name));
-
-		String primaryUrl = ArrayUtils.isEmpty(repositoryUrls) ? "" : repositoryUrls.remove(0);
-		add(new RepositoryUrlPanel("repositoryCloneUrl", primaryUrl));
-	}
-}
diff --git a/src/com/gitblit/wicket/panels/RefsPanel.java b/src/com/gitblit/wicket/panels/RefsPanel.java
deleted file mode 100644
index 3ba22c0..0000000
--- a/src/com/gitblit/wicket/panels/RefsPanel.java
+++ /dev/null
@@ -1,156 +0,0 @@
-/*
- * 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.wicket.panels;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
-import java.util.Map;
-
-import org.apache.wicket.Component;
-import org.apache.wicket.markup.html.basic.Label;
-import org.apache.wicket.markup.html.panel.Panel;
-import org.apache.wicket.markup.repeater.Item;
-import org.apache.wicket.markup.repeater.data.DataView;
-import org.apache.wicket.markup.repeater.data.ListDataProvider;
-import org.eclipse.jgit.lib.Constants;
-import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.revwalk.RevCommit;
-
-import com.gitblit.models.RefModel;
-import com.gitblit.wicket.WicketUtils;
-import com.gitblit.wicket.pages.CommitPage;
-import com.gitblit.wicket.pages.LogPage;
-import com.gitblit.wicket.pages.RepositoryPage;
-import com.gitblit.wicket.pages.TagPage;
-
-public class RefsPanel extends Panel {
-
-	private static final long serialVersionUID = 1L;
-
-	public RefsPanel(String id, final String repositoryName, RevCommit c,
-			Map<ObjectId, List<RefModel>> refs) {
-		this(id, repositoryName, refs.get(c.getId()));
-	}
-
-	public RefsPanel(String id, final String repositoryName, List<RefModel> refs) {
-		super(id);
-		if (refs == null) {
-			refs = new ArrayList<RefModel>();
-		}
-		Collections.sort(refs, new Comparator<RefModel>() {
-			@Override
-			public int compare(RefModel o1, RefModel o2) {
-				// sort remote heads last, otherwise sort by name
-				// this is so we can insert a break on the refs panel
-				// [head][branch][branch][tag][tag]
-				// [remote][remote][remote]
-				boolean remote1 = o1.displayName.startsWith(Constants.R_REMOTES);
-				boolean remote2 = o2.displayName.startsWith(Constants.R_REMOTES);
-				if (remote1 && remote2) {
-					// both are remote heads, sort by name
-					return o1.displayName.compareTo(o2.displayName);	
-				}
-				if (remote1) {
-					// o1 is remote, o2 comes first
-					return 1;
-				}
-				if (remote2) {
-					// remote is o2, o1 comes first
-					return -1;
-				}
-				// standard sort
-				return o1.displayName.compareTo(o2.displayName);
-			}
-		});
-		
-		// count remote and determine if we should insert a break
-		int remoteCount = 0;
-		for (RefModel ref : refs) {
-			if (ref.displayName.startsWith(Constants.R_REMOTES)) {
-				remoteCount++;
-			}
-		}
-		final boolean shouldBreak = remoteCount < refs.size();
-		
-		ListDataProvider<RefModel> refsDp = new ListDataProvider<RefModel>(refs);
-		DataView<RefModel> refsView = new DataView<RefModel>("ref", refsDp) {
-			private static final long serialVersionUID = 1L;
-			private boolean alreadyInsertedBreak = !shouldBreak;
-
-			public void populateItem(final Item<RefModel> item) {
-				RefModel entry = item.getModelObject();
-				String name = entry.displayName;
-				String objectid = entry.getReferencedObjectId().getName();
-				boolean breakLine = false;
-				Class<? extends RepositoryPage> linkClass = CommitPage.class;
-				String cssClass = "";
-				if (name.startsWith(Constants.R_HEADS)) {
-					// local branch
-					linkClass = LogPage.class;
-					name = name.substring(Constants.R_HEADS.length());
-					cssClass = "localBranch";
-				} else if (name.equals(Constants.HEAD)) {
-					// local head
-					linkClass = LogPage.class;
-					cssClass = "headRef";
-				} else if (name.startsWith(Constants.R_REMOTES)) {
-					// remote branch
-					linkClass = LogPage.class;
-					name = name.substring(Constants.R_REMOTES.length());
-					cssClass = "remoteBranch";
-					if (!alreadyInsertedBreak) {
-						breakLine = true;
-						alreadyInsertedBreak = true;
-					}
-				} else if (name.startsWith(Constants.R_TAGS)) {
-					// tag
-					if (entry.isAnnotatedTag()) {
-						linkClass = TagPage.class;
-						objectid = entry.getObjectId().getName();
-					} else {
-						linkClass = CommitPage.class;
-						objectid = entry.getReferencedObjectId().getName();
-					}
-					name = name.substring(Constants.R_TAGS.length());
-					cssClass = "tagRef";
-				} else if (name.startsWith(Constants.R_NOTES)) {
-					// codereview refs
-					linkClass = CommitPage.class;
-					cssClass = "otherRef";
-				} else if (name.startsWith(com.gitblit.Constants.R_GITBLIT)) {
-					// gitblit refs
-					linkClass = LogPage.class;
-					cssClass = "otherRef";
-					name = name.substring(com.gitblit.Constants.R_GITBLIT.length());
-				}
-
-				Component c = new LinkPanel("refName", null, name, linkClass,
-						WicketUtils.newObjectParameter(repositoryName, objectid));
-				WicketUtils.setCssClass(c, cssClass);
-				WicketUtils.setHtmlTooltip(c, name);
-				item.add(c);
-				Label lb = new Label("lineBreak", "<br/>");
-				lb.setVisible(breakLine);
-				lb.setRenderBodyOnly(true);
-				item.add(lb.setEscapeModelStrings(false));
-				item.setRenderBodyOnly(true);
-			}
-		};
-		add(refsView);
-	}
-}
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/panels/RepositoriesPanel.java b/src/com/gitblit/wicket/panels/RepositoriesPanel.java
deleted file mode 100644
index 726af61..0000000
--- a/src/com/gitblit/wicket/panels/RepositoriesPanel.java
+++ /dev/null
@@ -1,557 +0,0 @@
-/*
- * 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.wicket.panels;
-
-import java.text.MessageFormat;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-
-import org.apache.wicket.Component;
-import org.apache.wicket.PageParameters;
-import org.apache.wicket.extensions.markup.html.repeater.data.sort.OrderByBorder;
-import org.apache.wicket.extensions.markup.html.repeater.util.SortParam;
-import org.apache.wicket.extensions.markup.html.repeater.util.SortableDataProvider;
-import org.apache.wicket.markup.html.basic.Label;
-import org.apache.wicket.markup.html.link.BookmarkablePageLink;
-import org.apache.wicket.markup.html.link.ExternalLink;
-import org.apache.wicket.markup.html.link.Link;
-import org.apache.wicket.markup.html.panel.Fragment;
-import org.apache.wicket.markup.repeater.Item;
-import org.apache.wicket.markup.repeater.data.DataView;
-import org.apache.wicket.markup.repeater.data.IDataProvider;
-import org.apache.wicket.markup.repeater.data.ListDataProvider;
-import org.apache.wicket.model.IModel;
-import org.apache.wicket.model.Model;
-
-import com.gitblit.Constants.AccessRestrictionType;
-import com.gitblit.GitBlit;
-import com.gitblit.Keys;
-import com.gitblit.SyndicationServlet;
-import com.gitblit.models.ProjectModel;
-import com.gitblit.models.RepositoryModel;
-import com.gitblit.models.UserModel;
-import com.gitblit.utils.ArrayUtils;
-import com.gitblit.utils.StringUtils;
-import com.gitblit.wicket.GitBlitWebSession;
-import com.gitblit.wicket.WicketUtils;
-import com.gitblit.wicket.pages.BasePage;
-import com.gitblit.wicket.pages.EditRepositoryPage;
-import com.gitblit.wicket.pages.EmptyRepositoryPage;
-import com.gitblit.wicket.pages.ProjectPage;
-import com.gitblit.wicket.pages.RepositoriesPage;
-import com.gitblit.wicket.pages.SummaryPage;
-import com.gitblit.wicket.pages.UserPage;
-
-public class RepositoriesPanel extends BasePanel {
-
-	private static final long serialVersionUID = 1L;
-
-	public RepositoriesPanel(String wicketId, final boolean showAdmin, final boolean showManagement,
-			List<RepositoryModel> models, boolean enableLinks,
-			final Map<AccessRestrictionType, String> accessRestrictionTranslations) {
-		super(wicketId);
-
-		final boolean linksActive = enableLinks;
-		final boolean showSize = GitBlit.getBoolean(Keys.web.showRepositorySizes, true);
-
-		final UserModel user = GitBlitWebSession.get().getUser();
-
-		final IDataProvider<RepositoryModel> dp;
-
-		Fragment managementLinks;
-		if (showAdmin) {
-			// user is admin
-			managementLinks = new Fragment("managementPanel", "adminLinks", this);
-			managementLinks.add(new Link<Void>("clearCache") {
-
-				private static final long serialVersionUID = 1L;
-
-				@Override
-				public void onClick() {
-					GitBlit.self().resetRepositoryListCache();
-					setResponsePage(RepositoriesPage.class);
-				}
-			}.setVisible(GitBlit.getBoolean(Keys.git.cacheRepositoryList, true)));
-			managementLinks.add(new BookmarkablePageLink<Void>("newRepository", EditRepositoryPage.class));
-			add(managementLinks);
-		} else if (showManagement && user != null && user.canCreate()) {
-			// user can create personal repositories
-			managementLinks = new Fragment("managementPanel", "personalLinks", this);
-			managementLinks.add(new BookmarkablePageLink<Void>("newRepository", EditRepositoryPage.class));
-			add(managementLinks);
-		} else {
-			// user has no management permissions
-			add (new Label("managementPanel").setVisible(false));
-		}
-
-		if (GitBlit.getString(Keys.web.repositoryListType, "flat").equalsIgnoreCase("grouped")) {
-			List<RepositoryModel> rootRepositories = new ArrayList<RepositoryModel>();
-			Map<String, List<RepositoryModel>> groups = new HashMap<String, List<RepositoryModel>>();
-			for (RepositoryModel model : models) {
-				String rootPath = StringUtils.getRootPath(model.name);
-				if (StringUtils.isEmpty(rootPath)) {
-					// root repository
-					rootRepositories.add(model);
-				} else {
-					// non-root, grouped repository
-					if (!groups.containsKey(rootPath)) {
-						groups.put(rootPath, new ArrayList<RepositoryModel>());
-					}
-					groups.get(rootPath).add(model);
-				}
-			}
-			List<String> roots = new ArrayList<String>(groups.keySet());
-			Collections.sort(roots);
-
-			if (rootRepositories.size() > 0) {
-				// inject the root repositories at the top of the page
-				roots.add(0, "");
-				groups.put("", rootRepositories);
-			}
-						
-			List<RepositoryModel> groupedModels = new ArrayList<RepositoryModel>();
-			for (String root : roots) {
-				List<RepositoryModel> subModels = groups.get(root);
-				ProjectModel project = GitBlit.self().getProjectModel(root);
-				GroupRepositoryModel group = new GroupRepositoryModel(project.name, subModels.size());
-				if (project != null) {
-					group.title = project.title;
-					group.description = project.description;
-				}
-				groupedModels.add(group);
-				Collections.sort(subModels);
-				groupedModels.addAll(subModels);
-			}
-			dp = new RepositoriesProvider(groupedModels);
-		} else {
-			dp = new SortableRepositoriesProvider(models);
-		}
-
-		final String baseUrl = WicketUtils.getGitblitURL(getRequest());
-		final boolean showSwatch = GitBlit.getBoolean(Keys.web.repositoryListSwatches, true);
-		
-		DataView<RepositoryModel> dataView = new DataView<RepositoryModel>("row", dp) {
-			private static final long serialVersionUID = 1L;
-			int counter;
-			String currGroupName;
-
-			@Override
-			protected void onBeforeRender() {
-				super.onBeforeRender();
-				counter = 0;
-			}
-
-			public void populateItem(final Item<RepositoryModel> item) {
-				final RepositoryModel entry = item.getModelObject();
-				if (entry instanceof GroupRepositoryModel) {
-					GroupRepositoryModel groupRow = (GroupRepositoryModel) entry;
-					currGroupName = entry.name;
-					Fragment row = new Fragment("rowContent", "groupRepositoryRow", this);
-					item.add(row);
-					
-					String name = groupRow.name;
-					if (name.charAt(0) == '~') {
-						// user page
-						String username = name.substring(1);
-						UserModel user = GitBlit.self().getUserModel(username);
-						row.add(new LinkPanel("groupName", null, (user == null ? username : user.getDisplayName()) + " (" + groupRow.count + ")", UserPage.class, WicketUtils.newUsernameParameter(username)));
-						row.add(new Label("groupDescription", getString("gb.personalRepositories")));
-					} else {
-						// project page
-						row.add(new LinkPanel("groupName", null, groupRow.toString(), ProjectPage.class, WicketUtils.newProjectParameter(entry.name)));
-						row.add(new Label("groupDescription", entry.description == null ? "":entry.description));
-					}
-					WicketUtils.setCssClass(item, "group");
-					// reset counter so that first row is light background
-					counter = 0;
-					return;
-				}
-				Fragment row = new Fragment("rowContent", "repositoryRow", this);
-				item.add(row);
-
-				// try to strip group name for less cluttered list
-				String repoName = entry.toString();
-				if (!StringUtils.isEmpty(currGroupName) && (repoName.indexOf('/') > -1)) {
-					repoName = repoName.substring(currGroupName.length() + 1);
-				}
-								
-				// repository swatch
-				Component swatch;
-				if (entry.isBare){
-					swatch = new Label("repositorySwatch", "&nbsp;").setEscapeModelStrings(false);
-				} else {
-					swatch = new Label("repositorySwatch", "!");
-					WicketUtils.setHtmlTooltip(swatch, getString("gb.workingCopyWarning"));
-				}
-				WicketUtils.setCssBackground(swatch, entry.toString());
-				row.add(swatch);
-				swatch.setVisible(showSwatch);
-
-				if (linksActive) {
-					Class<? extends BasePage> linkPage;
-					if (entry.hasCommits) {
-						// repository has content
-						linkPage = SummaryPage.class;
-					} else {
-						// new/empty repository OR proposed repository
-						linkPage = EmptyRepositoryPage.class;
-					}
-
-					PageParameters pp = WicketUtils.newRepositoryParameter(entry.name);
-					row.add(new LinkPanel("repositoryName", "list", repoName, linkPage, pp));
-					row.add(new LinkPanel("repositoryDescription", "list", entry.description,
-							linkPage, pp));
-				} else {
-					// no links like on a federation page
-					row.add(new Label("repositoryName", repoName));
-					row.add(new Label("repositoryDescription", entry.description));
-				}
-				if (entry.hasCommits) {
-					// Existing repository
-					row.add(new Label("repositorySize", entry.size).setVisible(showSize));
-				} else {
-					// New repository
-					row.add(new Label("repositorySize", "<span class='empty'>(" + getString("gb.empty") + ")</span>")
-							.setEscapeModelStrings(false));
-				}
-
-				if (entry.isSparkleshared()) {
-					row.add(WicketUtils.newImage("sparkleshareIcon", "star_16x16.png",
-							getString("gb.isSparkleshared")));
-				} else {
-					row.add(WicketUtils.newClearPixel("sparkleshareIcon").setVisible(false));
-				}
-				
-				if (entry.isFork()) {
-					row.add(WicketUtils.newImage("forkIcon", "commit_divide_16x16.png",
-							getString("gb.isFork")));
-				} else {
-					row.add(WicketUtils.newClearPixel("forkIcon").setVisible(false));
-				}
-
-				if (entry.useTickets) {
-					row.add(WicketUtils.newImage("ticketsIcon", "bug_16x16.png",
-							getString("gb.tickets")));
-				} else {
-					row.add(WicketUtils.newBlankImage("ticketsIcon"));
-				}
-
-				if (entry.useDocs) {
-					row.add(WicketUtils
-							.newImage("docsIcon", "book_16x16.png", getString("gb.docs")));
-				} else {
-					row.add(WicketUtils.newBlankImage("docsIcon"));
-				}
-
-				if (entry.isFrozen) {
-					row.add(WicketUtils.newImage("frozenIcon", "cold_16x16.png",
-							getString("gb.isFrozen")));
-				} else {
-					row.add(WicketUtils.newClearPixel("frozenIcon").setVisible(false));
-				}
-
-				if (entry.isFederated) {
-					row.add(WicketUtils.newImage("federatedIcon", "federated_16x16.png",
-							getString("gb.isFederated")));
-				} else {
-					row.add(WicketUtils.newClearPixel("federatedIcon").setVisible(false));
-				}
-				switch (entry.accessRestriction) {
-				case NONE:
-					row.add(WicketUtils.newBlankImage("accessRestrictionIcon"));
-					break;
-				case PUSH:
-					row.add(WicketUtils.newImage("accessRestrictionIcon", "lock_go_16x16.png",
-							accessRestrictionTranslations.get(entry.accessRestriction)));
-					break;
-				case CLONE:
-					row.add(WicketUtils.newImage("accessRestrictionIcon", "lock_pull_16x16.png",
-							accessRestrictionTranslations.get(entry.accessRestriction)));
-					break;
-				case VIEW:
-					row.add(WicketUtils.newImage("accessRestrictionIcon", "shield_16x16.png",
-							accessRestrictionTranslations.get(entry.accessRestriction)));
-					break;
-				default:
-					row.add(WicketUtils.newBlankImage("accessRestrictionIcon"));
-				}
-
-				String owner = "";
-				if (!ArrayUtils.isEmpty(entry.owners)) {
-					// display first owner
-					for (String username : entry.owners) {
-						UserModel ownerModel = GitBlit.self().getUserModel(username);
-						if (ownerModel != null) {
-							owner = ownerModel.getDisplayName();
-							break;
-						}
-					}
-					if (entry.owners.size() > 1) {
-						owner += ", ...";
-					}
-				}
-				Label ownerLabel = new Label("repositoryOwner", owner);
-				WicketUtils.setHtmlTooltip(ownerLabel, ArrayUtils.toString(entry.owners));
-				row.add(ownerLabel);
-
-				String lastChange;
-				if (entry.lastChange.getTime() == 0) {
-					lastChange = "--";
-				} else {
-					lastChange = getTimeUtils().timeAgo(entry.lastChange);
-				}
-				Label lastChangeLabel = new Label("repositoryLastChange", lastChange);
-				row.add(lastChangeLabel);
-				WicketUtils.setCssClass(lastChangeLabel, getTimeUtils().timeAgoCss(entry.lastChange));
-
-				boolean showOwner = user != null && entry.isOwner(user.username);
-				boolean myPersonalRepository = showOwner && entry.isUsersPersonalRepository(user.username);
-				if (showAdmin || myPersonalRepository) {
-					Fragment repositoryLinks = new Fragment("repositoryLinks",
-							"repositoryAdminLinks", this);
-					repositoryLinks.add(new BookmarkablePageLink<Void>("editRepository",
-							EditRepositoryPage.class, WicketUtils
-									.newRepositoryParameter(entry.name)));
-					Link<Void> deleteLink = new Link<Void>("deleteRepository") {
-
-						private static final long serialVersionUID = 1L;
-
-						@Override
-						public void onClick() {
-							if (GitBlit.self().deleteRepositoryModel(entry)) {
-								if (dp instanceof SortableRepositoriesProvider) {
-									info(MessageFormat.format(getString("gb.repositoryDeleted"), entry));
-									((SortableRepositoriesProvider) dp).remove(entry);
-								} else {
-									setResponsePage(getPage().getClass(), getPage().getPageParameters());
-								}
-							} else {
-								error(MessageFormat.format(getString("gb.repositoryDeleteFailed"), entry));
-							}
-						}
-					};
-					deleteLink.add(new JavascriptEventConfirmation("onclick", MessageFormat.format(
-							getString("gb.deleteRepository"), entry)));
-					repositoryLinks.add(deleteLink);
-					row.add(repositoryLinks);
-				} else if (showOwner) {
-					Fragment repositoryLinks = new Fragment("repositoryLinks",
-							"repositoryOwnerLinks", this);
-					repositoryLinks.add(new BookmarkablePageLink<Void>("editRepository",
-							EditRepositoryPage.class, WicketUtils
-									.newRepositoryParameter(entry.name)));
-					row.add(repositoryLinks);
-				} else {
-					row.add(new Label("repositoryLinks"));
-				}
-				row.add(new ExternalLink("syndication", SyndicationServlet.asLink(baseUrl,
-						entry.name, null, 0)).setVisible(linksActive));
-				WicketUtils.setAlternatingBackground(item, counter);
-				counter++;
-			}
-		};
-		add(dataView);
-
-		if (dp instanceof SortableDataProvider<?>) {
-			// add sortable header
-			SortableDataProvider<?> sdp = (SortableDataProvider<?>) dp;
-			Fragment fragment = new Fragment("headerContent", "flatRepositoryHeader", this);
-			fragment.add(newSort("orderByRepository", SortBy.repository, sdp, dataView));
-			fragment.add(newSort("orderByDescription", SortBy.description, sdp, dataView));
-			fragment.add(newSort("orderByOwner", SortBy.owner, sdp, dataView));
-			fragment.add(newSort("orderByDate", SortBy.date, sdp, dataView));
-			add(fragment);
-		} else {
-			// not sortable
-			Fragment fragment = new Fragment("headerContent", "groupRepositoryHeader", this);
-			add(fragment);
-		}
-	}
-
-	private static class GroupRepositoryModel extends RepositoryModel {
-
-		private static final long serialVersionUID = 1L;
-
-		int count;
-		String title;
-
-		GroupRepositoryModel(String name, int count) {
-			super(name, "", "", new Date(0));
-			this.count = count;
-		}
-
-		@Override
-		public String toString() {
-			return (StringUtils.isEmpty(title) ? name  : title) + " (" + count + ")";
-		}
-	}
-
-	protected enum SortBy {
-		repository, description, owner, date;
-	}
-
-	protected OrderByBorder newSort(String wicketId, SortBy field, SortableDataProvider<?> dp,
-			final DataView<?> dataView) {
-		return new OrderByBorder(wicketId, field.name(), dp) {
-			private static final long serialVersionUID = 1L;
-
-			@Override
-			protected void onSortChanged() {
-				dataView.setCurrentPage(0);
-			}
-		};
-	}
-
-	private static class RepositoriesProvider extends ListDataProvider<RepositoryModel> {
-
-		private static final long serialVersionUID = 1L;
-
-		public RepositoriesProvider(List<RepositoryModel> list) {
-			super(list);
-		}
-
-		@Override
-		public List<RepositoryModel> getData() {
-			return super.getData();
-		}
-
-		public void remove(RepositoryModel model) {
-			int index = getData().indexOf(model);
-			RepositoryModel groupModel = null;
-			if (index == (getData().size() - 1)) {
-				// last element
-				if (index > 0) {
-					// previous element is group header, then this is last
-					// repository in group. remove group too.
-					if (getData().get(index - 1) instanceof GroupRepositoryModel) {
-						groupModel = getData().get(index - 1);
-					}
-				}
-			} else if (index < (getData().size() - 1)) {
-				// not last element. check next element for group match.
-				if (getData().get(index - 1) instanceof GroupRepositoryModel
-						&& getData().get(index + 1) instanceof GroupRepositoryModel) {
-					// repository is sandwiched by group headers so this
-					// repository is the only element in the group. remove
-					// group.
-					groupModel = getData().get(index - 1);
-				}
-			}
-
-			if (groupModel == null) {
-				// Find the group and decrement the count
-				for (int i = index; i >= 0; i--) {
-					if (getData().get(i) instanceof GroupRepositoryModel) {
-						((GroupRepositoryModel) getData().get(i)).count--;
-						break;
-					}
-				}
-			} else {
-				// Remove the group header
-				getData().remove(groupModel);
-			}
-
-			getData().remove(model);
-		}
-	}
-
-	private static class SortableRepositoriesProvider extends SortableDataProvider<RepositoryModel> {
-
-		private static final long serialVersionUID = 1L;
-
-		private List<RepositoryModel> list;
-
-		protected SortableRepositoriesProvider(List<RepositoryModel> list) {
-			this.list = list;
-			setSort(SortBy.date.name(), false);
-		}
-
-		public void remove(RepositoryModel model) {
-			list.remove(model);
-		}
-
-		@Override
-		public int size() {
-			if (list == null) {
-				return 0;
-			}
-			return list.size();
-		}
-
-		@Override
-		public IModel<RepositoryModel> model(RepositoryModel header) {
-			return new Model<RepositoryModel>(header);
-		}
-
-		@Override
-		public Iterator<RepositoryModel> iterator(int first, int count) {
-			SortParam sp = getSort();
-			String prop = sp.getProperty();
-			final boolean asc = sp.isAscending();
-
-			if (prop == null || prop.equals(SortBy.date.name())) {
-				Collections.sort(list, new Comparator<RepositoryModel>() {
-					@Override
-					public int compare(RepositoryModel o1, RepositoryModel o2) {
-						if (asc) {
-							return o1.lastChange.compareTo(o2.lastChange);
-						}
-						return o2.lastChange.compareTo(o1.lastChange);
-					}
-				});
-			} else if (prop.equals(SortBy.repository.name())) {
-				Collections.sort(list, new Comparator<RepositoryModel>() {
-					@Override
-					public int compare(RepositoryModel o1, RepositoryModel o2) {
-						if (asc) {
-							return o1.name.compareTo(o2.name);
-						}
-						return o2.name.compareTo(o1.name);
-					}
-				});
-			} else if (prop.equals(SortBy.owner.name())) {
-				Collections.sort(list, new Comparator<RepositoryModel>() {
-					@Override
-					public int compare(RepositoryModel o1, RepositoryModel o2) {
-						String own1 = ArrayUtils.toString(o1.owners);
-						String own2 = ArrayUtils.toString(o2.owners);
-						if (asc) {
-							return own1.compareTo(own2);
-						}
-						return own2.compareTo(own1);
-					}
-				});
-			} else if (prop.equals(SortBy.description.name())) {
-				Collections.sort(list, new Comparator<RepositoryModel>() {
-					@Override
-					public int compare(RepositoryModel o1, RepositoryModel o2) {
-						if (asc) {
-							return o1.description.compareTo(o2.description);
-						}
-						return o2.description.compareTo(o1.description);
-					}
-				});
-			}
-			return list.subList(first, first + count).iterator();
-		}
-	}
-}
diff --git a/src/com/gitblit/wicket/panels/RepositoryUrlPanel.html b/src/com/gitblit/wicket/panels/RepositoryUrlPanel.html
deleted file mode 100644
index d7c76f1..0000000
--- a/src/com/gitblit/wicket/panels/RepositoryUrlPanel.html
+++ /dev/null
@@ -1,30 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml"  
-      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"  
-      xml:lang="en"  
-      lang="en"> 
-
-<wicket:panel>
-	<span wicket:id="repositoryUrl" style="color: blue;">[repository url]</span><span class="hidden-phone hidden-tablet" wicket:id="copyFunction"></span>
-    
-    <!-- Plain JavaScript manual copy & paste -->
-    <wicket:fragment wicket:id="jsPanel">
-    	<span style="vertical-align:baseline;">
-    		<img wicket:id="copyIcon" wicket:message="title:gb.copyToClipboard"></img>
-    	</span>
-    </wicket:fragment>
-    
-    <!-- flash-based button-press copy & paste -->
-    <wicket:fragment wicket:id="clippyPanel">
-   		<object wicket:message="title:gb.copyToClipboard" style="vertical-align:middle;"
-   			wicket:id="clippy"
-   			width="14" 
-   			height="14"
-   			bgcolor="#ffffff" 
-       		quality="high"
-       		wmode="transparent"
-       		scale="noscale"
-       		allowScriptAccess="always"></object>
-	</wicket:fragment>
-</wicket:panel>
-</html>
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/panels/RepositoryUrlPanel.java b/src/com/gitblit/wicket/panels/RepositoryUrlPanel.java
deleted file mode 100644
index 58df028..0000000
--- a/src/com/gitblit/wicket/panels/RepositoryUrlPanel.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * 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.wicket.panels;
-
-import org.apache.wicket.markup.html.basic.Label;
-import org.apache.wicket.markup.html.image.ContextImage;
-import org.apache.wicket.markup.html.panel.Fragment;
-
-import com.gitblit.GitBlit;
-import com.gitblit.Keys;
-import com.gitblit.utils.StringUtils;
-import com.gitblit.wicket.WicketUtils;
-
-public class RepositoryUrlPanel extends BasePanel {
-
-	private static final long serialVersionUID = 1L;
-
-	public RepositoryUrlPanel(String wicketId, String url) {
-		super(wicketId);
-		add(new Label("repositoryUrl", url));
-		if (GitBlit.getBoolean(Keys.web.allowFlashCopyToClipboard, true)) {
-			// clippy: flash-based copy & paste
-			Fragment fragment = new Fragment("copyFunction", "clippyPanel", this);
-			String baseUrl = WicketUtils.getGitblitURL(getRequest());
-			ShockWaveComponent clippy = new ShockWaveComponent("clippy", baseUrl + "/clippy.swf");
-			clippy.setValue("flashVars", "text=" + StringUtils.encodeURL(url));
-			fragment.add(clippy);
-			add(fragment);
-		} else {
-			// javascript: manual copy & paste with modal browser prompt dialog
-			Fragment fragment = new Fragment("copyFunction", "jsPanel", this);
-			ContextImage img = WicketUtils.newImage("copyIcon", "clippy.png");
-			img.add(new JavascriptTextPrompt("onclick", "Copy to Clipboard (Ctrl+C, Enter)", url));
-			fragment.add(img);
-			add(fragment);
-		}
-	}
-}
diff --git a/src/com/gitblit/wicket/panels/TagsPanel.html b/src/com/gitblit/wicket/panels/TagsPanel.html
deleted file mode 100644
index ba9f15d..0000000
--- a/src/com/gitblit/wicket/panels/TagsPanel.html
+++ /dev/null
@@ -1,51 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml"  
-      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"  
-      xml:lang="en"  
-      lang="en"> 
-
-<body>
-<wicket:panel>
-
-	<!-- tags -->
-	<div class="header"><i class="icon-tags" style="vertical-align: middle;"></i> <b><span wicket:id="header">[tags header]</span></b></div>	
-	<table class="pretty">
-		<tbody>
-    		<tr wicket:id="tag">
-    			<td class="date"><span wicket:id="tagDate">[tag date]</span></td>    			
-    			<td><b><span wicket:id="tagName">[tag name]</span></b></td>
-    			<td class="hidden-phone icon"><img wicket:id="tagIcon" /></td>
-    			<td class="hidden-phone"><span wicket:id="tagDescription">[tag description]</span></td>
-    			<td class="hidden-phone rightAlign">
-    				<span wicket:id="tagLinks"></span>
-				</td>
-    		</tr>
-    	</tbody>
-	</table>
-	
-	<div wicket:id="allTags">[all tags]</div>	
-
-	<!--  annotated tag links -->
-	<wicket:fragment wicket:id="annotatedLinks">
-		<span class="link">
-			<a wicket:id="tag"><wicket:message key="gb.tag"></wicket:message></a> | <a wicket:id="commit"><wicket:message key="gb.commit"></wicket:message></a> | <a wicket:id="log"><wicket:message key="gb.log"></wicket:message></a>
-		</span>
-	</wicket:fragment>
-	
-	<!-- lightweight tag links -->
-	<wicket:fragment wicket:id="lightweightLinks">
-		<span class="link">
-			<a wicket:id="commit"><wicket:message key="gb.commit"></wicket:message></a> | <a wicket:id="log"><wicket:message key="gb.log"></wicket:message></a>
-		</span>
-	</wicket:fragment>
-
-	<!-- blob tag links -->
-	<wicket:fragment wicket:id="blobLinks">
-		<span class="link">
-			<a wicket:id="tag"><wicket:message key="gb.tag"></wicket:message></a> | <a wicket:id="blob"><wicket:message key="gb.blob"></wicket:message></a> | <a wicket:id="raw"><wicket:message key="gb.raw"></wicket:message></a>
-		</span>
-	</wicket:fragment>
-	
-</wicket:panel>
-</body>
-</html>
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/panels/TagsPanel.java b/src/com/gitblit/wicket/panels/TagsPanel.java
deleted file mode 100644
index 2bee6a6..0000000
--- a/src/com/gitblit/wicket/panels/TagsPanel.java
+++ /dev/null
@@ -1,170 +0,0 @@
-/*
- * 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.wicket.panels;
-
-import java.util.List;
-
-import org.apache.wicket.markup.html.basic.Label;
-import org.apache.wicket.markup.html.link.BookmarkablePageLink;
-import org.apache.wicket.markup.html.panel.Fragment;
-import org.apache.wicket.markup.repeater.Item;
-import org.apache.wicket.markup.repeater.data.DataView;
-import org.apache.wicket.markup.repeater.data.ListDataProvider;
-import org.apache.wicket.model.StringResourceModel;
-import org.eclipse.jgit.lib.Constants;
-import org.eclipse.jgit.lib.Repository;
-
-import com.gitblit.models.RefModel;
-import com.gitblit.utils.JGitUtils;
-import com.gitblit.utils.StringUtils;
-import com.gitblit.wicket.WicketUtils;
-import com.gitblit.wicket.pages.BlobPage;
-import com.gitblit.wicket.pages.CommitPage;
-import com.gitblit.wicket.pages.LogPage;
-import com.gitblit.wicket.pages.RawPage;
-import com.gitblit.wicket.pages.RepositoryPage;
-import com.gitblit.wicket.pages.TagPage;
-import com.gitblit.wicket.pages.TagsPage;
-import com.gitblit.wicket.pages.TreePage;
-
-public class TagsPanel extends BasePanel {
-
-	private static final long serialVersionUID = 1L;
-
-	private final boolean hasTags;
-
-	public TagsPanel(String wicketId, final String repositoryName, Repository r, final int maxCount) {
-		super(wicketId);
-
-		// header
-		List<RefModel> tags = JGitUtils.getTags(r, false, maxCount);
-		if (maxCount > 0) {
-			// summary page
-			// show tags page link
-			add(new LinkPanel("header", "title", new StringResourceModel("gb.tags", this, null),
-					TagsPage.class, WicketUtils.newRepositoryParameter(repositoryName)));
-		} else {
-			// tags page
-			add(new Label("header", new StringResourceModel("gb.tags", this, null)));
-		}
-
-		ListDataProvider<RefModel> tagsDp = new ListDataProvider<RefModel>(tags);
-		DataView<RefModel> tagView = new DataView<RefModel>("tag", tagsDp) {
-			private static final long serialVersionUID = 1L;
-			int counter;
-
-			public void populateItem(final Item<RefModel> item) {
-				RefModel entry = item.getModelObject();
-
-				item.add(WicketUtils.createDateLabel("tagDate", entry.getDate(), getTimeZone(), getTimeUtils()));
-
-				Class<? extends RepositoryPage> linkClass;
-				switch (entry.getReferencedObjectType()) {
-				case Constants.OBJ_BLOB:
-					linkClass = BlobPage.class;
-					break;
-				case Constants.OBJ_TREE:
-					linkClass = TreePage.class;
-					break;
-				case Constants.OBJ_COMMIT:
-				default:
-					linkClass = CommitPage.class;
-					break;
-				}
-				item.add(new LinkPanel("tagName", "list name", entry.displayName, linkClass,
-						WicketUtils.newObjectParameter(repositoryName, entry
-								.getReferencedObjectId().getName())));
-
-				// workaround for RevTag returning a lengthy shortlog. :(
-				String message = StringUtils.trimString(entry.getShortMessage(), 
-						com.gitblit.Constants.LEN_SHORTLOG);
-
-				if (linkClass.equals(BlobPage.class)) {
-					// Blob Tag Object
-					item.add(WicketUtils.newImage("tagIcon", "file_16x16.png"));
-					item.add(new LinkPanel("tagDescription", "list", message, TagPage.class,
-							WicketUtils.newObjectParameter(repositoryName, entry.getObjectId()
-									.getName())));
-
-					Fragment fragment = new Fragment("tagLinks", "blobLinks", this);
-					fragment.add(new BookmarkablePageLink<Void>("tag", TagPage.class, WicketUtils
-							.newObjectParameter(repositoryName, entry.getObjectId().getName()))
-							.setEnabled(entry.isAnnotatedTag()));
-
-					fragment.add(new BookmarkablePageLink<Void>("blob", linkClass, WicketUtils
-							.newObjectParameter(repositoryName, entry.getReferencedObjectId()
-									.getName())));
-
-					fragment.add(new BookmarkablePageLink<Void>("raw", RawPage.class, WicketUtils
-							.newObjectParameter(repositoryName, entry.getReferencedObjectId()
-									.getName())));
-					item.add(fragment);
-				} else {
-					// TODO Tree Tag Object
-					// Standard Tag Object
-					if (entry.isAnnotatedTag()) {
-						item.add(WicketUtils.newImage("tagIcon", "tag_16x16.png"));
-						item.add(new LinkPanel("tagDescription", "list", message, TagPage.class,
-								WicketUtils.newObjectParameter(repositoryName, entry.getObjectId()
-										.getName())));
-
-						Fragment fragment = new Fragment("tagLinks", "annotatedLinks", this);
-						fragment.add(new BookmarkablePageLink<Void>("tag", TagPage.class,
-								WicketUtils.newObjectParameter(repositoryName, entry.getObjectId()
-										.getName())).setEnabled(entry.isAnnotatedTag()));
-
-						fragment.add(new BookmarkablePageLink<Void>("commit", linkClass,
-								WicketUtils.newObjectParameter(repositoryName, entry
-										.getReferencedObjectId().getName())));
-
-						fragment.add(new BookmarkablePageLink<Void>("log", LogPage.class,
-								WicketUtils.newObjectParameter(repositoryName, entry.getName())));
-						item.add(fragment);
-					} else {
-						item.add(WicketUtils.newBlankImage("tagIcon"));
-						item.add(new LinkPanel("tagDescription", "list", message, CommitPage.class,
-								WicketUtils.newObjectParameter(repositoryName, entry.getObjectId()
-										.getName())));
-						Fragment fragment = new Fragment("tagLinks", "lightweightLinks", this);
-						fragment.add(new BookmarkablePageLink<Void>("commit", CommitPage.class,
-								WicketUtils.newObjectParameter(repositoryName, entry
-										.getReferencedObjectId().getName())));
-						fragment.add(new BookmarkablePageLink<Void>("log", LogPage.class,
-								WicketUtils.newObjectParameter(repositoryName, entry.getName())));
-						item.add(fragment);
-					}
-				}
-
-				WicketUtils.setAlternatingBackground(item, counter);
-				counter++;
-			}
-		};
-		add(tagView);
-		if (tags.size() < maxCount || maxCount <= 0) {
-			add(new Label("allTags", "").setVisible(false));
-		} else {
-			add(new LinkPanel("allTags", "link", new StringResourceModel("gb.allTags", this, null),
-					TagsPage.class, WicketUtils.newRepositoryParameter(repositoryName)));
-		}
-
-		hasTags = tags.size() > 0;
-	}
-
-	public TagsPanel hideIfEmpty() {
-		setVisible(hasTags);
-		return this;
-	}
-}
diff --git a/checkstyle.xml b/src/main/config/checkstyle.xml
similarity index 100%
rename from checkstyle.xml
rename to src/main/config/checkstyle.xml
diff --git a/distrib/authority.conf b/src/main/distrib/data/certs/authority.conf
similarity index 100%
rename from distrib/authority.conf
rename to src/main/distrib/data/certs/authority.conf
diff --git a/distrib/instructions.tmpl b/src/main/distrib/data/certs/instructions.tmpl
similarity index 100%
rename from distrib/instructions.tmpl
rename to src/main/distrib/data/certs/instructions.tmpl
diff --git a/distrib/mail.tmpl b/src/main/distrib/data/certs/mail.tmpl
similarity index 100%
rename from distrib/mail.tmpl
rename to src/main/distrib/data/certs/mail.tmpl
diff --git a/src/main/distrib/data/clientapps.json b/src/main/distrib/data/clientapps.json
new file mode 100644
index 0000000..2b15cd3
--- /dev/null
+++ b/src/main/distrib/data/clientapps.json
@@ -0,0 +1,81 @@
+[
+	{
+		"name": "Git",
+		"title": "Git",
+		"description": "a fast, open-source, distributed VCS",
+		"legal": "released under the GPLv2 open source license",
+		"command": "git clone ${repoUrl}",
+		"productUrl": "http://git-scm.com",
+		"icon": "git-black_32x32.png",
+		"isActive": true
+	},
+	{
+		"name": "SmartGit/Hg",
+		"title": "syntevo SmartGit/Hg\u2122",
+		"description": "a Git client for Windows, Mac, & Linux",
+		"legal": "\u00a9 2013 syntevo GmbH. All rights reserved.",
+		"cloneUrl": "smartgit://cloneRepo/${repoUrl}",
+		"productUrl": "http://www.syntevo.com/smartgithg",
+		"platforms": [ "windows", "macintosh", "linux" ],
+		"icon": "smartgithg_32x32.png",
+		"isActive": true
+	},
+	{
+		"name": "SourceTree",
+		"title": "Atlassian SourceTree\u2122",
+		"description": "a free Git client for Windows or Mac",
+		"legal": "\u00a9 2013 Atlassian. All rights reserved.",
+		"cloneUrl": "sourcetree://cloneRepo/${repoUrl}",
+		"productUrl": "http://sourcetreeapp.com",
+		"platforms": [ "windows", "macintosh" ],
+		"icon": "sourcetree_32x32.png",
+		"isActive": true
+	},
+	{
+		"name": "Tower",
+		"title": "fournova Tower\u2122",
+		"description": "a Git client for Mac",
+		"legal": "\u00a9 2013 fournova Software GmbH. All rights reserved.",
+		"cloneUrl": "gittower://openRepo/${repoUrl}",
+		"productUrl": "http://www.git-tower.com",
+		"platforms": [ "macintosh" ],
+		"icon": "tower_32x32.png",
+		"isActive": true
+	},
+	{
+		"name": "GitHub",
+		"title": "GitHub\u2122 for Mac",
+		"description": "a free Git client for Mac OS X",
+		"legal": "\u00a9 2013 GitHub. All rights reserved.",
+		"cloneUrl": "github-mac://openRepo/${repoUrl}",
+		"productUrl": "http://mac.github.com",
+		"transports": [ "http", "https" ],
+		"platforms": [ "macintosh" ],
+		"icon": "github_32x32.png",
+		"isActive": true
+	},
+	{
+		"name": "GitHub",
+		"title": "GitHub\u2122 for Windows",
+		"description": "a free Git client for Windows",
+		"legal": "\u00a9 2013 GitHub. All rights reserved.",
+		"cloneUrl": "github-windows://openRepo/${repoUrl}",
+		"productUrl": "http://windows.github.com",
+		"transports": [ "http", "https" ],
+		"platforms": [ "windows" ],
+		"icon": "github_32x32.png",
+		"isActive": true
+	},
+	{
+		"name": "SparkleShare",
+		"title": "SparkleShare\u2122",
+		"description": "an open source collaboration and sharing tool",
+		"legal": "released under the GPLv3 open source license",
+		"cloneUrl": "sparkleshare://addProject/${baseUrl}/sparkleshare/${repoUrl}.xml",
+		"productUrl": "http://sparkleshare.org",
+		"platforms": [ "windows", "macintosh", "linux" ],
+		"icon": "sparkleshare_32x32.png",
+		"minimumPermission" : "RW+",
+		"isActive": false
+	}
+]
\ No newline at end of file
diff --git a/src/main/distrib/data/git/project.mkd b/src/main/distrib/data/git/project.mkd
new file mode 100644
index 0000000..851b57a
--- /dev/null
+++ b/src/main/distrib/data/git/project.mkd
@@ -0,0 +1,12 @@
+This project contains all repositories created directly in the root of *git.repositoriesFolder*.
+
+This message is stored in *git.repositoriesFolder*/**project.mkd**
+
+#### Other Projects
+
+Each project, or repository group, may specify it's own message by defining a **project.mkd** file in the project folder.
+
+<pre>
+<i>git.repositoriesFolder</i>/projecta/<b>project.mkd</b>
+<i>git.repositoriesFolder</i>/projectb/<b>project.mkd</b>
+</pre>
diff --git a/src/main/distrib/data/gitblit.properties b/src/main/distrib/data/gitblit.properties
new file mode 100644
index 0000000..1ee6d80
--- /dev/null
+++ b/src/main/distrib/data/gitblit.properties
@@ -0,0 +1,1513 @@
+#
+# Gitblit Settings
+#
+
+# This settings file supports parameterization from the command-line for the
+# following command-line parameters:
+#
+#   --baseFolder    ${baseFolder}    SINCE 1.2.1
+#
+# Settings that support ${baseFolder} parameter substitution are indicated with the
+# BASEFOLDER attribute.  If the --baseFolder argument is unspecified, ${baseFolder}
+# and it's trailing / will be discarded from the setting value leaving a relative
+# path that is equivalent to pre-1.2.1 releases.
+#
+# e.g. "${baseFolder}/git" becomes "git", if --baseFolder is unspecified 
+#
+# Git Servlet Settings
+#
+
+# Base folder for repositories.
+# This folder may contain bare and non-bare repositories but Gitblit will only
+# allow you to push to bare repositories.
+# Use forward slashes even on Windows!!
+# e.g. c:/gitrepos
+#
+# SINCE 0.5.0
+# RESTART REQUIRED
+# BASEFOLDER
+git.repositoriesFolder = ${baseFolder}/git
+
+# Build the available repository list at startup and cache this list for reuse.
+# This reduces disk io when presenting the repositories page, responding to rpcs,
+# etc, but it means that  Gitblit will not automatically identify repositories
+# added or deleted by external tools.
+#
+# For this case you can use curl, wget, etc to issue an rpc request to clear the
+# cache (e.g. https://localhost/rpc?req=CLEAR_REPOSITORY_CACHE)
+#
+# SINCE 1.1.0
+git.cacheRepositoryList = true
+
+# Search the repositories folder subfolders for other repositories.
+# Repositories MAY NOT be nested (i.e. one repository within another)
+# but they may be grouped together in subfolders.
+# e.g. c:/gitrepos/libraries/mylibrary.git
+#      c:/gitrepos/libraries/myotherlibrary.git
+#
+# SINCE 0.5.0
+git.searchRepositoriesSubfolders = true
+
+# Maximum number of folders to recurse into when searching for repositories.
+# The default value, -1, disables depth limits.
+#
+# SINCE 1.1.0
+git.searchRecursionDepth = -1
+
+# List of regex exclusion patterns to match against folders found in
+# *git.repositoriesFolder*.
+# Use forward slashes even on Windows!!
+# e.g. test/jgit\.git
+#
+# SPACE-DELIMITED
+# CASE-SENSITIVE
+# SINCE 1.1.0
+git.searchExclusions =
+
+# List of regex url patterns for extracting a repository name when locating
+# submodules.
+#   e.g. git.submoduleUrlPatterns = .*?://github.com/(.*) will extract
+#   *gitblit/gitblit.git* from *git://github.com/gitblit/gitblit.git*
+# If no matches are found then the submodule repository name is assumed to be
+# whatever trails the last / character. (e.g. gitblit.git).
+#
+# SPACE-DELIMITED
+# CASE-SENSITIVE
+# SINCE 1.1.0
+git.submoduleUrlPatterns = .*?://github.com/(.*)
+
+# Specify the interface for Git Daemon to bind it's service.
+# You may specify an ip or an empty value to bind to all interfaces.
+# Specifying localhost will result in Gitblit ONLY listening to requests to
+# localhost.
+#
+# SINCE 1.3.0
+# RESTART REQUIRED
+git.daemonBindInterface = localhost
+
+# port for serving the Git Daemon service.  <= 0 disables this service.
+# On Unix/Linux systems, ports < 1024 require root permissions.
+# Recommended value: 9418
+#
+# SINCE 1.3.0
+# RESTART REQUIRED
+git.daemonPort = 9418
+
+# Allow push/pull over http/https with JGit servlet.
+# If you do NOT want to allow Git clients to clone/push to Gitblit set this
+# to false.  You might want to do this if you are only using ssh:// or git://.
+# If you set this false, consider changing the *web.otherUrls* setting to
+# indicate your clone/push urls.
+#
+# SINCE 0.5.0
+git.enableGitServlet = true
+
+# If you want to restrict all git servlet access to those with valid X509 client
+# certificates then set this value to true.
+#
+# SINCE 1.2.0
+git.requiresClientCertificate = false
+
+# Enforce date checks on client certificates to ensure that they are not being
+# used prematurely and that they have not expired.
+#
+# SINCE 1.2.0
+git.enforceCertificateValidity = true
+
+# List of OIDs to extract from a client certificate DN to map a certificate to
+# an account username.
+#
+# e.g. git.certificateUsernameOIDs = CN
+# e.g. git.certificateUsernameOIDs = FirstName LastName
+#
+# SPACE-DELIMITED
+# SINCE 1.2.0
+git.certificateUsernameOIDs = CN
+
+# Only serve/display bare repositories.
+# If there are non-bare repositories in git.repositoriesFolder and this setting
+# is true, they will be excluded from the ui. 
+#
+# SINCE 0.9.0
+git.onlyAccessBareRepositories = false
+
+# Allow an authenticated user to create a destination repository on a push if
+# the repository does not already exist.
+#
+# Administrator accounts can create a repository in any project.
+# These repositories are created with the default access restriction and authorization
+# control values.  The pushing account is set as the owner.
+#
+# Non-administrator accounts with the CREATE role may create personal repositories.
+# These repositories are created as VIEW restricted for NAMED users.
+# The pushing account is set as the owner.
+#
+# SINCE 1.2.0
+git.allowCreateOnPush = true
+
+# The default access restriction for new repositories.
+# Valid values are NONE, PUSH, CLONE, VIEW
+#  NONE = anonymous view, clone, & push
+#  PUSH = anonymous view & clone and authenticated push
+#  CLONE = anonymous view, authenticated clone & push
+#  VIEW = authenticated view, clone, & push
+#
+# SINCE 1.0.0
+git.defaultAccessRestriction = NONE
+
+# The default authorization control for new repositories.
+# Valid values are AUTHENTICATED and NAMED
+#  AUTHENTICATED = any authenticated user is granted restricted access
+#  NAMED = only named users/teams are granted restricted access
+#
+# SINCE 1.1.0
+git.defaultAuthorizationControl = NAMED
+
+# The default incremental push tag prefix.  Tag prefix applied to a repository
+# that has automatic push tags enabled and does not specify a custom tag prefix.
+#
+# If incremental push tags are enabled, the tips of each branch in the push will
+# be tagged with an increasing revision integer.
+#
+# e.g. refs/tags/r2345 or refs/tags/rev_2345 
+#
+# SINCE 1.3.0
+git.defaultIncrementalPushTagPrefix = r
+
+# Enable JGit-based garbage collection. (!!EXPERIMENTAL!!)
+#
+# USE AT YOUR OWN RISK!
+#
+# If enabled, the garbage collection executor scans all repositories once a day
+# at the hour of your choosing.  The GC executor will take each repository "offline",
+# one-at-a-time, to check if the repository satisfies it's GC trigger requirements.
+#
+# While the repository is offline it will be inaccessible from the web UI or from
+# any of the other services (git, rpc, rss, etc).
+#
+# Gitblit's GC Executor MAY NOT PLAY NICE with the other Git kids on the block,
+# especially on Windows systems, so if you are using other tools please coordinate
+# their usage with your GC Executor schedule or do not use this feature.
+#
+# The GC algorithm complex and the JGit team advises caution when using their
+# young implementation of GC.
+#
+# http://wiki.eclipse.org/EGit/New_and_Noteworthy/2.1#Garbage_Collector_and_Repository_Storage_Statistics
+#
+# EXPERIMENTAL
+# SINCE 1.2.0
+# RESTART REQUIRED
+git.enableGarbageCollection = false
+
+# Hour of the day for the GC Executor to scan repositories.
+# This value is in 24-hour time.
+#
+# SINCE 1.2.0
+git.garbageCollectionHour = 0
+
+# The default minimum total filesize of loose objects to trigger early garbage
+# collection.
+#
+# You may specify a custom threshold for a repository in the repository's settings.
+# Common unit suffixes of k, m, or g are supported.
+#
+# SINCE 1.2.0
+git.defaultGarbageCollectionThreshold = 500k
+
+# The default period, in days, between GCs for a repository.  If the total filesize
+# of the loose object exceeds *git.garbageCollectionThreshold* or the repository's
+# custom threshold, this period will be short-circuited. 
+#
+# e.g. if a repository collects 100KB of loose objects every day with a 500KB
+# threshold and a period of 7 days, it will take 5 days for the loose objects to
+# be collected, packed, and pruned.
+#
+# OR
+#
+# if a repository collects 10KB of loose objects every day with a 500KB threshold
+# and a period of 7 days, it will take the full 7 days for the loose objects to be
+# collected, packed, and pruned.
+#
+# You may specify a custom period for a repository in the repository's settings.
+#
+# The minimum value is 1 day since the GC Executor only runs once a day.
+#
+# SINCE 1.2.0
+git.defaultGarbageCollectionPeriod = 7
+
+# Number of bytes of a pack file to load into memory in a single read operation.
+# This is the "page size" of the JGit buffer cache, used for all pack access
+# operations. All disk IO occurs as single window reads. Setting this too large
+# may cause the process to load more data than is required; setting this too small
+# may increase the frequency of read() system calls.
+#
+# Default on JGit is 8 KiB on all platforms.
+#
+# Common unit suffixes of k, m, or g are supported.
+# Documentation courtesy of the Gerrit project.
+#
+# SINCE 1.0.0
+# RESTART REQUIRED
+git.packedGitWindowSize = 8k
+
+# Maximum number of bytes to load and cache in memory from pack files. If JGit
+# needs to access more than this many bytes it will unload less frequently used
+# windows to reclaim memory space within the process. As this buffer must be shared
+# with the rest of the JVM heap, it should be a fraction of the total memory available.
+#
+# The JGit team recommends setting this value larger than the size of your biggest
+# repository. This ensures you can serve most requests from memory.
+#
+# Default on JGit is 10 MiB on all platforms.
+#
+# Common unit suffixes of k, m, or g are supported.
+# Documentation courtesy of the Gerrit project.
+#
+# SINCE 1.0.0
+# RESTART REQUIRED
+git.packedGitLimit = 10m
+
+# Maximum number of bytes to reserve for caching base objects that multiple deltafied
+# objects reference. By storing the entire decompressed base object in a cache Git
+# is able to avoid unpacking and decompressing frequently used base objects multiple times.
+#
+# Default on JGit is 10 MiB on all platforms. You probably do not need to adjust
+# this value.
+#
+# Common unit suffixes of k, m, or g are supported.
+# Documentation courtesy of the Gerrit project.
+#
+# SINCE 1.0.0
+# RESTART REQUIRED
+git.deltaBaseCacheLimit = 10m
+
+# Maximum number of pack files to have open at once. A pack file must be opened
+# in order for any of its data to be available in a cached window.
+#
+# If you increase this to a larger setting you may need to also adjust the ulimit
+# on file descriptors for the host JVM, as Gitblit needs additional file descriptors
+# available for network sockets and other repository data manipulation.
+#
+# Default on JGit is 128 file descriptors on all platforms.
+# Documentation courtesy of the Gerrit project.
+#
+# SINCE 1.0.0
+# RESTART REQUIRED
+git.packedGitOpenFiles = 128
+
+# Largest object size, in bytes, that JGit will allocate as a contiguous byte
+# array. Any file revision larger than this threshold will have to be streamed,
+# typically requiring the use of temporary files under $GIT_DIR/objects to implement
+# psuedo-random access during delta decompression.
+#
+# Servers with very high traffic should set this to be larger than the size of
+# their common big files. For example a server managing the Android platform
+# typically has to deal with ~10-12 MiB XML files, so 15 m would be a reasonable
+# setting in that environment. Setting this too high may cause the JVM to run out
+# of heap space when handling very big binary files, such as device firmware or
+# CD-ROM ISO images. Make sure to adjust your JVM heap accordingly. 
+#
+# Default is 50 MiB on all platforms.
+#
+# Common unit suffixes of k, m, or g are supported.
+# Documentation courtesy of the Gerrit project.
+#
+# SINCE 1.0.0
+# RESTART REQUIRED
+git.streamFileThreshold = 50m
+
+# When true, JGit will use mmap() rather than malloc()+read() to load data from
+# pack files.  The use of mmap can be problematic on some JVMs as the garbage
+# collector must deduce that a memory mapped segment is no longer in use before
+# a call to munmap() can be made by the JVM native code.
+#
+# In server applications (such as Gitblit) that need to access many pack files,
+# setting this to true risks artificially running out of virtual address space, 
+# as the garbage collector cannot reclaim unused mapped spaces fast enough.
+#
+# Default on JGit is false. Although potentially slower, it yields much more
+# predictable behavior.
+# Documentation courtesy of the Gerrit project.
+#
+# SINCE 1.0.0
+# RESTART REQUIRED
+git.packedGitMmap = false
+
+#
+# Groovy Integration
+#
+
+# Location of Groovy scripts to use for Pre and Post receive hooks.
+# Use forward slashes even on Windows!!
+# e.g. c:/groovy
+#
+# RESTART REQUIRED
+# SINCE 0.8.0
+# BASEFOLDER
+groovy.scriptsFolder = ${baseFolder}/groovy
+
+# Specify the directory Grape uses for downloading libraries.
+# http://groovy.codehaus.org/Grape
+#
+# RESTART REQUIRED
+# SINCE 1.0.0
+# BASEFOLDER
+groovy.grapeFolder = ${baseFolder}/groovy/grape
+
+# Scripts to execute on Pre-Receive.
+#
+# These scripts execute after an incoming push has been parsed and validated
+# but BEFORE the changes are applied to the repository.  You might reject a
+# push in this script based on the repository and branch the push is attempting
+# to change.
+#
+# Script names are case-sensitive on case-sensitive file systems.  You may omit
+# the traditional ".groovy" from this list if your file extension is ".groovy" 
+#
+# NOTE:
+# These scripts are only executed when pushing to *Gitblit*, not to other Git
+# tooling you may be using.  Also note that these scripts are shared between
+# repositories. These are NOT repository-specific scripts!  Within the script
+# you may customize the control-flow for a specific repository by checking the
+# *repository* variable.
+#
+# SPACE-DELIMITED
+# CASE-SENSITIVE
+# SINCE 0.8.0
+groovy.preReceiveScripts =
+
+# Scripts to execute on Post-Receive.
+#
+# These scripts execute AFTER an incoming push has been applied to a repository.
+# You might trigger a continuous-integration build here or send a notification.
+#
+# Script names are case-sensitive on case-sensitive file systems.  You may omit
+# the traditional ".groovy" from this list if your file extension is ".groovy" 
+#
+# NOTE:
+# These scripts are only executed when pushing to *Gitblit*, not to other Git
+# tooling you may be using.  Also note that these scripts are shared between
+# repositories. These are NOT repository-specific scripts!  Within the script
+# you may customize the control-flow for a specific repository by checking the
+# *repository* variable.
+# 
+# SPACE-DELIMITED
+# CASE-SENSITIVE
+# SINCE 0.8.0
+groovy.postReceiveScripts =
+
+# Repository custom fields for Groovy Hook mechanism
+#
+# List of key=label pairs of custom fields to prompt for in the Edit Repository
+# page.  These keys are stored in the repository's git config file in the 
+# section [gitblit "customFields"].  Key names are alphanumeric only.  These
+# fields are intended to be used for the Groovy hook mechanism where a script
+# can adjust it's execution based on the custom fields stored in the repository
+# config.
+#
+# e.g. "commitMsgRegex=Commit Message Regular Expression" anotherProperty=Another
+#
+# SPACE-DELIMITED
+# SINCE 1.0.0
+groovy.customFields = 
+
+#
+# Fanout Settings
+#
+
+# Fanout is a PubSub notification service that can be used by Sparkleshare
+# to eliminate repository change polling.  The fanout service runs in a separate
+# thread on a separate port from the Gitblit http/https application.
+# This service is provided so that Sparkleshare may be used with Gitblit in
+# firewalled environments or where reliance on Sparkleshare's default notifications
+# server (notifications.sparkleshare.org) is unwanted.
+#
+# This service maintains an open socket connection from the client to the
+# Fanout PubSub service. This service may not work properly behind a proxy server.  
+
+# Specify the interface for Fanout to bind it's service.
+# You may specify an ip or an empty value to bind to all interfaces.
+# Specifying localhost will result in Gitblit ONLY listening to requests to
+# localhost.
+#
+# SINCE 1.2.1
+# RESTART REQUIRED
+fanout.bindInterface = localhost
+
+# port for serving the Fanout PubSub service.  <= 0 disables this service.
+# On Unix/Linux systems, ports < 1024 require root permissions.
+# Recommended value: 17000
+#
+# SINCE 1.2.1
+# RESTART REQUIRED
+fanout.port = 0
+
+# Use Fanout NIO service.  If false, a multi-threaded socket service will be used.
+# Be advised, the socket implementation spawns a thread per connection plus the
+# connection acceptor thread.  The NIO implementation is completely single-threaded.
+#
+# SINCE 1.2.1
+# RESTART REQUIRED
+fanout.useNio = true
+
+# Concurrent connection limit.  <= 0 disables concurrent connection throttling.
+# If > 0, only the specified number of concurrent connections will be allowed
+# and all other connections will be rejected.
+#
+# SINCE 1.2.1
+# RESTART REQUIRED
+fanout.connectionLimit = 0
+
+#
+# Authentication Settings
+#
+
+# Require authentication to see everything but the admin pages
+#
+# SINCE 0.5.0
+# RESTART REQUIRED
+web.authenticateViewPages = false
+
+# If web.authenticateViewPages=true you may optionally require a client-side
+# basic authentication prompt instead of the standard form-based login. 
+#
+# SINCE 1.3.0
+web.enforceHttpBasicAuthentication = false
+
+# Require admin authentication for the admin functions and pages
+#
+# SINCE 0.5.0
+# RESTART REQUIRED
+web.authenticateAdminPages = true
+
+# Allow Gitblit to store a cookie in the user's browser for automatic
+# authentication.  The cookie is generated by the user service.
+#
+# SINCE 0.5.0
+web.allowCookieAuthentication = true
+
+# Config file for storing project metadata
+#
+# SINCE 1.2.0
+# BASEFOLDER
+web.projectsFile = ${baseFolder}/projects.conf
+
+# Either the full path to a user config file (users.conf)
+# OR the full path to a simple user properties file (users.properties)
+# OR a fully qualified class name that implements the IUserService interface.
+#
+# Alternative user services:
+#    com.gitblit.LdapUserService
+#    com.gitblit.RedmineUserService
+#    com.gitblit.SalesforceUserService
+#    com.gitblit.WindowsUserService
+#
+# Any custom user service implementation must have a public default constructor.
+#
+# SINCE 0.5.0
+# RESTART REQUIRED
+# BASEFOLDER
+realm.userService = ${baseFolder}/users.conf
+
+# How to store passwords.
+# Valid values are plain, md5, or combined-md5.  md5 is the hash of password.
+# combined-md5 is the hash of username.toLowerCase()+password.
+# Default is md5.
+#
+# SINCE 0.5.0 
+realm.passwordStorage = md5
+
+# Minimum valid length for a plain text password.
+# Default value is 5.  Absolute minimum is 4.
+#
+# SINCE 0.5.0 
+realm.minPasswordLength = 5
+
+#
+# Gitblit Web Settings
+#
+# If blank Gitblit is displayed.
+#
+# SINCE 0.5.0
+web.siteName =
+
+# You may specify a different logo image for the header but it must be 120x45px.
+# If the specified file does not exist, the default Gitblit logo will be used.
+#
+# SINCE 1.3.0
+# BASEFOLDER
+web.headerLogo = ${baseFolder}/logo.png
+
+# You may specify a custom header background CSS color.  If unspecified, the
+# default color will be used.
+#
+# e.g. web.headerBackgroundColor = #002060
+#
+# SINCE 1.3.0
+web.headerBackgroundColor =
+
+# You may specify a custom header foreground CSS color.  If unspecified, the
+# default color will be used.
+#
+# e.g. web.headerForegroundColor = white
+#
+# SINCE 1.3.0
+web.headerForegroundColor =
+
+# You may specify a custom header foreground hover CSS color.  If unspecified, the
+# default color will be used.
+#
+# e.g. web.headerHoverColor = white
+#
+# SINCE 1.3.0
+web.headerHoverColor =
+
+# You may specify a custom header border CSS color.  If unspecified, the default
+# color will be used.
+#
+# e.g. web.headerBorderColor = #002060
+#
+# SINCE 1.3.0
+web.headerBorderColor =
+
+# You may specify a custom header border CSS color.  If unspecified, the default
+# color will be used.
+#
+# e.g. web.headerBorderFocusColor = #ff9900
+#
+# SINCE 1.3.0
+web.headerBorderFocusColor =
+
+# If *web.authenticateAdminPages*=true, users with "admin" role can create
+# repositories, create users, and edit repository metadata.
+#
+# If *web.authenticateAdminPages*=false, any user can execute the aforementioned
+# functions. 
+#
+# SINCE 0.5.0 
+web.allowAdministration = true
+
+# Allows rpc clients to list repositories and possibly manage or administer the 
+# Gitblit server, if the authenticated account has administrator permissions.
+# See *web.enableRpcManagement* and *web.enableRpcAdministration*.
+#
+# SINCE 0.7.0 
+web.enableRpcServlet = true
+
+# Allows rpc clients to manage repositories and users of the Gitblit instance,
+# if the authenticated account has administrator permissions.
+# Requires *web.enableRpcServlet=true*.
+#
+# SINCE 0.7.0 
+web.enableRpcManagement = false
+
+# Allows rpc clients to control the server settings and monitor the health of this
+# this Gitblit instance, if the authenticated account has administrator permissions.
+# Requires *web.enableRpcServlet=true* and *web.enableRpcManagement*.
+#
+# SINCE 0.7.0 
+web.enableRpcAdministration = false
+
+# Full path to a configurable robots.txt file.  With this file you can control
+# what parts of your Gitblit server respectable robots are allowed to traverse.
+# http://googlewebmastercentral.blogspot.com/2008/06/improving-on-robots-exclusion-protocol.html
+#
+# SINCE 1.0.0
+# BASEFOLDER
+web.robots.txt = ${baseFolder}/robots.txt
+
+# The number of minutes to cache a page in the browser since the last request.
+# The default value is 0 minutes.  A value <= 0 disables all page caching which
+# is the default behavior for Gitblit <= 1.3.0.
+#
+# SINCE 1.3.1
+web.pageCacheExpires = 0
+
+# If true, the web ui layout will respond and adapt to the browser's dimensions.
+# if false, the web ui will use a 940px fixed-width layout.
+# http://twitter.github.com/bootstrap/scaffolding.html#responsive
+#
+# SINCE 1.0.0
+web.useResponsiveLayout = true
+
+# Allow Gravatar images to be displayed in Gitblit pages.
+#
+# SINCE 0.8.0
+web.allowGravatar = true
+
+# Allow dynamic zip downloads.
+#
+# SINCE 0.5.0   
+web.allowZipDownloads = true
+
+# If *web.allowZipDownloads=true* the following formats will be displayed for
+# download compressed archive links:
+#
+# zip   = standard .zip
+# tar   = standard tar format (preserves *nix permissions and symlinks)
+# gz    = gz-compressed tar
+# xz    = xz-compressed tar
+# bzip2 = bzip2-compressed tar
+#
+# SPACE-DELIMITED
+# SINCE 1.2.0
+web.compressedDownloads = zip gz
+
+# Allow optional Lucene integration. Lucene indexing is an opt-in feature.
+# A repository may specify branches to index with Lucene instead of using Git
+# commit traversal. There are scenarios where you may want to completely disable
+# Lucene indexing despite a repository specifying indexed branches.  One such
+# scenario is on a resource-constrained federated Gitblit mirror.
+#
+# SINCE 0.9.0
+web.allowLuceneIndexing = true
+
+# Allows an authenticated user to create forks of a repository
+#
+# set this to false if you want to disable all fork controls on the web site
+#
+web.allowForking = true
+
+# Controls the length of shortened commit hash ids
+#
+# SINCE 1.2.0
+web.shortCommitIdLength = 6
+
+# Use Clippy (Flash solution) to provide a copy-to-clipboard button.
+# If false, a button with a more primitive JavaScript-based prompt box will
+# offer a 3-step (click, ctrl+c, enter) copy-to-clipboard alternative.
+#
+# SINCE 0.8.0
+web.allowFlashCopyToClipboard = true
+
+# Default maximum number of commits that a repository may contribute to the
+# activity page, regardless of the selected duration.  This setting may be valuable
+# for an extremely busy server.  This value may also be configed per-repository
+# in Edit Repository. 0 disables this throttle.
+#
+# SINCE 1.2.0
+web.maxActivityCommits = 0
+
+# Default number of entries to include in RSS Syndication links
+#
+# SINCE 0.5.0
+web.syndicationEntries = 25
+
+# Show the size of each repository on the repositories page.
+# This requires recursive traversal of each repository folder.  This may be
+# non-performant on some operating systems and/or filesystems. 
+#
+# SINCE 0.5.2
+web.showRepositorySizes = true
+
+# List of custom regex expressions that can be displayed in the Filters menu
+# of the Repositories and Activity pages.  Keep them very simple because you
+# are likely to run into encoding issues if they are too complex.
+#
+# Use !!! to separate the filters 
+#
+# SINCE 0.8.0
+web.customFilters =
+
+# Show federation registrations (without token) and the current pull status
+# to non-administrator users. 
+#
+# SINCE 0.6.0
+web.showFederationRegistrations = false
+
+# This is the message displayed when *web.authenticateViewPages=true*.
+# This can point to a file with Markdown content.
+# Specifying "gitblit" uses the internal login message.
+#
+# SINCE 0.7.0
+# BASEFOLDER
+web.loginMessage = gitblit
+
+# This is the message displayed above the repositories table.
+# This can point to a file with Markdown content.
+# Specifying "gitblit" uses the internal welcome message.
+#
+# SINCE 0.5.0
+# BASEFOLDER
+web.repositoriesMessage = gitblit
+
+# Ordered list of charsets/encodings to use when trying to display a blob.
+# If empty, UTF-8 and ISO-8859-1 are used.  The server's default charset
+# is always appended to the encoding list.  If all encodings fail to cleanly
+# decode the blob content, UTF-8 will be used with the standard malformed
+# input/unmappable character replacement strings.
+# 
+# SPACE-DELIMITED
+# SINCE 1.0.0
+web.blobEncodings = UTF-8 ISO-8859-1
+
+# Manually set the default timezone to be used by Gitblit for display in the 
+# web ui.  This value is independent of the JVM timezone.  Specifying a blank
+# value will default to the JVM timezone.
+# e.g. America/New_York, US/Pacific, UTC, Europe/Berlin
+#
+# SINCE 0.9.0
+# RESTART REQUIRED
+web.timezone =
+
+# Use the client timezone when formatting dates.
+# This uses AJAX to determine the browser's timezone and may require more
+# server overhead because a Wicket session is created.  All Gitblit pages
+# attempt to be stateless, if possible.
+#
+# SINCE 0.5.0
+# RESTART REQUIRED
+web.useClientTimezone = false
+
+# Time format
+# <http://download.oracle.com/javase/6/docs/api/java/text/SimpleDateFormat.html>
+#
+# SINCE 0.8.0
+web.timeFormat = HH:mm
+
+# Short date format
+# <http://download.oracle.com/javase/6/docs/api/java/text/SimpleDateFormat.html>
+#
+# SINCE 0.5.0
+web.datestampShortFormat = yyyy-MM-dd
+
+# Long date format
+#
+# SINCE 0.8.0
+web.datestampLongFormat = EEEE, MMMM d, yyyy
+
+# Long timestamp format
+# <http://download.oracle.com/javase/6/docs/api/java/text/SimpleDateFormat.html>
+#
+# SINCE 0.5.0
+web.datetimestampLongFormat = EEEE, MMMM d, yyyy HH:mm Z
+
+# Mount URL parameters
+# This setting controls if pretty or parameter URLs are used.
+# i.e.
+# if true:
+#     http://localhost/commit/myrepo/abcdef
+# if false:
+#     http://localhost/commit/?r=myrepo&h=abcdef
+#
+# SINCE 0.5.0
+# RESTART REQUIRED
+web.mountParameters = true
+
+# Some servlet containers (e.g. Tomcat >= 6.0.10) disallow '/' (%2F) encoding
+# in URLs as a security precaution for proxies.  This setting tells Gitblit
+# to preemptively replace '/' with '*' or '!' for url string parameters.
+#
+# <https://issues.apache.org/jira/browse/WICKET-1303>
+# <http://tomcat.apache.org/security-6.html#Fixed_in_Apache_Tomcat_6.0.10>
+# Add *-Dorg.apache.tomcat.util.buf.UDecoder.ALLOW_ENCODED_SLASH=true* to your
+# *CATALINA_OPTS* or to your JVM launch parameters
+#
+# SINCE 0.5.2
+web.forwardSlashCharacter = /
+
+# Show other URLs on the summary page for accessing your git repositories
+# Use spaces to separate urls.
+#
+# {0} is the token for the repository name
+# {1} is the token for the username
+#
+# The username is only practical if you have setup your other git serving
+# solutions accounts to have the same username as the Gitblit account.
+#
+# e.g.
+# web.otherUrls = ssh://localhost/git/{0} git://localhost/git/{0} https://{1}@localhost/r/{0}
+#
+# SPACE-DELIMITED
+# SINCE 0.5.0
+web.otherUrls = 
+
+# Should app-specific clone links be displayed for SourceTree, SparkleShare, etc?
+#
+# SINCE 1.3.0
+web.allowAppCloneLinks = true
+
+# Choose how to present the repositories list.
+#   grouped = group nested/subfolder repositories together (no sorting)
+#   flat = flat list of repositories (sorting allowed)
+#
+# SINCE 0.5.0
+web.repositoryListType = grouped
+
+# If using a grouped repository list and there are repositories at the
+# root level of your repositories folder, you may specify the displayed
+# group name with this setting.  This value is only used for web presentation.
+#
+# SINCE 0.5.0
+web.repositoryRootGroupName = main
+
+# Display the repository swatch color next to the repository name link in the 
+# repositories list. 
+#
+# SINCE 0.8.0
+web.repositoryListSwatches = true
+
+# Choose the diff presentation style: gitblt, gitweb, or plain
+#
+# SINCE 0.5.0
+web.diffStyle = gitblit
+
+# Control if email addresses are shown in web ui
+#
+# SINCE 0.5.0
+web.showEmailAddresses = true
+
+# Shows a combobox in the page links header with commit, committer, and author
+# search selection.  Default search is commit.
+#
+# SINCE 0.5.0
+web.showSearchTypeSelection = false
+
+# Generates a line graph of repository activity over time on the Summary page.
+# This uses the Google Charts API.
+#
+# SINCE 0.5.0 
+web.generateActivityGraph = true
+
+# The default number of days to show on the activity page.
+# Value must exceed 0 else default of 7 is used
+#
+# SINCE 0.8.0
+web.activityDuration = 7
+
+# Choices for days of activity to display.
+#
+# SPACE-DELIMITED
+# SINCE 1.3.0
+web.activityDurationChoices = 1 3 7 14 21 28
+
+# The number of days of commits to cache in memory for the dashboard, activity,
+# and project pages.  A value of 0 will disable all caching and will parse commits
+# in each repository per-request.  If the value > 0 these pages will try to fulfill
+# requests using the commit cache.  If the request specifies a period which falls
+# outside the commit cache window, then the cache will be ignored and the request
+# will be fulfilled by brute-force parsing all relevant commits per-repository.
+#
+# Consider the values specified for *web.activityDurationChoices* when setting
+# the cache size AND consider adjusting the JVM -Xmx heap parameter appropriately.
+#
+# SINCE 1.3.0
+# RESTART REQUIRED
+web.activityCacheDays = 14
+
+# Case-insensitive list of authors to exclude from metrics.  Useful for
+# eliminating bots.
+#
+# SPACE-DELIMITED
+# SINCE 1.3.0
+web.metricAuthorExclusions =
+
+# The number of commits to display on the summary page
+# Value must exceed 0 else default of 20 is used
+#
+# SINCE 0.5.0
+web.summaryCommitCount = 16
+
+# The number of tags/branches to display on the summary page.
+# -1 = all tags/branches
+# 0 = hide tags/branches
+# N = N tags/branches
+#
+# SINCE 0.5.0
+web.summaryRefsCount = 5
+
+# The number of items to show on a page before showing the first, prev, next
+# pagination links.  A default of 50 is used for any invalid value.
+#
+# SINCE 0.5.0
+web.itemsPerPage = 50
+
+# The number of reflog changes to display on the overview page
+# Value must exceed 0 else default of 5 is used
+#
+# SINCE 1.3.0
+web.overviewReflogCount = 5
+
+# The number of reflog changes to show on a reflog page before show the first,
+#  prev, next pagination links.  A default of 10 is used for any invalid value.
+#
+# SINCE 1.3.0
+web.reflogChangesPerPage = 10
+
+# Registered file extensions to ignore during Lucene indexing
+#
+# SPACE-DELIMITED
+# SINCE 0.9.0
+web.luceneIgnoreExtensions = 7z arc arj bin bmp dll doc docx exe gif gz jar jpg lib lzh odg odf odt pdf ppt png so swf xcf xls xlsx zip
+
+# Registered extensions for google-code-prettify
+#
+# SPACE-DELIMITED
+# SINCE 0.5.0
+web.prettyPrintExtensions = aea agc basic c cbm cl clj cpp cs css dart el erl erlang frm fs go groovy hs htm html java js latex lisp ll llvm lsp lua ml moxie mumps n nemerle pascal php pl prefs properties proto py r R rb rd Rd rkt s S scala scm sh Splus sql ss tcl tex vb vbs vhd vhdl wiki xml xq xquery yaml yml ymlapollo
+
+# Registered extensions for markdown transformation
+#
+# SPACE-DELIMITED
+# CASE-SENSITIVE
+# SINCE 0.5.0
+web.markdownExtensions = md mkd markdown MD MKD
+
+# Image extensions
+#
+# SPACE-DELIMITED
+# SINCE 0.5.0
+web.imageExtensions = bmp jpg gif png 
+
+# Registered extensions for binary blobs
+#
+# SPACE-DELIMITED
+# SINCE 0.5.0
+web.binaryExtensions = jar pdf tar.gz zip
+
+# Aggressive heap management will run the garbage collector on every generated
+# page.  This slows down page generation a little but improves heap consumption. 
+#
+# SINCE 0.5.0
+web.aggressiveHeapManagement = false
+
+# Run the webapp in debug mode
+#
+# SINCE 0.5.0
+# RESTART REQUIRED
+web.debugMode = false
+
+# Force a default locale for all users, ignoring the browser's settings.
+# An empty value allows Gitblit to use the translation preferred by the browser.
+#
+# Changing this value while the server is running will only affect new sessions.
+#
+# e.g. web.forceDefaultLocale = en
+#
+# SINCE 1.3.0
+web.forceDefaultLocale = 
+
+# Enable/disable global regex substitutions (i.e. shared across repositories)
+#
+# SINCE 0.5.0
+regex.global = true
+
+# Example global regex substitutions
+# Use !!! to separate the search pattern and the replace pattern
+# searchpattern!!!replacepattern
+# SINCE 0.5.0
+regex.global.bug = \\b(Bug:)(\\s*[#]?|-){0,1}(\\d+)\\b!!!<a href="http://somehost/bug/$3">Bug-Id: $3</a>
+# SINCE 0.5.0
+regex.global.changeid = \\b(Change-Id:\\s*)([A-Za-z0-9]*)\\b!!!<a href="http://somehost/changeid/$2">Change-Id: $2</a>
+
+# Example per-repository regex substitutions overrides global
+# SINCE 0.5.0
+regex.myrepository.bug = \\b(Bug:)(\\s*[#]?|-){0,1}(\\d+)\\b!!!<a href="http://elsewhere/bug/$3">Bug-Id: $3</a>
+
+#
+# Mail Settings
+# SINCE 0.6.0
+#
+# Mail settings are used to notify administrators of received federation proposals
+#
+
+# ip or hostname of smtp server
+#
+# SINCE 0.6.0
+mail.server =
+
+# port to use for smtp requests
+#
+# SINCE 0.6.0
+mail.port = 25
+
+# debug the mail executor
+#
+# SINCE 0.6.0
+mail.debug = false
+
+# use SMTPs flag
+mail.smtps = false
+
+# if your smtp server requires authentication, supply the credentials here
+#
+# SINCE 0.6.0
+mail.username =
+# SINCE 0.6.0
+mail.password =
+
+# from address for generated emails
+#
+# SINCE 0.6.0
+mail.fromAddress = 
+
+# List of email addresses for the Gitblit administrators
+#
+# SPACE-DELIMITED
+# SINCE 0.6.0
+mail.adminAddresses = 
+
+# List of email addresses for sending push email notifications.
+#
+# This key currently requires use of the sendemail.groovy hook script.
+# If you set sendemail.groovy in *groovy.postReceiveScripts* then email
+# notifications for all repositories (regardless of access restrictions!)
+# will be sent to these addresses.
+#
+# SPACE-DELIMITED
+# SINCE 0.8.0
+mail.mailingLists =
+
+#
+# Federation Settings
+# SINCE 0.6.0
+#
+# A Gitblit federation is a way to backup one Gitblit instance to another.
+#
+# *git.enableGitServlet* must be true to use this feature.
+
+# Your federation name is used for federation status acknowledgments.  If it is
+# unset, and you elect to send a status acknowledgment, your Gitblit instance
+# will be identified by its hostname, if available, else your internal ip address.
+# The source Gitblit instance will also append your external IP address to your
+# identification to differentiate multiple pulling systems behind a single proxy.
+#
+# SINCE 0.6.0
+federation.name =
+
+# Specify the passphrase of this Gitblit instance.
+#
+# An unspecified (empty) passphrase disables processing federation requests.
+#
+# This value can be anything you want: an integer, a sentence, an haiku, etc.
+# Keep the value simple, though, to avoid Java properties file encoding issues.
+#
+# Changing your passphrase will break any registrations you have established with other
+# Gitblit instances.
+#
+# CASE-SENSITIVE
+# SINCE 0.6.0
+# RESTART REQUIRED *(only to enable or disable federation)*
+federation.passphrase =
+
+# Control whether or not this Gitblit instance can receive federation proposals
+# from another Gitblit instance.  Registering a federated Gitblit is a manual
+# process.  Proposals help to simplify that process by allowing a remote Gitblit
+# instance to send your Gitblit instance the federation pull data.
+#
+# SINCE 0.6.0
+federation.allowProposals = false
+
+# The destination folder for cached federation proposals.
+# Use forward slashes even on Windows!!
+#
+# SINCE 0.6.0
+# BASEFOLDER
+federation.proposalsFolder = ${baseFolder}/proposals
+
+# The default pull frequency if frequency is unspecified on a registration
+#
+# SINCE 0.6.0
+federation.defaultFrequency = 60 mins
+
+# Federation Sets are named groups of repositories.  The Federation Sets are 
+# available for selection in the repository settings page.  You can assign a
+# repository to one or more sets and then distribute the token for the set.
+# This allows you to grant federation pull access to a subset of your available
+# repositories.  Tokens for federation sets only grant repository pull access.
+#
+# SPACE-DELIMITED
+# CASE-SENSITIVE
+# SINCE 0.6.0
+federation.sets = 
+
+# Federation pull registrations
+# Registrations are read once, at startup.
+#
+# RESTART REQUIRED
+#
+# frequency:
+#   The shortest frequency allowed is every 5 minutes
+#   Decimal frequency values are cast to integers
+#   Frequency values may be specified in mins, hours, or days
+#   Values that can not be parsed or are unspecified default to *federation.defaultFrequency*
+#
+# folder:
+#   if unspecified, the folder is *git.repositoriesFolder*
+#   if specified, the folder is relative to *git.repositoriesFolder*
+#
+# bare:
+#   if true, each repository will be created as a *bare* repository and will not
+#   have a working directory.
+#
+#   if false, each repository will be created as a normal repository suitable
+#   for local work.
+#
+# mirror:
+#   if true, each repository HEAD is reset to *origin/master* after each pull.
+#   The repository will be flagged *isFrozen* after the initial clone.
+#
+#   if false, each repository HEAD will point to the FETCH_HEAD of the initial
+#   clone from the origin until pushed to or otherwise manipulated.
+#
+# mergeAccounts:
+#   if true, remote accounts and their permissions are merged into your 
+#   users.properties file 
+#
+# notifyOnError:
+#   if true and the mail configuration is properly set, administrators will be
+#   notified by email of pull failures
+#
+# include and exclude:
+#   Space-delimited list of repositories to include or exclude from pull
+#   may be * wildcard to include or exclude all
+#   may use fuzzy match (e.g. org.eclipse.*)
+
+#
+# (Nearly) Perfect Mirror example
+#
+
+#federation.example1.url = https://go.gitblit.com
+#federation.example1.token = 6f3b8a24bf970f17289b234284c94f43eb42f0e4
+#federation.example1.frequency = 120 mins
+#federation.example1.folder =
+#federation.example1.bare = true 
+#federation.example1.mirror = true 
+#federation.example1.mergeAccounts = true
+
+#
+# Advanced Realm Settings
+#
+
+# Auto-creates user accounts based on the servlet container principal.  This
+# assumes that your Gitblit install is a protected resource and your container's
+# authentication process intercepts all Gitblit requests.
+#
+# SINCE 1.3.0
+realm.container.autoCreateAccounts = false
+
+# The WindowsUserService must be backed by another user service for standard user
+# and team management.
+# default: users.conf
+#
+# RESTART REQUIRED
+# BASEFOLDER
+# SINCE 1.3.0
+realm.windows.backingUserService = ${baseFolder}/users.conf
+
+# Allow or prohibit Windows guest account logins
+#
+# SINCE 1.3.0
+realm.windows.allowGuests = false
+
+# The default domain for authentication.
+#
+# If specified, this domain will be used for authentication UNLESS the supplied
+# login name manually specifies a domain (.e.g. mydomain\james or james@mydomain)
+#
+# If unspecified, the username must be specified in UPN format (name@domain).
+#
+# if "." (dot) is specified, ONLY the local account database will be used.
+#
+# SINCE 1.3.0
+realm.windows.defaultDomain =
+
+# The SalesforceUserService must be backed by another user service for standard user
+# and team management.
+# default: users.conf
+#
+# RESTART REQUIRED
+# BASEFOLDER
+# SINCE 1.3.0
+realm.salesforce.backingUserService = ${baseFolder}/users.conf
+
+# Restrict the Salesforce user to members of this org.
+# default: 0 (i.e. do not check the Org ID)
+#
+# SINCE 1.3.0
+realm.salesforce.orgId = 0
+
+# URL of the LDAP server.
+# To use encrypted transport, use either ldaps:// URL for SSL or ldap+tls:// to
+# send StartTLS command.
+#
+# SINCE 1.0.0
+realm.ldap.server = ldap://localhost
+
+# Login username for LDAP searches.
+# If this value is unspecified, anonymous LDAP login will be used.
+# 
+# e.g. mydomain\\username
+#
+# SINCE 1.0.0
+realm.ldap.username = cn=Directory Manager
+
+# Login password for LDAP searches.
+#
+# SINCE 1.0.0
+realm.ldap.password = password
+
+# The LdapUserService must be backed by another user service for standard user
+# and team management.
+# default: users.conf
+#
+# SINCE 1.0.0
+# RESTART REQUIRED
+# BASEFOLDER
+realm.ldap.backingUserService = ${baseFolder}/users.conf
+
+# Delegate team membership control to LDAP.
+#
+# If true, team user memberships will be specified by LDAP groups.  This will
+# disable team selection in Edit User and user selection in Edit Team.
+#
+# If false, LDAP will only be used for authentication and Gitblit will maintain
+# team memberships with the *realm.ldap.backingUserService*.
+#
+# SINCE 1.0.0
+realm.ldap.maintainTeams = false
+
+# Root node for all LDAP users
+#
+# This is the root node from which subtree user searches will begin.
+# If blank, Gitblit will search ALL nodes.
+#
+# SINCE 1.0.0
+realm.ldap.accountBase = OU=Users,OU=UserControl,OU=MyOrganization,DC=MyDomain
+
+# Filter criteria for LDAP users
+#
+# Query pattern to use when searching for a user account. This may be any valid 
+# LDAP query expression, including the standard (&) and (|) operators.
+#
+# Variables may be injected via the ${variableName} syntax.
+# Recognized variables are:
+#    ${username} - The text entered as the user name
+#
+# SINCE 1.0.0
+realm.ldap.accountPattern = (&(objectClass=person)(sAMAccountName=${username}))
+
+# Root node for all LDAP groups to be used as Gitblit Teams
+#
+# This is the root node from which subtree team searches will begin.
+# If blank, Gitblit will search ALL nodes.  
+#
+# SINCE 1.0.0
+realm.ldap.groupBase = OU=Groups,OU=UserControl,OU=MyOrganization,DC=MyDomain
+
+# Filter criteria for LDAP groups
+#
+# Query pattern to use when searching for a team. This may be any valid 
+# LDAP query expression, including the standard (&) and (|) operators.
+#
+# Variables may be injected via the ${variableName} syntax.
+# Recognized variables are:
+#    ${username} - The text entered as the user name
+#    ${dn} - The Distinguished Name of the user logged in
+#
+# All attributes from the LDAP User record are available. For example, if a user
+# has an attribute "fullName" set to "John", "(fn=${fullName})" will be 
+# translated to "(fn=John)".
+#
+# SINCE 1.0.0
+realm.ldap.groupMemberPattern = (&(objectClass=group)(member=${dn}))
+
+# LDAP users or groups that should be given administrator privileges.
+#
+# Teams are specified with a leading '@' character.  Groups with spaces in the
+# name can be entered as "@team name".
+#
+# e.g. realm.ldap.admins = john @git_admins "@git admins"
+#
+# SPACE-DELIMITED
+# SINCE 1.0.0
+realm.ldap.admins = @Git_Admins
+
+# Attribute(s) on the USER record that indicate their display (or full) name.
+# Leave blank for no mapping available in LDAP.
+#
+# This may be a single attribute, or a string of multiple attributes.  Examples:
+#  displayName - Uses the attribute 'displayName' on the user record
+#  ${personalTitle}. ${givenName} ${surname} - Will concatenate the 3 
+#       attributes together, with a '.' after personalTitle
+#
+# SINCE 1.0.0
+realm.ldap.displayName = displayName
+
+# Attribute(s) on the USER record that indicate their email address.
+# Leave blank for no mapping available in LDAP.
+#
+# This may be a single attribute, or a string of multiple attributes.  Examples:
+#  email - Uses the attribute 'email' on the user record
+#  ${givenName}.${surname}@gitblit.com -Will concatenate the 2 attributes
+#       together with a '.' and '@' creating something like first.last@gitblit.com 
+#
+# SINCE 1.0.0
+realm.ldap.email = email
+
+# Defines the cache period to be used when caching LDAP queries. This is currently
+# only used for LDAP user synchronization.
+#
+# Must be of the form '<long> <TimeUnit>' where <TimeUnit> is one of 'MILLISECONDS', 'SECONDS', 'MINUTES', 'HOURS', 'DAYS' 
+# default: 2 MINUTES
+#
+# RESTART REQUIRED
+realm.ldap.ldapCachePeriod = 2 MINUTES
+
+# Defines whether to synchronize all LDAP users into the backing user service
+#
+# Valid values: true, false
+# If left blank, false is assumed
+realm.ldap.synchronizeUsers.enable = false
+
+# Defines whether to delete non-existent LDAP users from the backing user service
+# during synchronization. depends on  realm.ldap.synchronizeUsers.enable = true
+#
+# Valid values: true, false
+# If left blank, true is assumed
+realm.ldap.synchronizeUsers.removeDeleted = true
+
+# Attribute on the USER record that indicate their username to be used in gitblit
+# when synchronizing users from LDAP
+# if blank, Gitblit will use uid
+# For MS Active Directory this may be sAMAccountName
+realm.ldap.uid = uid
+
+# The RedmineUserService must be backed by another user service for standard user
+# and team management.
+# default: users.conf
+#
+# RESTART REQUIRED
+# BASEFOLDER
+realm.redmine.backingUserService = ${baseFolder}/users.conf
+
+# URL of the Redmine.
+realm.redmine.url = http://example.com/redmine
+
+#
+# Server Settings
+#
+
+# The temporary folder to decompress the embedded gitblit webapp. 
+#
+# SINCE 0.5.0
+# RESTART REQUIRED
+# BASEFOLDER
+server.tempFolder = ${baseFolder}/temp
+
+# Use Jetty NIO connectors.  If false, Jetty Socket connectors will be used.
+#
+# SINCE 0.5.0
+# RESTART REQUIRED
+server.useNio = true
+
+# Specify the maximum number of concurrent http/https worker threads to allow. 
+#
+# SINCE 1.3.0
+# RESTART REQUIRED
+server.threadPoolSize = 50
+
+# Context path for the GO application.  You might want to change the context
+# path if running Gitblit behind a proxy layer such as mod_proxy.
+#
+# SINCE 0.7.0
+# RESTART REQUIRED
+server.contextPath = /
+
+# Standard http port to serve.  <= 0 disables this connector.
+# On Unix/Linux systems, ports < 1024 require root permissions.
+# Recommended value: 80 or 8080
+#
+# SINCE 0.5.0
+# RESTART REQUIRED
+server.httpPort = 0
+
+# Secure/SSL https port to serve. <= 0 disables this connector.
+# On Unix/Linux systems, ports < 1024 require root permissions.
+# Recommended value: 443 or 8443
+#
+# SINCE 0.5.0
+# RESTART REQUIRED
+server.httpsPort = 8443
+
+# Port for serving an Apache JServ Protocol (AJP) 1.3 connector for integrating
+# Gitblit GO into an Apache HTTP server setup.  <= 0 disables this connector.
+# Recommended value: 8009
+#
+# SINCE 0.9.0
+# RESTART REQUIRED
+server.ajpPort = 0
+
+# Specify the interface for Jetty to bind the standard connector.
+# You may specify an ip or an empty value to bind to all interfaces.
+# Specifying localhost will result in Gitblit ONLY listening to requests to
+# localhost.
+#
+# SINCE 0.5.0
+# RESTART REQUIRED
+server.httpBindInterface = localhost
+
+# Specify the interface for Jetty to bind the secure connector.
+# You may specify an ip or an empty value to bind to all interfaces.
+# Specifying localhost will result in Gitblit ONLY listening to requests to
+# localhost.
+#
+# SINCE 0.5.0
+# RESTART REQUIRED
+server.httpsBindInterface = localhost
+
+# Specify the interface for Jetty to bind the AJP connector.
+# You may specify an ip or an empty value to bind to all interfaces.
+# Specifying localhost will result in Gitblit ONLY listening to requests to
+# localhost.
+#
+# SINCE 0.9.0
+# RESTART REQUIRED
+server.ajpBindInterface = localhost
+
+# Alias of certificate to use for https/SSL serving.  If blank the first
+# certificate found in the keystore will be used. 
+#
+# SINCE 1.2.0
+# RESTART REQUIRED
+server.certificateAlias = localhost
+
+# Password for SSL keystore.
+# Keystore password and certificate password must match.
+# This is provided for convenience, its probably more secure to set this value
+# using the --storePassword command line parameter.
+#
+# If you are using the official JRE or JDK from Oracle you may not have the
+# JCE Unlimited Strength Jurisdiction Policy files bundled with your JVM.  Because
+# of this, your store/key password can not exceed 7 characters.  If you require
+# longer passwords you may need to install the JCE Unlimited Strength Jurisdiction
+# Policy files from Oracle.
+#
+# http://www.oracle.com/technetwork/java/javase/downloads/index.html
+#
+# Gitblit and the Gitblit Certificate Authority will both indicate if Unlimited
+# Strength encryption is available.
+#
+# SINCE 0.5.0
+# RESTART REQUIRED
+server.storePassword = gitblit
+
+# If serving over https (recommended) you might consider requiring clients to
+# authenticate with ssl certificates.  If enabled, only https clients with the
+# a valid client certificate will be able to access Gitblit.
+#
+# If disabled, client certificate authentication is optional and will be tried
+# first before falling-back to form authentication or basic authentication.
+#
+# Requiring client certificates to access any of Gitblit may be too extreme,
+# consider this carefully.
+#
+# SINCE 1.2.0
+# RESTART REQUIRED
+server.requireClientCertificates = false
+
+# Port for shutdown monitor to listen on.
+#
+# SINCE 0.5.0
+# RESTART REQUIRED
+server.shutdownPort = 8081
diff --git a/distrib/groovy/.gitignore b/src/main/distrib/data/groovy/.gitignore
similarity index 100%
rename from distrib/groovy/.gitignore
rename to src/main/distrib/data/groovy/.gitignore
diff --git a/distrib/groovy/blockpush.groovy b/src/main/distrib/data/groovy/blockpush.groovy
similarity index 100%
rename from distrib/groovy/blockpush.groovy
rename to src/main/distrib/data/groovy/blockpush.groovy
diff --git a/distrib/groovy/fogbugz.groovy b/src/main/distrib/data/groovy/fogbugz.groovy
similarity index 100%
rename from distrib/groovy/fogbugz.groovy
rename to src/main/distrib/data/groovy/fogbugz.groovy
diff --git a/distrib/groovy/jenkins.groovy b/src/main/distrib/data/groovy/jenkins.groovy
similarity index 100%
rename from distrib/groovy/jenkins.groovy
rename to src/main/distrib/data/groovy/jenkins.groovy
diff --git a/distrib/groovy/localclone.groovy b/src/main/distrib/data/groovy/localclone.groovy
similarity index 100%
rename from distrib/groovy/localclone.groovy
rename to src/main/distrib/data/groovy/localclone.groovy
diff --git a/distrib/groovy/protect-refs.groovy b/src/main/distrib/data/groovy/protect-refs.groovy
similarity index 100%
rename from distrib/groovy/protect-refs.groovy
rename to src/main/distrib/data/groovy/protect-refs.groovy
diff --git a/src/main/distrib/data/groovy/sendmail-html.groovy b/src/main/distrib/data/groovy/sendmail-html.groovy
new file mode 100644
index 0000000..2912e56
--- /dev/null
+++ b/src/main/distrib/data/groovy/sendmail-html.groovy
@@ -0,0 +1,516 @@
+/*
+ * 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.
+ */
+import com.gitblit.GitBlit
+import com.gitblit.Keys
+import com.gitblit.models.RepositoryModel
+import com.gitblit.models.TeamModel
+import com.gitblit.models.UserModel
+import com.gitblit.utils.JGitUtils
+import java.text.SimpleDateFormat
+
+import org.eclipse.jgit.api.Status;
+import org.eclipse.jgit.api.errors.JGitInternalException;
+import org.eclipse.jgit.diff.DiffEntry;
+import org.eclipse.jgit.diff.DiffFormatter;
+import org.eclipse.jgit.diff.RawTextComparator;
+import org.eclipse.jgit.diff.DiffEntry.ChangeType;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.IndexDiff;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Repository
+import org.eclipse.jgit.lib.Config
+import org.eclipse.jgit.patch.FileHeader;
+import org.eclipse.jgit.revwalk.RevCommit
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.transport.ReceiveCommand
+import org.eclipse.jgit.transport.ReceiveCommand.Result
+import org.eclipse.jgit.treewalk.FileTreeIterator;
+import org.eclipse.jgit.treewalk.EmptyTreeIterator;
+import org.eclipse.jgit.treewalk.CanonicalTreeParser;
+import org.eclipse.jgit.util.io.DisabledOutputStream;
+import org.slf4j.Logger
+import groovy.xml.MarkupBuilder
+
+import java.io.IOException;
+import java.security.MessageDigest
+
+
+/**
+ * Sample Gitblit Post-Receive Hook: sendmail-html
+ *
+ * The Post-Receive hook is executed AFTER the pushed commits have been applied
+ * to the Git repository.  This is the appropriate point to trigger an
+ * integration build or to send a notification.
+ * 
+ * This script is only executed when pushing to *Gitblit*, not to other Git
+ * tooling you may be using.
+ * 
+ * If this script is specified in *groovy.postReceiveScripts* of gitblit.properties
+ * or web.xml then it will be executed by any repository when it receives a
+ * push.  If you choose to share your script then you may have to consider
+ * tailoring control-flow based on repository access restrictions.
+ *
+ * Scripts may also be specified per-repository in the repository settings page.
+ * Shared scripts will be excluded from this list of available scripts.
+ * 
+ * This script is dynamically reloaded and it is executed within it's own
+ * exception handler so it will not crash another script nor crash Gitblit.
+ *
+ * If you want this hook script to fail and abort all subsequent scripts in the
+ * chain, "return false" at the appropriate failure points.
+ * 
+ * Bound Variables:
+ *  gitblit         Gitblit Server               com.gitblit.GitBlit
+ *  repository      Gitblit Repository           com.gitblit.models.RepositoryModel
+ *  user            Gitblit User                 com.gitblit.models.UserModel
+ *  commands        JGit commands                Collection<org.eclipse.jgit.transport.ReceiveCommand>
+ *  url             Base url for Gitblit         java.lang.String
+ *  logger          Logs messages to Gitblit     org.slf4j.Logger
+ *  clientLogger    Logs messages to Git client  com.gitblit.utils.ClientLogger
+ *
+ * Accessing Gitblit Custom Fields:
+ *   def myCustomField = repository.customFields.myCustomField
+ *  
+ */
+
+com.gitblit.models.UserModel userModel = user
+
+// Indicate we have started the script
+logger.info("sendmail-html hook triggered by ${user.username} for ${repository.name}")
+
+/*
+ * Primitive email notification.
+ * This requires the mail settings to be properly configured in Gitblit.
+ */
+
+Repository r = gitblit.getRepository(repository.name)
+
+// reuse existing repository config settings, if available
+Config config = r.getConfig()
+def mailinglist = config.getString('hooks', null, 'mailinglist')
+def emailprefix = config.getString('hooks', null, 'emailprefix')
+
+// set default values
+def toAddresses = []
+if (emailprefix == null) {
+    emailprefix = '[Gitblit]'
+}
+
+if (mailinglist != null) {
+    def addrs = mailinglist.split(/(,|\s)/)
+    toAddresses.addAll(addrs)
+}
+
+// add all mailing lists defined in gitblit.properties or web.xml
+toAddresses.addAll(gitblit.getStrings(Keys.mail.mailingLists))
+
+// add all team mailing lists
+def teams = gitblit.getRepositoryTeams(repository)
+for (team in teams) {
+    TeamModel model = gitblit.getTeamModel(team)
+    if (model.mailingLists) {
+        toAddresses.addAll(model.mailingLists)
+    }
+}
+
+// add all mailing lists for the repository
+toAddresses.addAll(repository.mailingLists)
+
+// define the summary and commit urls
+def repo = repository.name
+def summaryUrl = url + "/summary?r=$repo"
+def baseCommitUrl = url + "/commit?r=$repo&h="
+def baseBlobDiffUrl = url + "/blobdiff/?r=$repo&h="
+def baseCommitDiffUrl = url + "/commitdiff/?r=$repo&h="
+def forwardSlashChar = gitblit.getString(Keys.web.forwardSlashCharacter, '/')
+
+if (gitblit.getBoolean(Keys.web.mountParameters, true)) {
+    repo = repo.replace('/', forwardSlashChar).replace('/', '%2F')
+    summaryUrl = url + "/summary/$repo"
+    baseCommitUrl = url + "/commit/$repo/"
+    baseBlobDiffUrl = url + "/blobdiff/$repo/"
+    baseCommitDiffUrl = url + "/commitdiff/$repo/"
+}
+
+class HtmlMailWriter {
+    Repository repository
+    def url
+    def baseCommitUrl
+    def baseCommitDiffUrl
+    def baseBlobDiffUrl
+    def mountParameters
+	def forwardSlashChar
+	def includeGravatar
+	def shortCommitIdLength
+    def commitCount = 0
+    def commands
+    def writer = new StringWriter();
+    def builder = new MarkupBuilder(writer)
+
+    def writeStyle() {
+        builder.style(type:"text/css", '''
+    .table td {
+        vertical-align: middle;
+    }
+    tr.noborder td {
+        border: none;
+        padding-top: 0px;
+    }
+    .gravatar-column {
+        width: 5%; 
+    }
+    .author-column {
+        width: 20%; 
+    }
+    .commit-column {
+        width: 5%; 
+    }
+    .status-column {
+        width: 10%;
+    }
+    .table-disable-hover.table tbody tr:hover td,
+    .table-disable-hover.table tbody tr:hover th {
+        background-color: inherit;
+    }
+    .table-disable-hover.table-striped tbody tr:nth-child(odd):hover td,
+    .table-disable-hover.table-striped tbody tr:nth-child(odd):hover th {
+      background-color: #f9f9f9;
+    }
+    ''')
+    }
+
+    def writeBranchTitle(type, name, action, number) {
+        builder.div('class' : 'pageTitle') {
+			builder.span('class':'project') {
+				mkp.yield "$type "
+				span('class': 'repository', name )
+				if (number > 0) {
+					mkp.yield " $action ($number commits)"
+				} else {
+					mkp.yield " $action"
+				}
+			}
+        }
+    }
+
+    def writeBranchDeletedTitle(type, name) {
+		builder.div('class' : 'pageTitle', 'style':'color:red') {
+			builder.span('class':'project') {
+				mkp.yield "$type "
+				span('class': 'repository', name )
+				mkp.yield " deleted"
+			}
+		}
+    }
+
+    def commitUrl(RevCommit commit) {
+        "${baseCommitUrl}$commit.id.name"
+    }
+
+    def commitDiffUrl(RevCommit commit) {
+        "${baseCommitDiffUrl}$commit.id.name"
+    }
+
+    def encoded(String path) {
+        path.replace('/', forwardSlashChar).replace('/', '%2F')
+    }
+
+    def blobDiffUrl(objectId, path) {
+        if (mountParameters) {
+            // REST style
+            "${baseBlobDiffUrl}${objectId.name()}/${encoded(path)}"
+        } else {
+            "${baseBlobDiffUrl}${objectId.name()}&f=${path}"
+        }
+
+    }
+
+    def writeCommitTable(commits, includeChangedPaths=true) {
+        // Write commits table
+        builder.table('class':"table table-disable-hover") {
+            thead {
+                tr {
+					th(colspan: includeGravatar ? 2 : 1, "Author")
+                    th( "Commit" )
+                    th( "Message" )
+                }
+            }
+            tbody() {
+
+                // Write all the commits
+                for (commit in commits) {
+                    writeCommit(commit)
+
+					if (includeChangedPaths) {
+						// Write detail on that particular commit
+						tr('class' : 'noborder') {
+							td (colspan: includeGravatar ? 3 : 2)
+							td (colspan:2) { writeStatusTable(commit) }
+						}
+					}
+                }
+            }
+        }
+    }
+
+    def writeCommit(commit) {
+        def abbreviated = repository.newObjectReader().abbreviate(commit.id, shortCommitIdLength).name()
+        def author = commit.authorIdent.name
+        def email = commit.authorIdent.emailAddress
+        def message = commit.shortMessage
+        builder.tr {
+			if (includeGravatar) {
+				td('class':"gravatar-column") {
+					img(src:gravatarUrl(email), 'class':"gravatar")
+				}
+			}
+            td('class':"author-column", author)
+            td('class':"commit-column") {
+                a(href:commitUrl(commit)) {
+                    span('class':"label label-info",  abbreviated )
+                }
+            }
+            td {
+                mkp.yield message
+                a('class':'link', href:commitDiffUrl(commit), " [commitdiff]" )
+            }
+        }
+    }
+
+    def writeStatusLabel(style, tooltip) {
+        builder.span('class' : style,  'title' : tooltip )
+    }
+
+    def writeAddStatusLine(ObjectId id, FileHeader header) {		
+        builder.td('class':'changeType') {
+            writeStatusLabel("addition", "addition")
+        }
+        builder.td {
+            a(href:blobDiffUrl(id, header.newPath), header.newPath)
+        }
+    }
+
+    def writeCopyStatusLine(ObjectId id, FileHeader header) {
+        builder.td('class':'changeType') {
+            writeStatusLabel("rename", "rename")
+        }
+        builder.td() {
+            a(href:blobDiffUrl(id, header.newPath), header.oldPath + " copied to " + header.newPath)
+        }
+    }
+
+    def writeDeleteStatusLine(ObjectId id, FileHeader header) {
+        builder.td('class':'changeType') {
+            writeStatusLabel("deletion", "deletion")
+        }
+        builder.td() {
+            a(href:blobDiffUrl(id, header.oldPath), header.oldPath)
+        }
+    }
+
+    def writeModifyStatusLine(ObjectId id, FileHeader header) {
+        builder.td('class':'changeType') {
+			writeStatusLabel("modification", "modification")
+        }
+        builder.td() {
+            a(href:blobDiffUrl(id, header.oldPath), header.oldPath)
+        }
+    }
+
+    def writeRenameStatusLine(ObjectId id, FileHeader header) {
+        builder.td('class':'changeType') {
+             writeStatusLabel("rename", "rename")
+        }
+        builder.td() {
+            mkp.yield header.oldPath
+			mkp.yieldUnescaped "<b> -&rt; </b>"
+			a(href:blobDiffUrl(id, header.newPath),  header.newPath)
+        }
+    }
+
+    def writeStatusLine(ObjectId id, FileHeader header) {
+        builder.tr {
+            switch (header.changeType) {
+                case ChangeType.ADD:
+                    writeAddStatusLine(id, header)
+                    break;
+                case ChangeType.COPY:
+                    writeCopyStatusLine(id, header)
+                    break;
+                case ChangeType.DELETE:
+                    writeDeleteStatusLine(id, header)
+                    break;
+                case ChangeType.MODIFY:
+                    writeModifyStatusLine(id, header)
+                    break;
+                case ChangeType.RENAME:
+                    writeRenameStatusLine(id, header)
+                    break;
+            }
+        }
+    }
+
+    def writeStatusTable(RevCommit commit) {
+        DiffFormatter formatter = new DiffFormatter(DisabledOutputStream.INSTANCE)
+        formatter.setRepository(repository)
+        formatter.setDetectRenames(true)
+        formatter.setDiffComparator(RawTextComparator.DEFAULT);
+
+        def diffs
+		RevWalk rw = new RevWalk(repository)
+        if (commit.parentCount > 0) {
+			RevCommit parent = rw.parseCommit(commit.parents[0].id)
+            diffs = formatter.scan(parent.tree, commit.tree)
+        } else {
+            diffs = formatter.scan(new EmptyTreeIterator(),
+                                   new CanonicalTreeParser(null, rw.objectReader, commit.tree))
+        }
+		rw.dispose()
+        // Write status table
+        builder.table('class':"plain") {
+            tbody() {
+                for (DiffEntry entry in diffs) {
+                    FileHeader header = formatter.toFileHeader(entry)
+                    writeStatusLine(commit.id, header)
+                }
+            }
+        }
+    }
+
+
+    def md5(text) {
+
+        def digest = MessageDigest.getInstance("MD5")
+
+        //Quick MD5 of text
+        def hash = new BigInteger(1, digest.digest(text.getBytes()))
+                         .toString(16)
+                         .padLeft(32, "0")
+        hash.toString()
+    }
+
+    def gravatarUrl(email) {
+        def cleaned = email.trim().toLowerCase()
+        "http://www.gravatar.com/avatar/${md5(cleaned)}?s=30"
+    }
+
+    def writeNavbar() {
+        builder.div('class':"navbar navbar-fixed-top") {
+            div('class':"navbar-inner") {
+                div('class':"container") {
+                    a('class':"brand", href:"${url}", title:"GitBlit") {
+                        img(src:"${url}/gitblt_25_white.png",
+                            width:"79",
+                            height:"25",
+                            'class':"logo")
+                    }
+                }
+            }
+        }
+    }
+
+    def write() {
+        builder.html {
+            head {
+                link(rel:"stylesheet", href:"${url}/bootstrap/css/bootstrap.css")
+                link(rel:"stylesheet", href:"${url}/gitblit.css")
+				link(rel:"stylesheet", href:"${url}/bootstrap/css/bootstrap-responsive.css")
+                writeStyle()
+            }
+            body {
+
+                writeNavbar()
+
+				div('class':"container") {
+
+                for (command in commands) {
+                    def ref = command.refName
+                    def refType = 'Branch'
+                    if (ref.startsWith('refs/heads/')) {
+                        ref  = command.refName.substring('refs/heads/'.length())
+                    } else if (ref.startsWith('refs/tags/')) {
+                        ref  = command.refName.substring('refs/tags/'.length())
+                        refType = 'Tag'
+                    }
+
+                    switch (command.type) {
+                        case ReceiveCommand.Type.CREATE:
+							def commits = JGitUtils.getRevLog(repository, command.oldId.name, command.newId.name).reverse()
+							commitCount += commits.size()
+							if (refType == 'Branch') {
+								// new branch
+								writeBranchTitle(refType, ref, "created", commits.size())
+								writeCommitTable(commits, true)
+							} else {
+								// new tag
+								writeBranchTitle(refType, ref, "created", 0)
+								writeCommitTable(commits, false)
+							}
+                            break
+                        case ReceiveCommand.Type.UPDATE:
+                            def commits = JGitUtils.getRevLog(repository, command.oldId.name, command.newId.name).reverse()
+                            commitCount += commits.size()
+                            // fast-forward branch commits table
+                            // Write header
+                            writeBranchTitle(refType, ref, "updated", commits.size())
+                            writeCommitTable(commits)
+                            break
+                        case ReceiveCommand.Type.UPDATE_NONFASTFORWARD:
+                            def commits = JGitUtils.getRevLog(repository, command.oldId.name, command.newId.name).reverse()
+                            commitCount += commits.size()
+                            // non-fast-forward branch commits table
+                            // Write header
+                            writeBranchTitle(refType, ref, "updated [NON fast-forward]", commits.size())
+                            writeCommitTable(commits)
+                            break
+                        case ReceiveCommand.Type.DELETE:
+                            // deleted branch/tag
+                            writeBranchDeletedTitle(refType, ref)
+                            break
+                        default:
+                            break
+                    }
+                }
+                }
+            }
+        }
+        writer.toString()
+    }
+
+}
+
+def mailWriter = new HtmlMailWriter()
+mailWriter.repository = r
+mailWriter.baseCommitUrl = baseCommitUrl
+mailWriter.baseBlobDiffUrl = baseBlobDiffUrl
+mailWriter.baseCommitDiffUrl = baseCommitDiffUrl
+mailWriter.forwardSlashChar = forwardSlashChar
+mailWriter.commands = commands
+mailWriter.url = url
+mailWriter.mountParameters = GitBlit.getBoolean(Keys.web.mountParameters, true)
+mailWriter.includeGravatar = GitBlit.getBoolean(Keys.web.allowGravatar, true)
+mailWriter.shortCommitIdLength = GitBlit.getInteger(Keys.web.shortCommitIdLength, 8)
+
+def content = mailWriter.write()
+
+// close the repository reference
+r.close()
+
+// tell Gitblit to send the message (Gitblit filters duplicate addresses)
+def repositoryName = repository.name.substring(0, repository.name.length() - 4)
+gitblit.sendHtmlMail("${emailprefix} ${userModel.displayName} pushed ${mailWriter.commitCount} commits => $repositoryName",
+                     content,
+                     toAddresses)
diff --git a/distrib/groovy/sendmail.groovy b/src/main/distrib/data/groovy/sendmail.groovy
similarity index 100%
rename from distrib/groovy/sendmail.groovy
rename to src/main/distrib/data/groovy/sendmail.groovy
diff --git a/distrib/groovy/thebuggenie.groovy b/src/main/distrib/data/groovy/thebuggenie.groovy
similarity index 100%
rename from distrib/groovy/thebuggenie.groovy
rename to src/main/distrib/data/groovy/thebuggenie.groovy
diff --git a/distrib/projects.conf b/src/main/distrib/data/projects.conf
similarity index 100%
rename from distrib/projects.conf
rename to src/main/distrib/data/projects.conf
diff --git a/distrib/users.conf b/src/main/distrib/data/users.conf
similarity index 100%
rename from distrib/users.conf
rename to src/main/distrib/data/users.conf
diff --git a/src/main/distrib/federation.properties b/src/main/distrib/federation.properties
new file mode 100644
index 0000000..72058f0
--- /dev/null
+++ b/src/main/distrib/federation.properties
@@ -0,0 +1,83 @@
+#
+# Git Repository Settings
+#
+
+# Base folder for repositories
+# Use forward slashes even on Windows!!
+# e.g. c:/gitrepos
+#
+# SINCE 0.5.0
+# RESTART REQUIRED
+git.repositoriesFolder = ${baseFolder}/git
+
+# Search the repositories folder subfolders for other repositories.
+# Repositories MAY NOT be nested (i.e. one repository within another)
+# but they may be grouped together in subfolders.
+# e.g. c:/gitrepos/libraries/mylibrary.git
+#      c:/gitrepos/libraries/myotherlibrary.git
+#
+# SINCE 0.5.0
+git.searchRepositoriesSubfolders = true
+
+# Your federation name is used for federation status acknowledgments.  If it is
+# unset, and you elect to send a status acknowledgment, your Gitblit instance
+# will be identified by its hostname, if available, else your internal ip address.
+# The source Gitblit instance will also append your external IP address to your
+# identification to differentiate multiple pulling systems behind a single proxy.
+#
+# SINCE 0.6.0
+federation.name =
+
+# Federation pull registrations
+# Registrations are read once, at startup.
+#
+# RESTART REQUIRED
+#
+# frequency:
+#   The shortest frequency allowed is every 5 minutes
+#   Decimal frequency values are cast to integers
+#   Frequency values may be specified in mins, hours, or days
+#   Values that can not be parsed or are unspecified default to *federation.defaultFrequency*
+#
+# folder:
+#   if unspecified, the folder is *git.repositoriesFolder*
+#   if specified, the folder is relative to *git.repositoriesFolder*
+#
+# bare:
+#   if true, each repository will be created as a *bare* repository and will not
+#   have a working directory.
+#
+#   if false, each repository will be created as a normal repository suitable
+#   for local work.
+#
+# mirror:
+#   if true, each repository HEAD is reset to *origin/master* after each pull.
+#   The repository will be flagged *isFrozen* after the initial clone.
+#
+#   if false, each repository HEAD will point to the FETCH_HEAD of the initial
+#   clone from the origin until pushed to or otherwise manipulated.
+#
+# mergeAccounts:
+#   if true, remote accounts and their permissions are merged into your 
+#   users.properties file 
+#
+# notifyOnError:
+#   if true and the mail configuration is properly set, administrators will be
+#   notified by email of pull failures
+#
+# include and exclude:
+#   Space-delimited list of repositories to include or exclude from pull
+#   may be * wildcard to include or exclude all
+#   may use fuzzy match (e.g. org.eclipse.*)
+
+#
+# (Nearly) Perfect Mirror example
+#
+
+#federation.example1.url = https://go.gitblit.com
+#federation.example1.token = 6f3b8a24bf970f17289b234284c94f43eb42f0e4
+#federation.example1.frequency = 120 mins
+#federation.example1.folder =
+#federation.example1.bare = true 
+#federation.example1.mirror = true 
+#federation.example1.mergeAccounts = true
diff --git a/src/main/distrib/linux/add-indexed-branch.sh b/src/main/distrib/linux/add-indexed-branch.sh
new file mode 100644
index 0000000..d31f43e
--- /dev/null
+++ b/src/main/distrib/linux/add-indexed-branch.sh
@@ -0,0 +1,21 @@
+#!/bin/bash
+# --------------------------------------------------------------------------
+# This is for Lucene search integration.
+#
+# Allows you to add an indexed branch specification to the repository config
+# for all matching repositories in the specified folder.
+#
+# All repositories are included unless excluded using a --skip parameter.
+# --skip supports simple wildcard fuzzy matching however only 1 asterisk is
+# allowed per parameter.
+#
+# Always use forward-slashes for the path separator in your parameters!!
+#
+# Set FOLDER to the server's git.repositoriesFolder
+# Set BRANCH ("default" or fully qualified ref - i.e. refs/heads/master)
+# Set EXCLUSIONS for any repositories that you do not want to change
+# --------------------------------------------------------------------------
+SET FOLDER=git
+SET EXCLUSIONS=--skip test.git --skip group/test*
+SET BRANCH=default
+java -cp gitblit.jar;"%CD%\ext\*" com.gitblit.AddIndexedBranch --repositoriesFolder %FOLDER% --branch %BRANCH% %EXCLUSIONS%
\ No newline at end of file
diff --git a/src/main/distrib/linux/authority.sh b/src/main/distrib/linux/authority.sh
new file mode 100644
index 0000000..ce5c237
--- /dev/null
+++ b/src/main/distrib/linux/authority.sh
@@ -0,0 +1,2 @@
+#!/bin/bash
+java -cp gitblit.jar com.gitblit.authority.Launcher --baseFolder data
diff --git a/src/main/distrib/linux/gitblit-stop.sh b/src/main/distrib/linux/gitblit-stop.sh
new file mode 100644
index 0000000..2fef203
--- /dev/null
+++ b/src/main/distrib/linux/gitblit-stop.sh
@@ -0,0 +1,2 @@
+#!/bin/bash
+java -jar gitblit.jar --baseFolder data --stop
diff --git a/src/main/distrib/linux/gitblit.sh b/src/main/distrib/linux/gitblit.sh
new file mode 100644
index 0000000..7d631e7
--- /dev/null
+++ b/src/main/distrib/linux/gitblit.sh
@@ -0,0 +1,2 @@
+#!/bin/bash
+java -jar gitblit.jar --baseFolder data
diff --git a/src/main/distrib/linux/install-service-centos.sh b/src/main/distrib/linux/install-service-centos.sh
new file mode 100644
index 0000000..19d28c7
--- /dev/null
+++ b/src/main/distrib/linux/install-service-centos.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+sudo cp service-centos.sh /etc/init.d/gitblit
+sudo chkconfig --add gitblit
diff --git a/src/main/distrib/linux/install-service-ubuntu.sh b/src/main/distrib/linux/install-service-ubuntu.sh
new file mode 100644
index 0000000..72c4e6c
--- /dev/null
+++ b/src/main/distrib/linux/install-service-ubuntu.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+sudo cp service-ubuntu.sh /etc/init.d/gitblit
+sudo update-rc.d gitblit defaults
diff --git a/distrib/java-proxy-config.sh b/src/main/distrib/linux/java-proxy-config.sh
similarity index 100%
rename from distrib/java-proxy-config.sh
rename to src/main/distrib/linux/java-proxy-config.sh
diff --git a/distrib/gitblit-centos b/src/main/distrib/linux/service-centos.sh
similarity index 100%
rename from distrib/gitblit-centos
rename to src/main/distrib/linux/service-centos.sh
diff --git a/distrib/gitblit-ubuntu b/src/main/distrib/linux/service-ubuntu.sh
similarity index 100%
rename from distrib/gitblit-ubuntu
rename to src/main/distrib/linux/service-ubuntu.sh
diff --git a/src/main/distrib/win/add-indexed-branch.cmd b/src/main/distrib/win/add-indexed-branch.cmd
new file mode 100644
index 0000000..a7c4451
--- /dev/null
+++ b/src/main/distrib/win/add-indexed-branch.cmd
@@ -0,0 +1,20 @@
+@REM --------------------------------------------------------------------------
+@REM This is for Lucene search integration.
+@REM
+@REM Allows you to add an indexed branch specification to the repository config
+@REM for all matching repositories in the specified folder.
+@REM
+@REM All repositories are included unless excluded using a --skip parameter.
+@REM --skip supports simple wildcard fuzzy matching however only 1 asterisk is
+@REM allowed per parameter.
+@REM
+@REM Always use forward-slashes for the path separator in your parameters!!
+@REM
+@REM Set FOLDER to the server's git.repositoriesFolder
+@REM Set BRANCH ("default" or fully qualified ref - i.e. refs/heads/master)
+@REM Set EXCLUSIONS for any repositories that you do not want to change
+@REM --------------------------------------------------------------------------
+@SET FOLDER=c:/gitblit/git
+@SET EXCLUSIONS=--skip test.git --skip group/test*
+@SET BRANCH=default
+@java -cp gitblit.jar;"%CD%\ext\*" com.gitblit.AddIndexedBranch --repositoriesFolder %FOLDER% --branch %BRANCH% %EXCLUSIONS% %*
diff --git a/distrib/amd64/gitblit.exe b/src/main/distrib/win/amd64/gitblit.exe
similarity index 100%
rename from distrib/amd64/gitblit.exe
rename to src/main/distrib/win/amd64/gitblit.exe
Binary files differ
diff --git a/src/main/distrib/win/authority.cmd b/src/main/distrib/win/authority.cmd
new file mode 100644
index 0000000..f9a1864
--- /dev/null
+++ b/src/main/distrib/win/authority.cmd
@@ -0,0 +1 @@
+@java -cp gitblit.jar com.gitblit.authority.Launcher --baseFolder data %*
diff --git a/src/main/distrib/win/gitblit-stop.cmd b/src/main/distrib/win/gitblit-stop.cmd
new file mode 100644
index 0000000..34f0f4b
--- /dev/null
+++ b/src/main/distrib/win/gitblit-stop.cmd
@@ -0,0 +1 @@
+@java -jar gitblit.jar --stop --baseFolder data %*
diff --git a/src/main/distrib/win/gitblit.cmd b/src/main/distrib/win/gitblit.cmd
new file mode 100644
index 0000000..1a6d7e0
--- /dev/null
+++ b/src/main/distrib/win/gitblit.cmd
@@ -0,0 +1 @@
+@java -jar gitblit.jar --baseFolder data %*
diff --git a/distrib/gitblitw.exe b/src/main/distrib/win/gitblitw.exe
similarity index 100%
rename from distrib/gitblitw.exe
rename to src/main/distrib/win/gitblitw.exe
Binary files differ
diff --git a/distrib/ia64/gitblit.exe b/src/main/distrib/win/ia64/gitblit.exe
similarity index 100%
rename from distrib/ia64/gitblit.exe
rename to src/main/distrib/win/ia64/gitblit.exe
Binary files differ
diff --git a/src/main/distrib/win/installService.cmd b/src/main/distrib/win/installService.cmd
new file mode 100644
index 0000000..a684ab2
--- /dev/null
+++ b/src/main/distrib/win/installService.cmd
@@ -0,0 +1,38 @@
+@REM Install Gitblit as a Windows service.
+
+@REM gitblitw.exe (prunmgr.exe) is a GUI application for monitoring 
+@REM and configuring the Gitblit procrun service.
+@REM
+@REM By default this tool launches the service properties dialog
+@REM but it also has some other very useful functionality.
+@REM
+@REM http://commons.apache.org/daemon/procrun.html
+
+@REM arch = x86, amd64, or ia32
+SET ARCH=amd64
+
+@REM Be careful not to introduce trailing whitespace after the ^ characters.
+@REM Use ; or # to separate values in the --StartParams parameter.
+"%CD%\%ARCH%\gitblit.exe"  //IS//gitblit ^
+		 --DisplayName="gitblit" ^
+		 --Description="a pure Java Git solution" ^
+		 --Startup=auto ^
+		 --LogPath="%CD%\logs" ^
+		 --LogLevel=INFO ^
+		 --LogPrefix=gitblit ^
+		 --StdOutput=auto ^
+		 --StdError=auto ^
+		 --StartPath="%CD%" ^
+		 --StartClass=org.moxie.MxLauncher ^
+		 --StartMethod=main ^
+		 --StartParams="--storePassword;gitblit;--baseFolder;%CD%\data" ^
+		 --StartMode=jvm ^
+		 --StopPath="%CD%" ^
+		 --StopClass=org.moxie.MxLauncher ^
+		 --StopMethod=main ^
+		 --StopParams="--stop;--baseFolder;%CD%\data" ^
+		 --StopMode=jvm ^
+		 --Classpath="%CD%\gitblit.jar" ^
+		 --Jvm=auto ^
+		 --JvmMx=1024
+		 
\ No newline at end of file
diff --git a/distrib/uninstallService.cmd b/src/main/distrib/win/uninstallService.cmd
similarity index 100%
rename from distrib/uninstallService.cmd
rename to src/main/distrib/win/uninstallService.cmd
diff --git a/distrib/x86/gitblit.exe b/src/main/distrib/win/x86/gitblit.exe
similarity index 100%
rename from distrib/x86/gitblit.exe
rename to src/main/distrib/win/x86/gitblit.exe
Binary files differ
diff --git a/src/main/java/.gitignore b/src/main/java/.gitignore
new file mode 100644
index 0000000..b978906
--- /dev/null
+++ b/src/main/java/.gitignore
@@ -0,0 +1 @@
+/clientapps.json
diff --git a/src/main/java/WEB-INF/web.xml b/src/main/java/WEB-INF/web.xml
new file mode 100644
index 0000000..cf71465
--- /dev/null
+++ b/src/main/java/WEB-INF/web.xml
@@ -0,0 +1,292 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<web-app version="2.4"
+	xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
+
+	<!-- The base folder is used to specify the root location of your Gitblit data.
+	
+			${baseFolder}/gitblit.properties
+			${baseFolder}/users.conf
+			${baseFolder}/projects.conf
+			${baseFolder}/robots.txt
+			${baseFolder}/git
+			${baseFolder}/groovy
+			${baseFolder}/groovy/grape
+			${baseFolder}/proposals
+
+		By default, this location is WEB-INF/data.  It is recommended to set this
+		path to a location outside your webapps folder that is writable by your
+		servlet container.  Gitblit will copy the WEB-INF/data files to that
+		location for you when it restarts.  This approach makes upgrading simpler.
+		All you have to do is set this parameter for the new release and then
+		review the defaults for any new settings.  Settings are always versioned
+		with a SINCE x.y.z attribute and also noted in the release changelog.
+		-->
+	<context-param>
+		<param-name>baseFolder</param-name>
+		<param-value>${contextFolder}/WEB-INF/data</param-value>
+	</context-param>
+	
+	<!-- Gitblit Displayname -->
+	<display-name>
+		Gitblit - @gb.version@
+	</display-name>
+
+	<!-- PARAMS --> 
+	 
+	<!-- Gitblit Context Listener --><!-- STRIP	 
+	<listener>
+ 		<listener-class>com.gitblit.GitBlit</listener-class>
+ 	</listener>STRIP --> 	
+	
+	
+	<!-- Git Servlet
+		 <url-pattern> MUST match: 
+			* GitFilter
+			* com.gitblit.Constants.GIT_PATH
+			* Wicket Filter ignorePaths parameter -->
+	<servlet>
+		<servlet-name>GitServlet</servlet-name>
+		<servlet-class>com.gitblit.git.GitServlet</servlet-class>
+	</servlet>
+	<servlet-mapping>
+		<servlet-name>GitServlet</servlet-name>		
+		<url-pattern>/git/*</url-pattern>
+	</servlet-mapping>
+
+	
+	<!-- SparkleShare Invite Servlet
+		 <url-pattern> MUST match: 
+			* com.gitblit.Constants.SPARKLESHARE_INVITE_PATH
+			* Wicket Filter ignorePaths parameter -->
+	<servlet>
+		<servlet-name>SparkleShareInviteServlet</servlet-name>
+		<servlet-class>com.gitblit.SparkleShareInviteServlet</servlet-class>
+	</servlet>
+	<servlet-mapping>
+		<servlet-name>SparkleShareInviteServlet</servlet-name>		
+		<url-pattern>/sparkleshare/*</url-pattern>
+	</servlet-mapping>
+
+	
+	<!-- Syndication Servlet
+		 <url-pattern> MUST match: 
+			* SyndicationFilter
+			* com.gitblit.Constants.SYNDICATION_PATH
+			* Wicket Filter ignorePaths parameter -->
+	<servlet>
+		<servlet-name>SyndicationServlet</servlet-name>
+		<servlet-class>com.gitblit.SyndicationServlet</servlet-class>		
+	</servlet>
+	<servlet-mapping>
+		<servlet-name>SyndicationServlet</servlet-name>
+		<url-pattern>/feed/*</url-pattern>
+	</servlet-mapping>
+	
+	
+	<!-- Zip Servlet
+		 <url-pattern> MUST match: 
+			* ZipServlet
+			* com.gitblit.Constants.ZIP_PATH
+			* Wicket Filter ignorePaths parameter -->
+	<servlet>
+		<servlet-name>ZipServlet</servlet-name>
+		<servlet-class>com.gitblit.DownloadZipServlet</servlet-class>		
+	</servlet>
+	<servlet-mapping>
+		<servlet-name>ZipServlet</servlet-name>
+		<url-pattern>/zip/*</url-pattern>
+	</servlet-mapping>
+	
+	
+	<!-- Federation Servlet
+		 <url-pattern> MUST match: 
+		 	* com.gitblit.Constants.FEDERATION_PATH		 
+			* Wicket Filter ignorePaths parameter -->
+	<servlet>
+		<servlet-name>FederationServlet</servlet-name>
+		<servlet-class>com.gitblit.FederationServlet</servlet-class>		
+	</servlet>
+	<servlet-mapping>
+		<servlet-name>FederationServlet</servlet-name>
+		<url-pattern>/federation/*</url-pattern>
+	</servlet-mapping>	
+	
+	
+	<!-- Rpc Servlet
+		 <url-pattern> MUST match: 
+		 	* com.gitblit.Constants.RPC_PATH		 
+			* Wicket Filter ignorePaths parameter -->
+	<servlet>
+		<servlet-name>RpcServlet</servlet-name>
+		<servlet-class>com.gitblit.RpcServlet</servlet-class>		
+	</servlet>
+	<servlet-mapping>
+		<servlet-name>RpcServlet</servlet-name>
+		<url-pattern>/rpc/*</url-pattern>
+	</servlet-mapping>	
+
+
+	<!-- Pages Servlet
+		 <url-pattern> MUST match: 
+			* PagesFilter
+			* com.gitblit.Constants.PAGES_PATH
+			* Wicket Filter ignorePaths parameter -->
+	<servlet>
+		<servlet-name>PagesServlet</servlet-name>
+		<servlet-class>com.gitblit.PagesServlet</servlet-class>
+	</servlet>
+	<servlet-mapping>
+		<servlet-name>PagesServlet</servlet-name>		
+		<url-pattern>/pages/*</url-pattern>
+	</servlet-mapping>	
+
+	
+	<!-- Logo Servlet
+		 <url-pattern> MUST match: 
+			* Wicket Filter ignorePaths parameter -->
+	<servlet>
+		<servlet-name>LogoServlet</servlet-name>
+		<servlet-class>com.gitblit.LogoServlet</servlet-class>
+	</servlet>
+	<servlet-mapping>
+		<servlet-name>LogoServlet</servlet-name>		
+		<url-pattern>/logo.png</url-pattern>
+	</servlet-mapping>
+
+
+	<!-- Robots.txt Servlet
+		 <url-pattern> MUST match: 
+			* Wicket Filter ignorePaths parameter -->
+	<servlet>
+		<servlet-name>RobotsTxtServlet</servlet-name>
+		<servlet-class>com.gitblit.RobotsTxtServlet</servlet-class>
+	</servlet>
+	<servlet-mapping>
+		<servlet-name>RobotsTxtServlet</servlet-name>		
+		<url-pattern>/robots.txt</url-pattern>
+	</servlet-mapping>
+
+	
+	<!-- Git Access Restriction Filter
+		 <url-pattern> MUST match: 
+			* GitServlet
+			* com.gitblit.Constants.GIT_PATH
+			* Wicket Filter ignorePaths parameter -->
+	<filter>
+		<filter-name>GitFilter</filter-name>
+		<filter-class>com.gitblit.GitFilter</filter-class>
+	</filter>
+	<filter-mapping>
+		<filter-name>GitFilter</filter-name>
+		<url-pattern>/git/*</url-pattern>
+	</filter-mapping>
+	
+	
+	<!-- Syndication Restriction Filter
+		 <url-pattern> MUST match: 
+			* SyndicationServlet
+			* com.gitblit.Constants.SYNDICATION_PATH
+			* Wicket Filter ignorePaths parameter -->
+	<filter>
+		<filter-name>SyndicationFilter</filter-name>
+		<filter-class>com.gitblit.SyndicationFilter</filter-class>
+	</filter>
+	<filter-mapping>
+		<filter-name>SyndicationFilter</filter-name>
+		<url-pattern>/feed/*</url-pattern>
+	</filter-mapping>
+	
+	
+	<!-- Download Zip Restriction Filter
+		 <url-pattern> MUST match: 
+			* DownloadZipServlet
+			* com.gitblit.Constants.ZIP_PATH
+			* Wicket Filter ignorePaths parameter -->
+	<filter>
+		<filter-name>ZipFilter</filter-name>
+		<filter-class>com.gitblit.DownloadZipFilter</filter-class>
+	</filter>
+	<filter-mapping>
+		<filter-name>ZipFilter</filter-name>
+		<url-pattern>/zip/*</url-pattern>
+	</filter-mapping>
+
+		
+	<!-- Rpc Restriction Filter
+		 <url-pattern> MUST match: 
+			* RpcServlet
+			* com.gitblit.Constants.RPC_PATH
+			* Wicket Filter ignorePaths parameter -->
+	<filter>
+		<filter-name>RpcFilter</filter-name>
+		<filter-class>com.gitblit.RpcFilter</filter-class>
+	</filter>
+	<filter-mapping>
+		<filter-name>RpcFilter</filter-name>
+		<url-pattern>/rpc/*</url-pattern>
+	</filter-mapping>
+
+
+	<!-- Pges Restriction Filter
+		 <url-pattern> MUST match: 
+			* PagesServlet
+			* com.gitblit.Constants.PAGES_PATH
+			* Wicket Filter ignorePaths parameter -->
+	<filter>
+		<filter-name>PagesFilter</filter-name>
+		<filter-class>com.gitblit.PagesFilter</filter-class>
+	</filter>
+	<filter-mapping>
+		<filter-name>PagesFilter</filter-name>
+		<url-pattern>/pages/*</url-pattern>
+	</filter-mapping>
+	
+	<filter>
+		<filter-name>EnforceAuthenticationFilter</filter-name>
+		<filter-class>com.gitblit.EnforceAuthenticationFilter</filter-class>
+	</filter>
+	<filter-mapping>
+        <filter-name>EnforceAuthenticationFilter</filter-name>
+        <url-pattern>/*</url-pattern>
+    </filter-mapping>
+
+
+	<!-- Wicket Filter -->
+    <filter>
+        <filter-name>wicketFilter</filter-name>
+        <filter-class>
+            com.gitblit.wicket.GitblitWicketFilter
+        </filter-class>
+        <init-param>
+            <param-name>applicationClassName</param-name>
+            <param-value>com.gitblit.wicket.GitBlitWebApp</param-value>
+        </init-param>
+        <init-param>
+            <param-name>ignorePaths</param-name>
+            <!-- Paths should match 
+             	* SyndicationFilter <url-pattern>
+             	* SyndicationServlet <url-pattern>
+             	* com.gitblit.Constants.SYNDICATION_PATH
+             	* GitFilter <url-pattern>
+             	* GitServlet <url-pattern>
+             	* com.gitblit.Constants.GIT_PATH
+             	* SparkleshareInviteServlet <url-pattern>
+             	* com.gitblit.Constants.SPARKLESHARE_INVITE_PATH
+             	* Zipfilter <url-pattern>
+             	* ZipServlet <url-pattern>
+             	* com.gitblit.Constants.ZIP_PATH
+             	* FederationServlet <url-pattern>
+             	* RpcFilter <url-pattern>
+             	* RpcServlet <url-pattern>
+             	* PagesFilter <url-pattern>
+             	* PagesServlet <url-pattern>
+             	* com.gitblit.Constants.PAGES_PATH -->
+            <param-value>git/,feed/,zip/,federation/,rpc/,pages/,robots.txt,logo.png,sparkleshare/</param-value>
+        </init-param>
+    </filter>
+    <filter-mapping>
+        <filter-name>wicketFilter</filter-name>
+        <url-pattern>/*</url-pattern>
+    </filter-mapping>
+</web-app>
\ No newline at end of file
diff --git a/src/main/java/WEB-INF/weblogic.xml b/src/main/java/WEB-INF/weblogic.xml
new file mode 100644
index 0000000..cd08111
--- /dev/null
+++ b/src/main/java/WEB-INF/weblogic.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<wls:weblogic-web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:wls="http://www.bea.com/ns/weblogic/90" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd http://www.bea.com/ns/weblogic/90 http://www.bea.com/ns/weblogic/90/weblogic-web-app.xsd">
+    <wls:weblogic-version>12.1.1</wls:weblogic-version>
+    <wls:context-root>gitblit</wls:context-root>
+    <wls:container-descriptor>
+         <wls:show-archived-real-path-enabled>true</wls:show-archived-real-path-enabled>
+         <wls:prefer-web-inf-classes>true</wls:prefer-web-inf-classes>
+    </wls:container-descriptor>
+</wls:weblogic-web-app> 
\ No newline at end of file
diff --git a/src/com/gitblit/.gitignore b/src/main/java/com/gitblit/.gitignore
similarity index 100%
rename from src/com/gitblit/.gitignore
rename to src/main/java/com/gitblit/.gitignore
diff --git a/src/com/gitblit/AccessRestrictionFilter.java b/src/main/java/com/gitblit/AccessRestrictionFilter.java
similarity index 100%
rename from src/com/gitblit/AccessRestrictionFilter.java
rename to src/main/java/com/gitblit/AccessRestrictionFilter.java
diff --git a/src/main/java/com/gitblit/AddIndexedBranch.java b/src/main/java/com/gitblit/AddIndexedBranch.java
new file mode 100644
index 0000000..7a16bbd
--- /dev/null
+++ b/src/main/java/com/gitblit/AddIndexedBranch.java
@@ -0,0 +1,149 @@
+/*
+ * 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;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
+
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.RepositoryCache.FileKey;
+import org.eclipse.jgit.lib.StoredConfig;
+import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
+import org.eclipse.jgit.util.FS;
+
+import com.beust.jcommander.JCommander;
+import com.beust.jcommander.Parameter;
+import com.beust.jcommander.ParameterException;
+import com.beust.jcommander.Parameters;
+import com.gitblit.models.RefModel;
+import com.gitblit.utils.ArrayUtils;
+import com.gitblit.utils.JGitUtils;
+import com.gitblit.utils.StringUtils;
+
+/**
+ * Utility class to add an indexBranch setting to matching repositories.
+ * 
+ * @author James Moger
+ * 
+ */
+public class AddIndexedBranch {
+
+	public static void main(String... args) {
+		Params params = new Params();
+		JCommander jc = new JCommander(params);
+		try {
+			jc.parse(args);
+		} catch (ParameterException t) {
+			System.err.println(t.getMessage());
+			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) {
+			boolean skip = false;
+			for (String exclusion : exclusions) {
+				if (StringUtils.fuzzyMatch(repo, exclusion)) {
+					skip = true;
+					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);
+					for (RefModel refModel : list) {
+						System.out.println(MessageFormat.format("adding [gitblit] indexBranch={0} for {1}", refModel.getName(), repo));
+						indexedBranches.add(refModel.getName());
+					}
+				}
+				else {
+					// add only one branch to index ('default' if not specified)
+					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) {
+						indexedBranches.add(branch);
+					}
+				}
+				config.setStringList("gitblit", null, "indexBranch", new ArrayList<String>(indexedBranches));
+				config.save();
+				modCount++;
+			} catch (Exception e) {
+				System.err.println(repo);
+				e.printStackTrace();
+			}
+		}
+		
+		System.out.println(MessageFormat.format("updated {0} repository configurations, skipped {1}", modCount, skipCount));
+	}
+
+	
+
+	/**
+	 * JCommander Parameters class for AddIndexedBranch.
+	 */
+	@Parameters(separators = " ")
+	private static class Params {
+
+		@Parameter(names = { "--repositoriesFolder" }, description = "The root repositories folder ", required = true)
+		public String folder;
+
+		@Parameter(names = { "--branch" }, description = "The branch to index", required = false)
+		public String branch = "default";
+
+		@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;
+	}
+}
diff --git a/src/main/java/com/gitblit/AuthenticationFilter.java b/src/main/java/com/gitblit/AuthenticationFilter.java
new file mode 100644
index 0000000..388452e
--- /dev/null
+++ b/src/main/java/com/gitblit/AuthenticationFilter.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright 2011 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit;
+
+import java.io.IOException;
+import java.security.Principal;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.gitblit.models.UserModel;
+import com.gitblit.utils.DeepCopier;
+import com.gitblit.utils.StringUtils;
+
+/**
+ * The AuthenticationFilter is a servlet filter that preprocesses requests that
+ * match its url pattern definition in the web.xml file.
+ * 
+ * http://en.wikipedia.org/wiki/Basic_access_authentication
+ * 
+ * @author James Moger
+ * 
+ */
+public abstract class AuthenticationFilter implements Filter {
+
+	protected static final String CHALLENGE = "Basic realm=\"" + Constants.NAME + "\"";
+
+	protected static final String SESSION_SECURED = "com.gitblit.secured";
+
+	protected transient Logger logger = LoggerFactory.getLogger(getClass());
+
+	/**
+	 * doFilter does the actual work of preprocessing the request to ensure that
+	 * the user may proceed.
+	 * 
+	 * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest,
+	 *      javax.servlet.ServletResponse, javax.servlet.FilterChain)
+	 */
+	@Override
+	public abstract void doFilter(final ServletRequest request, final ServletResponse response,
+			final FilterChain chain) throws IOException, ServletException;
+	
+	/**
+	 * Allow the filter to require a client certificate to continue processing.
+	 * 
+	 * @return true, if a client certificate is required
+	 */
+	protected boolean requiresClientCertificate() {
+		return false;
+	}
+
+	/**
+	 * Returns the full relative url of the request.
+	 * 
+	 * @param httpRequest
+	 * @return url
+	 */
+	protected String getFullUrl(HttpServletRequest httpRequest) {
+		String servletUrl = httpRequest.getContextPath() + httpRequest.getServletPath();
+		String url = httpRequest.getRequestURI().substring(servletUrl.length());
+		String params = httpRequest.getQueryString();
+		if (url.length() > 0 && url.charAt(0) == '/') {
+			url = url.substring(1);
+		}
+		String fullUrl = url + (StringUtils.isEmpty(params) ? "" : ("?" + params));
+		return fullUrl;
+	}
+
+	/**
+	 * Returns the user making the request, if the user has authenticated.
+	 * 
+	 * @param httpRequest
+	 * @return user
+	 */
+	protected UserModel getUser(HttpServletRequest httpRequest) {
+		UserModel user = GitBlit.self().authenticate(httpRequest, requiresClientCertificate());
+		return user;
+	}
+
+	/**
+	 * Taken from Jetty's LoginAuthenticator.renewSessionOnAuthentication()
+	 */
+	protected void newSession(HttpServletRequest request, HttpServletResponse response) {
+		HttpSession oldSession = request.getSession(false);
+		if (oldSession != null && oldSession.getAttribute(SESSION_SECURED) == null) {
+			synchronized (this) {
+				Map<String, Object> attributes = new HashMap<String, Object>();
+				Enumeration<String> e = oldSession.getAttributeNames();
+				while (e.hasMoreElements()) {
+					String name = e.nextElement();
+					attributes.put(name, oldSession.getAttribute(name));
+					oldSession.removeAttribute(name);
+				}
+				oldSession.invalidate();
+
+				HttpSession newSession = request.getSession(true);
+				newSession.setAttribute(SESSION_SECURED, Boolean.TRUE);
+				for (Map.Entry<String, Object> entry : attributes.entrySet()) {
+					newSession.setAttribute(entry.getKey(), entry.getValue());
+				}
+			}
+		}
+	}
+
+	/**
+	 * @see javax.servlet.Filter#init(javax.servlet.FilterConfig)
+	 */
+	@Override
+	public void init(final FilterConfig config) throws ServletException {
+	}
+
+	/**
+	 * @see javax.servlet.Filter#destroy()
+	 */
+	@Override
+	public void destroy() {
+	}
+
+	/**
+	 * Wraps a standard HttpServletRequest and overrides user principal methods.
+	 */
+	public static class AuthenticatedRequest extends HttpServletRequestWrapper {
+
+		private UserModel user;
+
+		public AuthenticatedRequest(HttpServletRequest req) {
+			super(req);
+			user = DeepCopier.copy(UserModel.ANONYMOUS);
+		}
+
+		UserModel getUser() {
+			return user;
+		}
+
+		void setUser(UserModel user) {
+			this.user = user;
+		}
+
+		@Override
+		public String getRemoteUser() {
+			return user.username;
+		}
+
+		@Override
+		public boolean isUserInRole(String role) {
+			if (role.equals(Constants.ADMIN_ROLE)) {
+				return user.canAdmin();
+			}
+			// Gitblit does not currently use actual roles in the traditional
+			// servlet container sense.  That is the reason this is marked
+			// deprecated, but I may want to revisit this.
+			return user.canAccessRepository(role);
+		}
+
+		@Override
+		public Principal getUserPrincipal() {
+			return user;
+		}
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/ConfigUserService.java b/src/main/java/com/gitblit/ConfigUserService.java
new file mode 100644
index 0000000..8a6c92f
--- /dev/null
+++ b/src/main/java/com/gitblit/ConfigUserService.java
@@ -0,0 +1,1105 @@
+/*
+ * Copyright 2011 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit;
+
+import java.io.File;
+import java.io.IOException;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.eclipse.jgit.lib.StoredConfig;
+import org.eclipse.jgit.storage.file.FileBasedConfig;
+import org.eclipse.jgit.util.FS;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.gitblit.Constants.AccessPermission;
+import com.gitblit.models.TeamModel;
+import com.gitblit.models.UserModel;
+import com.gitblit.models.UserRepositoryPreferences;
+import com.gitblit.utils.ArrayUtils;
+import com.gitblit.utils.DeepCopier;
+import com.gitblit.utils.StringUtils;
+
+/**
+ * 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 {
+
+	private static final String TEAM = "team";
+
+	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";
+
+	private static final String ROLE = "role";
+
+	private static final String MAILINGLIST = "mailingList";
+
+	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;
+
+	private final Logger logger = LoggerFactory.getLogger(ConfigUserService.class);
+
+	private final Map<String, UserModel> users = new ConcurrentHashMap<String, UserModel>();
+
+	private final Map<String, UserModel> cookies = new ConcurrentHashMap<String, UserModel>();
+
+	private final Map<String, TeamModel> teams = new ConcurrentHashMap<String, TeamModel>();
+
+	private volatile long lastModified;
+	
+	private volatile boolean forceReload;
+
+	public ConfigUserService(File realmFile) {
+		this.realmFile = realmFile;
+	}
+
+	/**
+	 * Setup the user service.
+	 * 
+	 * @param settings
+	 * @since 0.7.0
+	 */
+	@Override
+	public void setup(IStoredSettings settings) {
+	}
+
+	/**
+	 * Does the user service support changes to credentials?
+	 * 
+	 * @return true or false
+	 * @since 1.0.0
+	 */
+	@Override
+	public boolean supportsCredentialChanges() {
+		return true;
+	}
+
+	/**
+	 * Does the user service support changes to user display name?
+	 * 
+	 * @return true or false
+	 * @since 1.0.0
+	 */
+	@Override
+	public boolean supportsDisplayNameChanges() {
+		return true;
+	}
+
+	/**
+	 * Does the user service support changes to user email address?
+	 * 
+	 * @return true or false
+	 * @since 1.0.0
+	 */
+	@Override
+	public boolean supportsEmailAddressChanges() {
+		return true;
+	}
+
+	/**
+	 * Does the user service support changes to team memberships?
+	 * 
+	 * @return true or false
+	 * @since 1.0.0
+	 */	
+	public boolean supportsTeamMembershipChanges() {
+		return true;
+	}
+	
+	/**
+	 * Does the user service support cookie authentication?
+	 * 
+	 * @return true or false
+	 */
+	@Override
+	public boolean supportsCookies() {
+		return true;
+	}
+
+	/**
+	 * Returns the cookie value for the specified user.
+	 * 
+	 * @param model
+	 * @return cookie value
+	 */
+	@Override
+	public String getCookie(UserModel model) {
+		if (!StringUtils.isEmpty(model.cookie)) {
+			return model.cookie;
+		}
+		read();
+		UserModel storedModel = users.get(model.username.toLowerCase());
+		return storedModel.cookie;
+	}
+
+	/**
+	 * Authenticate a user based on their cookie.
+	 * 
+	 * @param cookie
+	 * @return a user object or null
+	 */
+	@Override
+	public UserModel authenticate(char[] cookie) {
+		String hash = new String(cookie);
+		if (StringUtils.isEmpty(hash)) {
+			return null;
+		}
+		read();
+		UserModel model = null;
+		if (cookies.containsKey(hash)) {
+			model = cookies.get(hash);
+		}
+		return model;
+	}
+
+	/**
+	 * Authenticate a user based on a username and password.
+	 * 
+	 * @param username
+	 * @param password
+	 * @return a user object or null
+	 */
+	@Override
+	public UserModel authenticate(String username, char[] password) {
+		read();
+		UserModel returnedUser = null;
+		UserModel user = getUserModel(username);
+		if (user == null) {
+			return null;
+		}
+		if (user.password.startsWith(StringUtils.MD5_TYPE)) {
+			// password digest
+			String md5 = StringUtils.MD5_TYPE + StringUtils.getMD5(new String(password));
+			if (user.password.equalsIgnoreCase(md5)) {
+				returnedUser = user;
+			}
+		} else if (user.password.startsWith(StringUtils.COMBINED_MD5_TYPE)) {
+			// username+password digest
+			String md5 = StringUtils.COMBINED_MD5_TYPE
+					+ StringUtils.getMD5(username.toLowerCase() + new String(password));
+			if (user.password.equalsIgnoreCase(md5)) {
+				returnedUser = user;
+			}
+		} else if (user.password.equals(new String(password))) {
+			// plain-text password
+			returnedUser = user;
+		}
+		return returnedUser;
+	}
+
+	/**
+	 * Logout a user.
+	 * 
+	 * @param user
+	 */
+	@Override
+	public void logout(UserModel user) {	
+	}
+	
+	/**
+	 * Retrieve the user object for the specified username.
+	 * 
+	 * @param username
+	 * @return a user object or null
+	 */
+	@Override
+	public UserModel getUserModel(String username) {
+		read();
+		UserModel model = users.get(username.toLowerCase());
+		if (model != null) {
+			// clone the model, otherwise all changes to this object are
+			// live and unpersisted
+			model = DeepCopier.copy(model);
+		}
+		return model;
+	}
+
+	/**
+	 * Updates/writes a complete user object.
+	 * 
+	 * @param model
+	 * @return true if update is successful
+	 */
+	@Override
+	public boolean updateUserModel(UserModel model) {
+		return updateUserModel(model.username, model);
+	}
+
+	/**
+	 * Updates/writes all specified user objects.
+	 * 
+	 * @param models a list of user models
+	 * @return true if update is successful
+	 * @since 1.2.0
+	 */
+	@Override
+	public boolean updateUserModels(Collection<UserModel> models) {
+		try {
+			read();
+			for (UserModel model : models) {
+				UserModel originalUser = users.remove(model.username.toLowerCase());
+				users.put(model.username.toLowerCase(), model);
+				// null check on "final" teams because JSON-sourced UserModel
+				// can have a null teams object
+				if (model.teams != null) {
+					for (TeamModel team : model.teams) {
+						TeamModel t = teams.get(team.name.toLowerCase());
+						if (t == null) {
+							// new team
+							team.addUser(model.username);
+							teams.put(team.name.toLowerCase(), team);
+						} else {
+							// do not clobber existing team definition
+							// maybe because this is a federated user
+							t.addUser(model.username);							
+						}
+					}
+
+					// check for implicit team removal
+					if (originalUser != null) {
+						for (TeamModel team : originalUser.teams) {
+							if (!model.isTeamMember(team.name)) {
+								team.removeUser(model.username);
+							}
+						}
+					}
+				}
+			}
+			write();
+			return true;
+		} catch (Throwable t) {
+			logger.error(MessageFormat.format("Failed to update user {0} models!", models.size()),
+					t);
+		}
+		return false;
+	}
+
+	/**
+	 * 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
+	 *            the user object to use for username
+	 * @return true if update is successful
+	 */
+	@Override
+	public boolean updateUserModel(String username, UserModel model) {
+		UserModel originalUser = null;
+		try {
+			read();
+			originalUser = users.remove(username.toLowerCase());
+			users.put(model.username.toLowerCase(), model);
+			// null check on "final" teams because JSON-sourced UserModel
+			// can have a null teams object
+			if (model.teams != null) {
+				for (TeamModel team : model.teams) {
+					TeamModel t = teams.get(team.name.toLowerCase());
+					if (t == null) {
+						// new team
+						team.addUser(username);
+						teams.put(team.name.toLowerCase(), team);
+					} else {
+						// do not clobber existing team definition
+						// maybe because this is a federated user
+						t.removeUser(username);
+						t.addUser(model.username);
+					}
+				}
+
+				// check for implicit team removal
+				if (originalUser != null) {
+					for (TeamModel team : originalUser.teams) {
+						if (!model.isTeamMember(team.name)) {
+							team.removeUser(username);
+						}
+					}
+				}
+			}
+			write();
+			return true;
+		} catch (Throwable t) {
+			if (originalUser != null) {
+				// restore original user
+				users.put(originalUser.username.toLowerCase(), originalUser);
+			} else {
+				// drop attempted add
+				users.remove(model.username.toLowerCase());
+			}
+			logger.error(MessageFormat.format("Failed to update user model {0}!", model.username),
+					t);
+		}
+		return false;
+	}
+
+	/**
+	 * Deletes the user object from the user service.
+	 * 
+	 * @param model
+	 * @return true if successful
+	 */
+	@Override
+	public boolean deleteUserModel(UserModel model) {
+		return deleteUser(model.username);
+	}
+
+	/**
+	 * Delete the user object with the specified username
+	 * 
+	 * @param username
+	 * @return true if successful
+	 */
+	@Override
+	public boolean deleteUser(String username) {
+		try {
+			// Read realm file
+			read();
+			UserModel model = users.remove(username.toLowerCase());
+			if (model == null) {
+				// user does not exist
+				return false;
+			}
+			// remove user from team
+			for (TeamModel team : model.teams) {
+				TeamModel t = teams.get(team.name);
+				if (t == null) {
+					// new team
+					team.removeUser(username);
+					teams.put(team.name.toLowerCase(), team);
+				} else {
+					// existing team
+					t.removeUser(username);
+				}
+			}
+			write();
+			return true;
+		} catch (Throwable t) {
+			logger.error(MessageFormat.format("Failed to delete user {0}!", username), t);
+		}
+		return false;
+	}
+
+	/**
+	 * Returns the list of all teams available to the login service.
+	 * 
+	 * @return list of all teams
+	 * @since 0.8.0
+	 */
+	@Override
+	public List<String> getAllTeamNames() {
+		read();
+		List<String> list = new ArrayList<String>(teams.keySet());
+		Collections.sort(list);
+		return list;
+	}
+
+	/**
+	 * Returns the list of all teams available to the login service.
+	 * 
+	 * @return list of all teams
+	 * @since 0.8.0
+	 */
+	@Override
+	public List<TeamModel> getAllTeams() {
+		read();
+		List<TeamModel> list = new ArrayList<TeamModel>(teams.values());
+		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
+	 */
+	@Override
+	public List<String> getTeamnamesForRepositoryRole(String role) {
+		List<String> list = new ArrayList<String>();
+		try {
+			read();
+			for (Map.Entry<String, TeamModel> entry : teams.entrySet()) {
+				TeamModel model = entry.getValue();
+				if (model.hasRepositoryPermission(role)) {
+					list.add(model.name);
+				}
+			}
+		} catch (Throwable t) {
+			logger.error(MessageFormat.format("Failed to get teamnames for role {0}!", role), t);
+		}
+		Collections.sort(list);
+		return list;
+	}
+
+	/**
+	 * 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
+	 * @return true if successful
+	 */
+	@Override
+	public boolean setTeamnamesForRepositoryRole(String role, List<String> teamnames) {
+		try {
+			Set<String> specifiedTeams = new HashSet<String>();
+			for (String teamname : teamnames) {
+				specifiedTeams.add(teamname.toLowerCase());
+			}
+
+			read();
+
+			// identify teams which require add or remove role
+			for (TeamModel team : teams.values()) {
+				// team has role, check against revised team list
+				if (specifiedTeams.contains(team.name.toLowerCase())) {
+					team.addRepositoryPermission(role);
+				} else {
+					// remove role from team
+					team.removeRepositoryPermission(role);
+				}
+			}
+
+			// persist changes
+			write();
+			return true;
+		} catch (Throwable t) {
+			logger.error(MessageFormat.format("Failed to set teams for role {0}!", role), t);
+		}
+		return false;
+	}
+
+	/**
+	 * Retrieve the team object for the specified team name.
+	 * 
+	 * @param teamname
+	 * @return a team object or null
+	 * @since 0.8.0
+	 */
+	@Override
+	public TeamModel getTeamModel(String teamname) {
+		read();
+		TeamModel model = teams.get(teamname.toLowerCase());
+		if (model != null) {
+			// clone the model, otherwise all changes to this object are
+			// live and unpersisted
+			model = DeepCopier.copy(model);
+		}
+		return model;
+	}
+
+	/**
+	 * Updates/writes a complete team object.
+	 * 
+	 * @param model
+	 * @return true if update is successful
+	 * @since 0.8.0
+	 */
+	@Override
+	public boolean updateTeamModel(TeamModel model) {
+		return updateTeamModel(model.name, model);
+	}
+
+	/**
+	 * Updates/writes all specified team objects.
+	 * 
+	 * @param models a list of team models
+	 * @return true if update is successful
+	 * @since 1.2.0
+	 */
+	@Override
+	public boolean updateTeamModels(Collection<TeamModel> models) {
+		try {
+			read();
+			for (TeamModel team : models) {
+				teams.put(team.name.toLowerCase(), team);
+			}
+			write();
+			return true;
+		} catch (Throwable t) {
+			logger.error(MessageFormat.format("Failed to update team {0} models!", models.size()), t);
+		}
+		return false;
+	}
+
+	/**
+	 * 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
+	 *            the team object to use for teamname
+	 * @return true if update is successful
+	 * @since 0.8.0
+	 */
+	@Override
+	public boolean updateTeamModel(String teamname, TeamModel model) {
+		TeamModel original = null;
+		try {
+			read();
+			original = teams.remove(teamname.toLowerCase());
+			teams.put(model.name.toLowerCase(), model);
+			write();
+			return true;
+		} catch (Throwable t) {
+			if (original != null) {
+				// restore original team
+				teams.put(original.name.toLowerCase(), original);
+			} else {
+				// drop attempted add
+				teams.remove(model.name.toLowerCase());
+			}
+			logger.error(MessageFormat.format("Failed to update team model {0}!", model.name), t);
+		}
+		return false;
+	}
+
+	/**
+	 * Deletes the team object from the user service.
+	 * 
+	 * @param model
+	 * @return true if successful
+	 * @since 0.8.0
+	 */
+	@Override
+	public boolean deleteTeamModel(TeamModel model) {
+		return deleteTeam(model.name);
+	}
+
+	/**
+	 * Delete the team object with the specified teamname
+	 * 
+	 * @param teamname
+	 * @return true if successful
+	 * @since 0.8.0
+	 */
+	@Override
+	public boolean deleteTeam(String teamname) {
+		try {
+			// Read realm file
+			read();
+			teams.remove(teamname.toLowerCase());
+			write();
+			return true;
+		} catch (Throwable t) {
+			logger.error(MessageFormat.format("Failed to delete team {0}!", teamname), t);
+		}
+		return false;
+	}
+
+	/**
+	 * Returns the list of all users available to the login service.
+	 * 
+	 * @return list of all usernames
+	 */
+	@Override
+	public List<String> getAllUsernames() {
+		read();
+		List<String> list = new ArrayList<String>(users.keySet());
+		Collections.sort(list);
+		return list;
+	}
+	
+	/**
+	 * Returns the list of all users available to the login service.
+	 * 
+	 * @return list of all usernames
+	 */
+	@Override
+	public List<UserModel> getAllUsers() {
+		read();
+		List<UserModel> list = new ArrayList<UserModel>(users.values());
+		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
+	 */
+	@Override
+	public List<String> getUsernamesForRepositoryRole(String role) {
+		List<String> list = new ArrayList<String>();
+		try {
+			read();
+			for (Map.Entry<String, UserModel> entry : users.entrySet()) {
+				UserModel model = entry.getValue();
+				if (model.hasRepositoryPermission(role)) {
+					list.add(model.username);
+				}
+			}
+		} catch (Throwable t) {
+			logger.error(MessageFormat.format("Failed to get usernames for role {0}!", role), t);
+		}
+		Collections.sort(list);
+		return list;
+	}
+
+	/**
+	 * 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
+	 * @return true if successful
+	 */
+	@Override
+	@Deprecated
+	public boolean setUsernamesForRepositoryRole(String role, List<String> usernames) {
+		try {
+			Set<String> specifiedUsers = new HashSet<String>();
+			for (String username : usernames) {
+				specifiedUsers.add(username.toLowerCase());
+			}
+
+			read();
+
+			// identify users which require add or remove role
+			for (UserModel user : users.values()) {
+				// user has role, check against revised user list
+				if (specifiedUsers.contains(user.username.toLowerCase())) {
+					user.addRepositoryPermission(role);
+				} else {
+					// remove role from user
+					user.removeRepositoryPermission(role);
+				}
+			}
+
+			// persist changes
+			write();
+			return true;
+		} catch (Throwable t) {
+			logger.error(MessageFormat.format("Failed to set usernames for role {0}!", role), t);
+		}
+		return false;
+	}
+
+	/**
+	 * Renames a repository role.
+	 * 
+	 * @param oldRole
+	 * @param newRole
+	 * @return true if successful
+	 */
+	@Override
+	public boolean renameRepositoryRole(String oldRole, String newRole) {
+		try {
+			read();
+			// identify users which require role rename
+			for (UserModel model : users.values()) {
+				if (model.hasRepositoryPermission(oldRole)) {
+					AccessPermission permission = model.removeRepositoryPermission(oldRole);
+					model.setRepositoryPermission(newRole, permission);
+				}
+			}
+
+			// identify teams which require role rename
+			for (TeamModel model : teams.values()) {
+				if (model.hasRepositoryPermission(oldRole)) {
+					AccessPermission permission = model.removeRepositoryPermission(oldRole);
+					model.setRepositoryPermission(newRole, permission);
+				}
+			}
+			// persist changes
+			write();
+			return true;
+		} catch (Throwable t) {
+			logger.error(
+					MessageFormat.format("Failed to rename role {0} to {1}!", oldRole, newRole), t);
+		}
+		return false;
+	}
+
+	/**
+	 * Removes a repository role from all users.
+	 * 
+	 * @param role
+	 * @return true if successful
+	 */
+	@Override
+	public boolean deleteRepositoryRole(String role) {
+		try {
+			read();
+
+			// identify users which require role rename
+			for (UserModel user : users.values()) {
+				user.removeRepositoryPermission(role);
+			}
+
+			// identify teams which require role rename
+			for (TeamModel team : teams.values()) {
+				team.removeRepositoryPermission(role);
+			}
+
+			// persist changes
+			write();
+			return true;
+		} catch (Throwable t) {
+			logger.error(MessageFormat.format("Failed to delete role {0}!", role), t);
+		}
+		return false;
+	}
+
+	/**
+	 * Writes the properties file.
+	 * 
+	 * @throws IOException
+	 */
+	private synchronized void write() throws IOException {
+		// Write a temporary copy of the users file
+		File realmFileCopy = new File(realmFile.getAbsolutePath() + ".tmp");
+
+		StoredConfig config = new FileBasedConfig(realmFileCopy, FS.detect());
+
+		// write users
+		for (UserModel model : users.values()) {
+			if (!StringUtils.isEmpty(model.password)) {
+				config.setString(USER, model.username, PASSWORD, model.password);
+			}
+			if (!StringUtils.isEmpty(model.cookie)) {
+				config.setString(USER, model.username, COOKIE, model.cookie);
+			}
+			if (!StringUtils.isEmpty(model.displayName)) {
+				config.setString(USER, model.username, DISPLAYNAME, model.displayName);
+			}
+			if (!StringUtils.isEmpty(model.emailAddress)) {
+				config.setString(USER, model.username, EMAILADDRESS, model.emailAddress);
+			}
+			if (!StringUtils.isEmpty(model.organizationalUnit)) {
+				config.setString(USER, model.username, ORGANIZATIONALUNIT, model.organizationalUnit);
+			}
+			if (!StringUtils.isEmpty(model.organization)) {
+				config.setString(USER, model.username, ORGANIZATION, model.organization);
+			}
+			if (!StringUtils.isEmpty(model.locality)) {
+				config.setString(USER, model.username, LOCALITY, model.locality);
+			}
+			if (!StringUtils.isEmpty(model.stateProvince)) {
+				config.setString(USER, model.username, STATEPROVINCE, model.stateProvince);
+			}
+			if (!StringUtils.isEmpty(model.countryCode)) {
+				config.setString(USER, model.username, COUNTRYCODE, model.countryCode);
+			}
+			if (model.getPreferences() != null) {
+				if (!StringUtils.isEmpty(model.getPreferences().locale)) {
+					config.setString(USER, model.username, LOCALE, model.getPreferences().locale);
+				}
+			}
+
+			// user roles
+			List<String> roles = new ArrayList<String>();
+			if (model.canAdmin) {
+				roles.add(Constants.ADMIN_ROLE);
+			}
+			if (model.canFork) {
+				roles.add(Constants.FORK_ROLE);
+			}
+			if (model.canCreate) {
+				roles.add(Constants.CREATE_ROLE);
+			}
+			if (model.excludeFromFederation) {
+				roles.add(Constants.NOT_FEDERATED_ROLE);
+			}
+			if (roles.size() == 0) {
+				// we do this to ensure that user record with no password
+				// is written.  otherwise, StoredConfig optimizes that account
+				// away. :(
+				roles.add(Constants.NO_ROLE);
+			}
+			config.setStringList(USER, model.username, ROLE, roles);
+
+			// discrete repository permissions
+			if (model.permissions != null && !model.canAdmin) {
+				List<String> permissions = new ArrayList<String>();
+				for (Map.Entry<String, AccessPermission> entry : model.permissions.entrySet()) {
+					if (entry.getValue().exceeds(AccessPermission.NONE)) {
+						permissions.add(entry.getValue().asRole(entry.getKey()));
+					}
+				}
+				config.setStringList(USER, model.username, REPOSITORY, permissions);
+			}
+			
+			// user preferences
+			if (model.getPreferences() != null) {
+				List<String> starred =  model.getPreferences().getStarredRepositories();
+				if (starred.size() > 0) {
+					config.setStringList(USER, model.username, STARRED, starred);
+				}
+			}
+		}
+
+		// write teams
+		for (TeamModel model : teams.values()) {
+			// team roles
+			List<String> roles = new ArrayList<String>();
+			if (model.canAdmin) {
+				roles.add(Constants.ADMIN_ROLE);
+			}
+			if (model.canFork) {
+				roles.add(Constants.FORK_ROLE);
+			}
+			if (model.canCreate) {
+				roles.add(Constants.CREATE_ROLE);
+			}
+			if (roles.size() == 0) {
+				// we do this to ensure that team record is written.
+				// Otherwise, StoredConfig might optimizes that record away.
+				roles.add(Constants.NO_ROLE);
+			}
+			config.setStringList(TEAM, model.name, ROLE, roles);
+			
+			if (!model.canAdmin) {
+				// write team permission for non-admin teams
+				if (model.permissions == null) {
+					// null check on "final" repositories because JSON-sourced TeamModel
+					// can have a null repositories object
+					if (!ArrayUtils.isEmpty(model.repositories)) {
+						config.setStringList(TEAM, model.name, REPOSITORY, new ArrayList<String>(
+								model.repositories));
+					}
+				} else {
+					// discrete repository permissions
+					List<String> permissions = new ArrayList<String>();
+					for (Map.Entry<String, AccessPermission> entry : model.permissions.entrySet()) {
+						if (entry.getValue().exceeds(AccessPermission.NONE)) {
+							// code:repository (e.g. RW+:~james/myrepo.git
+							permissions.add(entry.getValue().asRole(entry.getKey()));
+						}
+					}
+					config.setStringList(TEAM, model.name, REPOSITORY, permissions);
+				}
+			}
+
+			// null check on "final" users because JSON-sourced TeamModel
+			// can have a null users object
+			if (!ArrayUtils.isEmpty(model.users)) {
+				config.setStringList(TEAM, model.name, USER, new ArrayList<String>(model.users));
+			}
+
+			// null check on "final" mailing lists because JSON-sourced
+			// TeamModel can have a null users object
+			if (!ArrayUtils.isEmpty(model.mailingLists)) {
+				config.setStringList(TEAM, model.name, MAILINGLIST, new ArrayList<String>(
+						model.mailingLists));
+			}
+
+			// null check on "final" preReceiveScripts because JSON-sourced
+			// TeamModel can have a null preReceiveScripts object
+			if (!ArrayUtils.isEmpty(model.preReceiveScripts)) {
+				config.setStringList(TEAM, model.name, PRERECEIVE, model.preReceiveScripts);
+			}
+
+			// null check on "final" postReceiveScripts because JSON-sourced
+			// TeamModel can have a null postReceiveScripts object
+			if (!ArrayUtils.isEmpty(model.postReceiveScripts)) {
+				config.setStringList(TEAM, model.name, POSTRECEIVE, model.postReceiveScripts);
+			}
+		}
+
+		config.save();
+		// manually set the forceReload flag because not all JVMs support real
+		// millisecond resolution of lastModified. (issue-55)
+		forceReload = true;
+
+		// If the write is successful, delete the current file and rename
+		// the temporary copy to the original filename.
+		if (realmFileCopy.exists() && realmFileCopy.length() > 0) {
+			if (realmFile.exists()) {
+				if (!realmFile.delete()) {
+					throw new IOException(MessageFormat.format("Failed to delete {0}!",
+							realmFile.getAbsolutePath()));
+				}
+			}
+			if (!realmFileCopy.renameTo(realmFile)) {
+				throw new IOException(MessageFormat.format("Failed to rename {0} to {1}!",
+						realmFileCopy.getAbsolutePath(), realmFile.getAbsolutePath()));
+			}
+		} else {
+			throw new IOException(MessageFormat.format("Failed to save {0}!",
+					realmFileCopy.getAbsolutePath()));
+		}
+	}
+
+	/**
+	 * Reads the realm file and rebuilds the in-memory lookup tables.
+	 */
+	protected synchronized void read() {
+		if (realmFile.exists() && (forceReload || (realmFile.lastModified() != lastModified))) {
+			forceReload = false;
+			lastModified = realmFile.lastModified();
+			users.clear();
+			cookies.clear();
+			teams.clear();
+
+			try {
+				StoredConfig config = new FileBasedConfig(realmFile, FS.detect());
+				config.load();
+				Set<String> usernames = config.getSubsections(USER);
+				for (String username : usernames) {
+					UserModel user = new UserModel(username.toLowerCase());
+					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);
+					user.organization = config.getString(USER, username, ORGANIZATION);
+					user.locality = config.getString(USER, username, LOCALITY);
+					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);	
+					if (StringUtils.isEmpty(user.cookie) && !StringUtils.isEmpty(user.password)) {
+						user.cookie = StringUtils.getSHA1(user.username + user.password);
+					}
+
+					// user roles
+					Set<String> roles = new HashSet<String>(Arrays.asList(config.getStringList(
+							USER, username, ROLE)));
+					user.canAdmin = roles.contains(Constants.ADMIN_ROLE);
+					user.canFork = roles.contains(Constants.FORK_ROLE);
+					user.canCreate = roles.contains(Constants.CREATE_ROLE);
+					user.excludeFromFederation = roles.contains(Constants.NOT_FEDERATED_ROLE);
+
+					// repository memberships
+					if (!user.canAdmin) {
+						// non-admin, read permissions
+						Set<String> repositories = new HashSet<String>(Arrays.asList(config
+								.getStringList(USER, username, REPOSITORY)));
+						for (String repository : repositories) {
+							user.addRepositoryPermission(repository);
+						}
+					}
+
+					// starred repositories
+					Set<String> starred = new HashSet<String>(Arrays.asList(config
+							.getStringList(USER, username, STARRED)));
+					for (String repository : starred) {
+						UserRepositoryPreferences prefs = user.getPreferences().getRepositoryPreferences(repository);
+						prefs.starred = true;
+					}
+
+					// update cache
+					users.put(user.username, user);
+					if (!StringUtils.isEmpty(user.cookie)) {
+						cookies.put(user.cookie, user);
+					}
+				}
+
+				// load the teams
+				Set<String> teamnames = config.getSubsections(TEAM);
+				for (String teamname : teamnames) {
+					TeamModel team = new TeamModel(teamname);
+					Set<String> roles = new HashSet<String>(Arrays.asList(config.getStringList(
+							TEAM, teamname, ROLE)));
+					team.canAdmin = roles.contains(Constants.ADMIN_ROLE);
+					team.canFork = roles.contains(Constants.FORK_ROLE);
+					team.canCreate = roles.contains(Constants.CREATE_ROLE);
+					
+					if (!team.canAdmin) {
+						// non-admin team, read permissions
+						team.addRepositoryPermissions(Arrays.asList(config.getStringList(TEAM, teamname,
+								REPOSITORY)));
+					}
+					team.addUsers(Arrays.asList(config.getStringList(TEAM, teamname, USER)));
+					team.addMailingLists(Arrays.asList(config.getStringList(TEAM, teamname,
+							MAILINGLIST)));
+					team.preReceiveScripts.addAll(Arrays.asList(config.getStringList(TEAM,
+							teamname, PRERECEIVE)));
+					team.postReceiveScripts.addAll(Arrays.asList(config.getStringList(TEAM,
+							teamname, POSTRECEIVE)));
+
+					teams.put(team.name.toLowerCase(), team);
+
+					// set the teams on the users
+					for (String user : team.users) {
+						UserModel model = users.get(user);
+						if (model != null) {
+							model.teams.add(team);
+						}
+					}
+				}
+			} catch (Exception e) {
+				logger.error(MessageFormat.format("Failed to read {0}", realmFile), e);
+			}
+		}
+	}
+
+	protected long lastModified() {
+		return lastModified;
+	}
+
+	@Override
+	public String toString() {
+		return getClass().getSimpleName() + "(" + realmFile.getAbsolutePath() + ")";
+	}
+}
diff --git a/src/main/java/com/gitblit/Constants.java b/src/main/java/com/gitblit/Constants.java
new file mode 100644
index 0000000..aa3767c
--- /dev/null
+++ b/src/main/java/com/gitblit/Constants.java
@@ -0,0 +1,494 @@
+/*
+ * Copyright 2011 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.net.URL;
+import java.util.jar.Attributes;
+import java.util.jar.Manifest;
+
+/**
+ * Constant values used by Gitblit.
+ * 
+ * @author James Moger
+ * 
+ */
+public class Constants {
+
+	public static final String NAME = "Gitblit";
+
+	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 GIT_PATH = "/git/";
+
+	public static final String ZIP_PATH = "/zip/";
+
+	public static final String SYNDICATION_PATH = "/feed/";
+
+	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 BORDER = "***********************************************************";
+
+	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() {
+		String v = Constants.class.getPackage().getImplementationVersion();
+		if (v == null) {
+			return "0.0.0-SNAPSHOT";
+		}
+		return v;
+	}
+
+	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";
+		String classPath = clazz.getResource(className).toString();
+		if (!classPath.startsWith("jar")) {
+			// Class not from JAR
+			return defaultValue;
+		}
+		try {
+			String manifestPath = classPath.substring(0, classPath.lastIndexOf("!") + 1) + "/META-INF/MANIFEST.MF";
+			Manifest manifest = new Manifest(new URL(manifestPath).openStream());
+			Attributes attr = manifest.getMainAttributes();
+			String value = attr.getValue(attrib);
+			return value;
+		} catch (Exception e) {
+		}
+		return defaultValue;
+	}
+	
+	/**
+	 * Enumeration representing the four access restriction levels.
+	 */
+	public static enum AccessRestrictionType {
+		NONE, PUSH, CLONE, VIEW;
+
+		public static AccessRestrictionType fromName(String name) {
+			for (AccessRestrictionType type : values()) {
+				if (type.name().equalsIgnoreCase(name)) {
+					return type;
+				}
+			}
+			return NONE;
+		}
+
+		public boolean exceeds(AccessRestrictionType type) {
+			return this.ordinal() > type.ordinal();
+		}
+
+		public boolean atLeast(AccessRestrictionType type) {
+			return this.ordinal() >= type.ordinal();
+		}
+
+		public String toString() {
+			return name();
+		}
+		
+		public boolean isValidPermission(AccessPermission permission) {
+			switch (this) {
+			case VIEW:
+				// VIEW restriction
+				// all access permissions are valid
+				return true;
+			case CLONE:
+				// CLONE restriction
+				// only CLONE or greater access permissions are valid
+				return permission.atLeast(AccessPermission.CLONE);
+			case PUSH:
+				// PUSH restriction
+				// only PUSH or greater access permissions are valid 
+				return permission.atLeast(AccessPermission.PUSH);
+			case NONE:
+				// NO access restriction
+				// all access permissions are invalid
+				return false;
+			}
+			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)) {
+					return type;
+				}
+			}
+			return NAMED;
+		}
+		
+		public String toString() {
+			return name();
+		}
+	}
+
+
+	/**
+	 * Enumeration representing the types of federation tokens.
+	 */
+	public static enum FederationToken {
+		ALL, USERS_AND_REPOSITORIES, REPOSITORIES;
+
+		public static FederationToken fromName(String name) {
+			for (FederationToken type : values()) {
+				if (type.name().equalsIgnoreCase(name)) {
+					return type;
+				}
+			}
+			return REPOSITORIES;
+		}
+
+		public String toString() {
+			return name();
+		}
+	}
+
+	/**
+	 * Enumeration representing the types of federation requests.
+	 */
+	public static enum FederationRequest {
+		POKE, PROPOSAL, PULL_REPOSITORIES, PULL_USERS, PULL_TEAMS, PULL_SETTINGS, PULL_SCRIPTS, STATUS;
+
+		public static FederationRequest fromName(String name) {
+			for (FederationRequest type : values()) {
+				if (type.name().equalsIgnoreCase(name)) {
+					return type;
+				}
+			}
+			return PULL_REPOSITORIES;
+		}
+
+		public String toString() {
+			return name();
+		}
+	}
+
+	/**
+	 * Enumeration representing the statii of federation requests.
+	 */
+	public static enum FederationPullStatus {
+		PENDING, FAILED, SKIPPED, PULLED, MIRRORED, NOCHANGE, EXCLUDED;
+
+		public static FederationPullStatus fromName(String name) {
+			for (FederationPullStatus type : values()) {
+				if (type.name().equalsIgnoreCase(name)) {
+					return type;
+				}
+			}
+			return PENDING;
+		}
+
+		@Override
+		public String toString() {
+			return name();
+		}
+	}
+
+	/**
+	 * Enumeration representing the federation types.
+	 */
+	public static enum FederationStrategy {
+		EXCLUDE, FEDERATE_THIS, FEDERATE_ORIGIN;
+
+		public static FederationStrategy fromName(String name) {
+			for (FederationStrategy type : values()) {
+				if (type.name().equalsIgnoreCase(name)) {
+					return type;
+				}
+			}
+			return FEDERATE_THIS;
+		}
+
+		public boolean exceeds(FederationStrategy type) {
+			return this.ordinal() > type.ordinal();
+		}
+
+		public boolean atLeast(FederationStrategy type) {
+			return this.ordinal() >= type.ordinal();
+		}
+
+		@Override
+		public String toString() {
+			return name();
+		}
+	}
+
+	/**
+	 * Enumeration representing the possible results of federation proposal
+	 * requests.
+	 */
+	public static enum FederationProposalResult {
+		ERROR, FEDERATION_DISABLED, MISSING_DATA, NO_PROPOSALS, NO_POKE, ACCEPTED;
+
+		@Override
+		public String toString() {
+			return name();
+		}
+	}
+
+	/**
+	 * Enumeration representing the possible remote procedure call requests from
+	 * a client.
+	 */
+	public static enum RpcRequest {
+		// Order is important here.  anything above LIST_SETTINGS requires
+		// administrator privileges and web.allowRpcManagement.
+		CLEAR_REPOSITORY_CACHE, GET_PROTOCOL, LIST_REPOSITORIES, LIST_BRANCHES, LIST_SETTINGS,
+		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_FEDERATION_REGISTRATIONS, LIST_FEDERATION_RESULTS, LIST_FEDERATION_PROPOSALS, LIST_FEDERATION_SETS,
+		EDIT_SETTINGS, LIST_STATUS;
+
+		public static RpcRequest fromName(String name) {
+			for (RpcRequest type : values()) {
+				if (type.name().equalsIgnoreCase(name)) {
+					return type;
+				}
+			}
+			return null;
+		}		
+
+		public boolean exceeds(RpcRequest type) {
+			return this.ordinal() > type.ordinal();
+		}
+
+		@Override
+		public String toString() {
+			return name();
+		}
+	}
+
+	/**
+	 * Enumeration of the search types.
+	 */
+	public static enum SearchType {
+		AUTHOR, COMMITTER, COMMIT;
+	
+		public static SearchType forName(String name) {
+			for (SearchType type : values()) {
+				if (type.name().equalsIgnoreCase(name)) {
+					return type;
+				}
+			}
+			return COMMIT;
+		}
+	
+		@Override
+		public String toString() {
+			return name().toLowerCase();
+		}
+	}
+	
+	/**
+	 * The types of objects that can be indexed and queried.
+	 */
+	public static enum SearchObjectType {
+		commit, blob, issue;
+
+		static SearchObjectType fromName(String name) {
+			for (SearchObjectType value : values()) {
+				if (value.name().equals(name)) {
+					return value;
+				}
+			}
+			return null;
+		}
+	}
+	
+	/**
+	 * 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;
+		}
+
+		public boolean atMost(AccessPermission perm) {
+			return ordinal() <= perm.ordinal();
+		}
+
+		public boolean atLeast(AccessPermission perm) {
+			return ordinal() >= perm.ordinal();
+		}
+
+		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) {
+				// legacy/undefined assume full permissions
+				return AccessPermission.LEGACY;
+			} else {
+				// code:repository
+				return AccessPermission.fromCode(fields[0]);
+			}
+		}
+		
+		public static String repositoryFromRole(String role) {
+			String [] fields = role.split(":", 2);
+			if (fields.length == 1) {
+				// legacy/undefined assume full permissions
+				return role;
+			} else {
+				// code:repository
+				return fields[1];
+			}
+		}
+		
+		public static AccessPermission fromCode(String code) {
+			for (AccessPermission perm : values()) {
+				if (perm.code.equalsIgnoreCase(code)) {
+					return perm;
+				}
+			}
+			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();
+		}
+	}
+
+	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;
+		
+		public boolean isLocal() {
+			return this == LOCAL;
+		}
+	}
+
+	@Documented
+	@Retention(RetentionPolicy.RUNTIME)
+	public @interface Unused {
+	}
+}
diff --git a/src/com/gitblit/DownloadZipFilter.java b/src/main/java/com/gitblit/DownloadZipFilter.java
similarity index 100%
rename from src/com/gitblit/DownloadZipFilter.java
rename to src/main/java/com/gitblit/DownloadZipFilter.java
diff --git a/src/main/java/com/gitblit/DownloadZipServlet.java b/src/main/java/com/gitblit/DownloadZipServlet.java
new file mode 100644
index 0000000..8a4a710
--- /dev/null
+++ b/src/main/java/com/gitblit/DownloadZipServlet.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright 2011 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit;
+
+import java.io.IOException;
+import java.net.SocketException;
+import java.text.MessageFormat;
+import java.text.ParseException;
+import java.util.Date;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.gitblit.utils.CompressionUtils;
+import com.gitblit.utils.JGitUtils;
+import com.gitblit.utils.MarkdownUtils;
+import com.gitblit.utils.StringUtils;
+
+/**
+ * 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)) {
+					return format;
+				}
+			}
+			return zip;
+		}
+	}
+
+	public DownloadZipServlet() {
+		super();
+	}
+
+	/**
+	 * Returns an url to this servlet for the specified parameters.
+	 * 
+	 * @param baseURL
+	 * @param repository
+	 * @param objectId
+	 * @param path
+	 * @param format
+	 * @return an url
+	 */
+	public static String asLink(String baseURL, String repository, String objectId, String path, Format format) {
+		if (baseURL.length() > 0 && baseURL.charAt(baseURL.length() - 1) == '/') {
+			baseURL = baseURL.substring(0, baseURL.length() - 1);
+		}
+		return baseURL + Constants.ZIP_PATH + "?r=" + repository
+				+ (path == null ? "" : ("&p=" + path))
+				+ (objectId == null ? "" : ("&h=" + objectId))
+				+ (format == null ? "" : ("&format=" + format.name()));
+	}
+
+	/**
+	 * Creates a zip stream from the repository of the requested data.
+	 * 
+	 * @param request
+	 * @param response
+	 * @throws javax.servlet.ServletException
+	 * @throws java.io.IOException
+	 */
+	private void processRequest(javax.servlet.http.HttpServletRequest request,
+			javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException,
+			java.io.IOException {
+		if (!GitBlit.getBoolean(Keys.web.allowZipDownloads, true)) {
+			logger.warn("Zip downloads are disabled");
+			response.sendError(HttpServletResponse.SC_FORBIDDEN);
+			return;
+		}
+		
+		Format format = Format.zip;
+		String repository = request.getParameter("r");
+		String basePath = request.getParameter("p");
+		String objectId = request.getParameter("h");
+		String f = request.getParameter("format");
+		if (!StringUtils.isEmpty(f)) {
+			format = Format.fromName(f);
+		}
+		
+		try {
+			String name = repository;
+			if (name.indexOf('/') > -1) {
+				name = name.substring(name.lastIndexOf('/') + 1);
+			}
+			name = StringUtils.stripDotGit(name);
+
+			if (!StringUtils.isEmpty(basePath)) {
+				name += "-" + basePath.replace('/', '_');
+			}
+			if (!StringUtils.isEmpty(objectId)) {
+				name += "-" + objectId;
+			}
+						
+			Repository r = GitBlit.self().getRepository(repository);
+			if (r == null) {
+				if (GitBlit.self().isCollectingGarbage(repository)) {
+					error(response, MessageFormat.format("# Error\nGitblit is busy collecting garbage in {0}", repository));
+					return;
+				} else {
+					error(response, MessageFormat.format("# Error\nFailed to find repository {0}", repository));
+					return;
+				}
+			}
+			RevCommit commit = JGitUtils.getCommit(r, objectId);
+			if (commit == null) {
+				error(response, MessageFormat.format("# Error\nFailed to find commit {0}", objectId));
+				r.close();
+				return;
+			}
+			Date date = JGitUtils.getCommitDate(commit);
+
+			String contentType = "application/octet-stream";
+			response.setContentType(contentType + "; charset=" + response.getCharacterEncoding());
+			response.setHeader("Content-Disposition", "attachment; filename=\"" + name + format.extension + "\"");
+			response.setDateHeader("Last-Modified", date.getTime());
+			response.setHeader("Cache-Control", "no-cache");
+			response.setHeader("Pragma", "no-cache");
+			response.setDateHeader("Expires", 0);
+
+			try {
+				switch (format) {
+				case zip:
+					CompressionUtils.zip(r, basePath, objectId, response.getOutputStream());
+					break;
+				case tar:
+					CompressionUtils.tar(r, basePath, objectId, response.getOutputStream());
+					break;
+				case gz:
+					CompressionUtils.gz(r, basePath, objectId, response.getOutputStream());
+					break;
+				case xz:
+					CompressionUtils.xz(r, basePath, objectId, response.getOutputStream());
+					break;
+				case bzip2:
+					CompressionUtils.bzip2(r, basePath, objectId, response.getOutputStream());
+					break;
+				}
+				
+				response.flushBuffer();
+			} catch (SocketException 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);	
+				}
+			} catch (Throwable t) {
+				logger.error("Failed to write attachment to client", t);
+			}
+
+			// close the repository
+			r.close();
+		} catch (Throwable t) {
+			logger.error("Failed to write attachment to client", t);
+		}
+	}
+
+	private void error(HttpServletResponse response, String mkd) throws ServletException,
+			IOException, ParseException {
+		String content = MarkdownUtils.transformMarkdown(mkd);
+		response.setContentType("text/html; charset=" + Constants.ENCODING);
+		response.getWriter().write(content);
+	}
+
+	@Override
+	protected void doPost(javax.servlet.http.HttpServletRequest request,
+			javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException,
+			java.io.IOException {
+		processRequest(request, response);
+	}
+
+	@Override
+	protected void doGet(javax.servlet.http.HttpServletRequest request,
+			javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException,
+			java.io.IOException {
+		processRequest(request, response);
+	}
+}
diff --git a/src/main/java/com/gitblit/EnforceAuthenticationFilter.java b/src/main/java/com/gitblit/EnforceAuthenticationFilter.java
new file mode 100644
index 0000000..2a17996
--- /dev/null
+++ b/src/main/java/com/gitblit/EnforceAuthenticationFilter.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2013 Laurens Vrijnsen
+ * Copyright 2013 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */package com.gitblit;
+
+import java.io.IOException;
+import java.text.MessageFormat;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.gitblit.models.UserModel;
+
+/**
+ * This filter enforces authentication via HTTP Basic Authentication, if the settings indicate so.
+ * It looks at the settings "web.authenticateViewPages" and "web.enforceHttpBasicAuthentication"; if
+ * both are true, any unauthorized access will be met with a HTTP Basic Authentication header.
+ *
+ * @author Laurens Vrijnsen
+ *
+ */
+public class EnforceAuthenticationFilter implements Filter {
+	
+	protected transient Logger logger = LoggerFactory.getLogger(getClass());
+
+	/* 
+	 * @see javax.servlet.Filter#init(javax.servlet.FilterConfig)
+	 */
+	@Override
+	public void init(FilterConfig filterConfig) throws ServletException {
+		// 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; 
+		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()));
+			@SuppressWarnings("static-access")
+			String CHALLENGE = MessageFormat.format("Basic realm=\"{0}\"", GitBlit.self().getString("web.siteName",""));
+			HttpResponse.setHeader("WWW-Authenticate", CHALLENGE);
+			HttpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED);
+			return;
+
+		} else {
+			// user is authenticated, or don't care, continue handling
+			chain.doFilter( request, response );
+			
+		} // authenticated
+	} // doFilter
+
+	
+	/* 
+	 * @see javax.servlet.Filter#destroy()
+	 */
+	@Override
+	public void destroy() {
+		// Nothing to be done
+
+	} // destroy
+
+}
diff --git a/src/main/java/com/gitblit/FederationClient.java b/src/main/java/com/gitblit/FederationClient.java
new file mode 100644
index 0000000..d34aadb
--- /dev/null
+++ b/src/main/java/com/gitblit/FederationClient.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright 2011 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+import com.beust.jcommander.JCommander;
+import com.beust.jcommander.Parameter;
+import com.beust.jcommander.ParameterException;
+import com.beust.jcommander.Parameters;
+import com.gitblit.models.FederationModel;
+import com.gitblit.utils.FederationUtils;
+import com.gitblit.utils.StringUtils;
+
+/**
+ * Command-line client to pull federated Gitblit repositories.
+ * 
+ * @author James Moger
+ * 
+ */
+public class FederationClient {
+
+	public static void main(String[] args) {
+		Params params = new Params();
+		JCommander jc = new JCommander(params);
+		try {
+			jc.parse(args);
+		} catch (ParameterException t) {
+			usage(jc, t);
+		}
+
+		System.out.println("Gitblit Federation Client v" + Constants.getVersion() + " (" + Constants.getBuildDate() + ")");
+
+		// command-line specified base folder
+		File baseFolder = new File(System.getProperty("user.dir"));
+		if (!StringUtils.isEmpty(params.baseFolder)) {
+			baseFolder = new File(params.baseFolder);
+		}
+
+		File regFile = com.gitblit.utils.FileUtils.resolveParameter(Constants.baseFolder$, baseFolder, params.registrationsFile);
+		IStoredSettings settings = new FileSettings(regFile.getAbsolutePath());
+		List<FederationModel> registrations = new ArrayList<FederationModel>();
+		if (StringUtils.isEmpty(params.url)) {
+			registrations.addAll(FederationUtils.getFederationRegistrations(settings));
+		} else {
+			if (StringUtils.isEmpty(params.token)) {
+				System.out.println("Must specify --token parameter!");
+				System.exit(0);
+			}
+			FederationModel model = new FederationModel("Gitblit");
+			model.url = params.url;
+			model.token = params.token;
+			model.mirror = params.mirror;
+			model.bare = params.bare;
+			model.frequency = params.frequency;
+			model.folder = "";
+			registrations.add(model);
+		}
+		if (registrations.size() == 0) {
+			System.out.println("No Federation Registrations!  Nothing to do.");
+			System.exit(0);
+		}
+		
+		// command-line specified repositories folder
+		if (!StringUtils.isEmpty(params.repositoriesFolder)) {
+			settings.overrideSetting(Keys.git.repositoriesFolder, new File(
+					params.repositoriesFolder).getAbsolutePath());
+		}
+
+		// configure the Gitblit singleton for minimal, non-server operation
+		GitBlit.self().configureContext(settings, baseFolder, false);
+		FederationPullExecutor executor = new FederationPullExecutor(registrations, params.isDaemon);
+		executor.run();
+		if (!params.isDaemon) {
+			System.out.println("Finished.");
+			System.exit(0);
+		}
+	}
+
+	private static void usage(JCommander jc, ParameterException t) {
+		System.out.println(Constants.getGitBlitVersion());
+		System.out.println();
+		if (t != null) {
+			System.out.println(t.getMessage());
+			System.out.println();
+		}
+
+		if (jc != null) {
+			jc.usage();
+		}
+		System.exit(0);
+	}
+
+	/**
+	 * JCommander Parameters class for FederationClient.
+	 */
+	@Parameters(separators = " ")
+	private static class Params {
+
+		@Parameter(names = { "--registrations" }, description = "Gitblit Federation Registrations File", required = false)
+		public String registrationsFile = "${baseFolder}/federation.properties";
+
+		@Parameter(names = { "--daemon" }, description = "Runs in daemon mode to schedule and pull repositories", required = false)
+		public boolean isDaemon;
+
+		@Parameter(names = { "--url" }, description = "URL of Gitblit instance to mirror from", required = false)
+		public String url;
+
+		@Parameter(names = { "--mirror" }, description = "Mirror repositories", required = false)
+		public boolean mirror;
+
+		@Parameter(names = { "--bare" }, description = "Create bare repositories", required = false)
+		public boolean bare;
+
+		@Parameter(names = { "--token" }, description = "Federation Token", required = false)
+		public String token;
+
+		@Parameter(names = { "--frequency" }, description = "Period to wait between pull attempts (requires --daemon)", required = false)
+		public String frequency = "60 mins";
+
+		@Parameter(names = { "--baseFolder" }, description = "Base folder for received data", required = false)
+		public String baseFolder;
+
+		@Parameter(names = { "--repositoriesFolder" }, description = "Destination folder for cloned repositories", required = false)
+		public String repositoriesFolder;
+
+	}
+}
diff --git a/src/main/java/com/gitblit/FederationPullExecutor.java b/src/main/java/com/gitblit/FederationPullExecutor.java
new file mode 100644
index 0000000..25cd32a
--- /dev/null
+++ b/src/main/java/com/gitblit/FederationPullExecutor.java
@@ -0,0 +1,523 @@
+/*
+ * Copyright 2011 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit;
+
+import static org.eclipse.jgit.lib.Constants.DOT_GIT_EXT;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.net.InetAddress;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.StoredConfig;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.transport.CredentialsProvider;
+import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.gitblit.Constants.AccessPermission;
+import com.gitblit.Constants.FederationPullStatus;
+import com.gitblit.Constants.FederationStrategy;
+import com.gitblit.GitBlitException.ForbiddenException;
+import com.gitblit.models.FederationModel;
+import com.gitblit.models.RefModel;
+import com.gitblit.models.RepositoryModel;
+import com.gitblit.models.TeamModel;
+import com.gitblit.models.UserModel;
+import com.gitblit.utils.ArrayUtils;
+import com.gitblit.utils.FederationUtils;
+import com.gitblit.utils.FileUtils;
+import com.gitblit.utils.JGitUtils;
+import com.gitblit.utils.JGitUtils.CloneResult;
+import com.gitblit.utils.StringUtils;
+import com.gitblit.utils.TimeUtils;
+
+/**
+ * FederationPullExecutor pulls repository updates and, optionally, user
+ * accounts and server settings from registered Gitblit instances.
+ */
+public class FederationPullExecutor implements Runnable {
+
+	private final Logger logger = LoggerFactory.getLogger(FederationPullExecutor.class);
+
+	private final List<FederationModel> registrations;
+
+	private final boolean isDaemon;
+
+	/**
+	 * Constructor for specifying a single federation registration. This
+	 * constructor is used to schedule the next pull execution.
+	 * 
+	 * @param registration
+	 */
+	private FederationPullExecutor(FederationModel registration) {
+		this(Arrays.asList(registration), true);
+	}
+
+	/**
+	 * 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
+	 *            false, the federation pull operation is executed once.
+	 */
+	public FederationPullExecutor(List<FederationModel> registrations, boolean isDaemon) {
+		this.registrations = registrations;
+		this.isDaemon = isDaemon;
+	}
+
+	/**
+	 * Run method for this pull executor.
+	 */
+	@Override
+	public void run() {
+		for (FederationModel registration : registrations) {
+			FederationPullStatus was = registration.getLowestStatus();
+			try {
+				Date now = new Date(System.currentTimeMillis());
+				pull(registration);
+				sendStatusAcknowledgment(registration);
+				registration.lastPull = now;
+				FederationPullStatus is = registration.getLowestStatus();
+				if (is.ordinal() < was.ordinal()) {
+					// the status for this registration has downgraded
+					logger.warn("Federation pull status of {0} is now {1}", registration.name,
+							is.name());
+					if (registration.notifyOnError) {
+						String message = "Federation pull of " + registration.name + " @ "
+								+ registration.url + " is now at " + is.name();
+						GitBlit.self()
+								.sendMailToAdministrators(
+										"Pull Status of " + registration.name + " is " + is.name(),
+										message);
+					}
+				}
+			} catch (Throwable t) {
+				logger.error(MessageFormat.format(
+						"Failed to pull from federated gitblit ({0} @ {1})", registration.name,
+						registration.url), t);
+			} finally {
+				if (isDaemon) {
+					schedule(registration);
+				}
+			}
+		}
+	}
+
+	/**
+	 * Mirrors a repository and, optionally, the server's users, and/or
+	 * configuration settings from a origin Gitblit instance.
+	 * 
+	 * @param registration
+	 * @throws Exception
+	 */
+	private void pull(FederationModel registration) throws Exception {
+		Map<String, RepositoryModel> repositories = FederationUtils.getRepositories(registration,
+				true);
+		String registrationFolder = registration.folder.toLowerCase().trim();
+		// confirm valid characters in server alias
+		Character c = StringUtils.findInvalidCharacter(registrationFolder);
+		if (c != null) {
+			logger.error(MessageFormat
+					.format("Illegal character ''{0}'' in folder name ''{1}'' of federation registration {2}!",
+							c, registrationFolder, registration.name));
+			return;
+		}
+		File repositoriesFolder = GitBlit.getRepositoriesFolder();
+		File registrationFolderFile = new File(repositoriesFolder, registrationFolder);
+		registrationFolderFile.mkdirs();
+
+		// Clone/Pull the repository
+		for (Map.Entry<String, RepositoryModel> entry : repositories.entrySet()) {
+			String cloneUrl = entry.getKey();
+			RepositoryModel repository = entry.getValue();
+			if (!repository.hasCommits) {
+				logger.warn(MessageFormat.format(
+						"Skipping federated repository {0} from {1} @ {2}. Repository is EMPTY.",
+						repository.name, registration.name, registration.url));
+				registration.updateStatus(repository, FederationPullStatus.SKIPPED);
+				continue;
+			}
+
+			// Determine local repository name
+			String repositoryName;
+			if (StringUtils.isEmpty(registrationFolder)) {
+				repositoryName = repository.name;
+			} else {
+				repositoryName = registrationFolder + "/" + repository.name;
+			}
+
+			if (registration.bare) {
+				// bare repository, ensure .git suffix
+				if (!repositoryName.toLowerCase().endsWith(DOT_GIT_EXT)) {
+					repositoryName += DOT_GIT_EXT;
+				}
+			} else {
+				// normal repository, strip .git suffix
+				if (repositoryName.toLowerCase().endsWith(DOT_GIT_EXT)) {
+					repositoryName = repositoryName.substring(0,
+							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;
+			}
+
+			if (existingRepository != null) {
+				StoredConfig config = existingRepository.getConfig();
+				config.load();
+				String origin = config.getString("remote", "origin", "url");
+				RevCommit commit = JGitUtils.getCommit(existingRepository,
+						org.eclipse.jgit.lib.Constants.FETCH_HEAD);
+				if (commit != null) {
+					fetchHead = commit.getName();
+				}
+				existingRepository.close();
+				if (!origin.startsWith(registration.url)) {
+					logger.warn(MessageFormat
+							.format("Skipping federated repository {0} from {1} @ {2}. Origin does not match, consider EXCLUDING.",
+									repository.name, registration.name, registration.url));
+					registration.updateStatus(repository, FederationPullStatus.SKIPPED);
+					continue;
+				}
+			}
+
+			// clone/pull this repository
+			CredentialsProvider credentials = new UsernamePasswordCredentialsProvider(
+					Constants.FEDERATION_USER, registration.token);
+			logger.info(MessageFormat.format("Pulling federated repository {0} from {1} @ {2}",
+					repository.name, registration.name, registration.url));
+
+			CloneResult result = JGitUtils.cloneRepository(registrationFolderFile, repository.name,
+					cloneUrl, registration.bare, credentials);
+			Repository r = GitBlit.self().getRepository(repositoryName);
+			RepositoryModel rm = GitBlit.self().getRepositoryModel(repositoryName);
+			repository.isFrozen = registration.mirror;
+			if (result.createdRepository) {
+				// default local settings
+				repository.federationStrategy = FederationStrategy.EXCLUDE;
+				repository.isFrozen = registration.mirror;
+				repository.showRemoteBranches = !registration.mirror;
+				logger.info(MessageFormat.format("     cloning {0}", repository.name));
+				registration.updateStatus(repository, FederationPullStatus.MIRRORED);
+			} else {
+				// fetch and update
+				boolean fetched = false;
+				RevCommit commit = JGitUtils.getCommit(r, org.eclipse.jgit.lib.Constants.FETCH_HEAD);
+				String newFetchHead = commit.getName();
+				fetched = fetchHead == null || !fetchHead.equals(newFetchHead);
+
+				if (registration.mirror) {
+					// mirror
+					if (fetched) {
+						// update local branches to match the remote tracking branches
+						for (RefModel ref : JGitUtils.getRemoteBranches(r, false, -1)) {
+							if (ref.displayName.startsWith("origin/")) {
+								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;
+						} else {
+							newHead = repository.HEAD;
+						}
+						JGitUtils.setHEADtoRef(r, newHead);
+						logger.info(MessageFormat.format("     resetting HEAD of {0} to {1}",
+								repository.name, newHead));
+						registration.updateStatus(repository, FederationPullStatus.MIRRORED);
+					} else {
+						// indicate no commits pulled
+						registration.updateStatus(repository, FederationPullStatus.NOCHANGE);
+					}
+				} else {
+					// non-mirror
+					if (fetched) {
+						// indicate commits pulled to origin/master
+						registration.updateStatus(repository, FederationPullStatus.PULLED);
+					} else {
+						// indicate no commits pulled
+						registration.updateStatus(repository, FederationPullStatus.NOCHANGE);
+					}
+				}
+
+				// preserve local settings
+				repository.isFrozen = rm.isFrozen;
+				repository.federationStrategy = rm.federationStrategy;
+
+				// merge federation sets
+				Set<String> federationSets = new HashSet<String>();
+				if (rm.federationSets != null) {
+					federationSets.addAll(rm.federationSets);
+				}
+				if (repository.federationSets != null) {
+					federationSets.addAll(repository.federationSets);
+				}
+				repository.federationSets = new ArrayList<String>(federationSets);
+				
+				// merge indexed branches
+				Set<String> indexedBranches = new HashSet<String>();
+				if (rm.indexedBranches != null) {
+					indexedBranches.addAll(rm.indexedBranches);
+				}
+				if (repository.indexedBranches != null) {
+					indexedBranches.addAll(repository.indexedBranches);
+				}
+				repository.indexedBranches = new ArrayList<String>(indexedBranches);
+
+			}
+			// only repositories that are actually _cloned_ from the origin
+			// Gitblit repository are marked as federated. If the origin
+			// is from somewhere else, these repositories are not considered
+			// "federated" repositories.
+			repository.isFederated = cloneUrl.startsWith(registration.url);
+
+			GitBlit.self().updateConfiguration(r, repository);
+			r.close();
+		}
+
+		IUserService userService = null;
+
+		try {
+			// Pull USERS
+			// TeamModels are automatically pulled because they are contained
+			// within the UserModel. The UserService creates unknown teams
+			// and updates existing teams.
+			Collection<UserModel> users = FederationUtils.getUsers(registration);
+			if (users != null && users.size() > 0) {
+				File realmFile = new File(registrationFolderFile, registration.name + "_users.conf");
+				realmFile.delete();
+				userService = new ConfigUserService(realmFile);
+				for (UserModel user : users) {
+					userService.updateUserModel(user.username, user);
+
+					// merge the origin permissions and origin accounts into
+					// the user accounts of this Gitblit instance
+					if (registration.mergeAccounts) {
+						// reparent all repository permissions if the local
+						// repositories are stored within subfolders
+						if (!StringUtils.isEmpty(registrationFolder)) {
+							if (user.permissions != null) {
+								// pulling from >= 1.2 version
+								Map<String, AccessPermission> copy = new HashMap<String, AccessPermission>(user.permissions);
+								user.permissions.clear();
+								for (Map.Entry<String, AccessPermission> entry : copy.entrySet()) {
+									user.setRepositoryPermission(registrationFolder + "/" + entry.getKey(), entry.getValue());
+								}
+							} else {
+								// pulling from <= 1.1 version
+								List<String> permissions = new ArrayList<String>(user.repositories);
+								user.repositories.clear();
+								for (String permission : permissions) {
+									user.addRepositoryPermission(registrationFolder + "/" + permission);
+								}
+							}
+						}
+
+						// insert new user or update local user
+						UserModel localUser = GitBlit.self().getUserModel(user.username);
+						if (localUser == null) {
+							// create new local user
+							GitBlit.self().updateUserModel(user.username, user, true);
+						} else {
+							// update repository permissions of local user
+							if (user.permissions != null) {
+								// pulling from >= 1.2 version
+								Map<String, AccessPermission> copy = new HashMap<String, AccessPermission>(user.permissions);
+								for (Map.Entry<String, AccessPermission> entry : copy.entrySet()) {
+									localUser.setRepositoryPermission(entry.getKey(), entry.getValue());
+								}
+							} else {
+								// pulling from <= 1.1 version
+								for (String repository : user.repositories) {
+									localUser.addRepositoryPermission(repository);
+								}
+							}
+							localUser.password = user.password;
+							localUser.canAdmin = user.canAdmin;
+							GitBlit.self().updateUserModel(localUser.username, localUser, false);
+						}
+
+						for (String teamname : GitBlit.self().getAllTeamnames()) {
+							TeamModel team = GitBlit.self().getTeamModel(teamname);
+							if (user.isTeamMember(teamname) && !team.hasUser(user.username)) {
+								// new team member
+								team.addUser(user.username);
+								GitBlit.self().updateTeamModel(teamname, team, false);
+							} else if (!user.isTeamMember(teamname) && team.hasUser(user.username)) {
+								// remove team member
+								team.removeUser(user.username);
+								GitBlit.self().updateTeamModel(teamname, team, false);
+							}
+
+							// update team repositories
+							TeamModel remoteTeam = user.getTeam(teamname);
+							if (remoteTeam != null) {
+								if (remoteTeam.permissions != null) {
+									// pulling from >= 1.2
+									for (Map.Entry<String, AccessPermission> entry : remoteTeam.permissions.entrySet()){
+										team.setRepositoryPermission(entry.getKey(), entry.getValue());
+									}
+									GitBlit.self().updateTeamModel(teamname, team, false);
+								} else if(!ArrayUtils.isEmpty(remoteTeam.repositories)) {
+									// pulling from <= 1.1
+									team.addRepositoryPermissions(remoteTeam.repositories);
+									GitBlit.self().updateTeamModel(teamname, team, false);
+								}
+							}
+						}
+					}
+				}
+			}
+		} catch (ForbiddenException e) {
+			// ignore forbidden exceptions
+		} catch (IOException e) {
+			logger.warn(MessageFormat.format(
+					"Failed to retrieve USERS from federated gitblit ({0} @ {1})",
+					registration.name, registration.url), e);
+		}
+
+		try {
+			// Pull TEAMS
+			// We explicitly pull these even though they are embedded in
+			// UserModels because it is possible to use teams to specify
+			// mailing lists or push scripts without specifying users.
+			if (userService != null) {
+				Collection<TeamModel> teams = FederationUtils.getTeams(registration);
+				if (teams != null && teams.size() > 0) {
+					for (TeamModel team : teams) {
+						userService.updateTeamModel(team);
+					}
+				}
+			}
+		} catch (ForbiddenException e) {
+			// ignore forbidden exceptions
+		} catch (IOException e) {
+			logger.warn(MessageFormat.format(
+					"Failed to retrieve TEAMS from federated gitblit ({0} @ {1})",
+					registration.name, registration.url), e);
+		}
+
+		try {
+			// Pull SETTINGS
+			Map<String, String> settings = FederationUtils.getSettings(registration);
+			if (settings != null && settings.size() > 0) {
+				Properties properties = new Properties();
+				properties.putAll(settings);
+				FileOutputStream os = new FileOutputStream(new File(registrationFolderFile,
+						registration.name + "_" + Constants.PROPERTIES_FILE));
+				properties.store(os, null);
+				os.close();
+			}
+		} catch (ForbiddenException e) {
+			// ignore forbidden exceptions
+		} catch (IOException e) {
+			logger.warn(MessageFormat.format(
+					"Failed to retrieve SETTINGS from federated gitblit ({0} @ {1})",
+					registration.name, registration.url), e);
+		}
+
+		try {
+			// Pull SCRIPTS
+			Map<String, String> scripts = FederationUtils.getScripts(registration);
+			if (scripts != null && scripts.size() > 0) {
+				for (Map.Entry<String, String> script : scripts.entrySet()) {
+					String scriptName = script.getKey();
+					if (scriptName.endsWith(".groovy")) {
+						scriptName = scriptName.substring(0, scriptName.indexOf(".groovy"));
+					}
+					File file = new File(registrationFolderFile, registration.name + "_"
+							+ scriptName + ".groovy");
+					FileUtils.writeContent(file, script.getValue());
+				}
+			}
+		} catch (ForbiddenException e) {
+			// ignore forbidden exceptions
+		} catch (IOException e) {
+			logger.warn(MessageFormat.format(
+					"Failed to retrieve SCRIPTS from federated gitblit ({0} @ {1})",
+					registration.name, registration.url), e);
+		}
+	}
+
+	/**
+	 * Sends a status acknowledgment to the origin Gitblit instance. This
+	 * includes the results of the federated pull.
+	 * 
+	 * @param registration
+	 * @throws Exception
+	 */
+	private void sendStatusAcknowledgment(FederationModel registration) throws Exception {
+		if (!registration.sendStatus) {
+			// skip status acknowledgment
+			return;
+		}
+		InetAddress addr = InetAddress.getLocalHost();
+		String federationName = GitBlit.getString(Keys.federation.name, null);
+		if (StringUtils.isEmpty(federationName)) {
+			federationName = addr.getHostName();
+		}
+		FederationUtils.acknowledgeStatus(addr.getHostAddress(), registration);
+		logger.info(MessageFormat.format("Pull status sent to {0}", registration.url));
+	}
+
+	/**
+	 * Schedules the next check of the federated Gitblit instance.
+	 * 
+	 * @param registration
+	 */
+	private void schedule(FederationModel registration) {
+		// schedule the next pull
+		int mins = TimeUtils.convertFrequencyToMinutes(registration.frequency);
+		registration.nextPull = new Date(System.currentTimeMillis() + (mins * 60 * 1000L));
+		GitBlit.self().executor()
+				.schedule(new FederationPullExecutor(registration), mins, TimeUnit.MINUTES);
+		logger.info(MessageFormat.format(
+				"Next pull of {0} @ {1} scheduled for {2,date,yyyy-MM-dd HH:mm}",
+				registration.name, registration.url, registration.nextPull));
+	}
+}
diff --git a/src/com/gitblit/FederationServlet.java b/src/main/java/com/gitblit/FederationServlet.java
similarity index 100%
rename from src/com/gitblit/FederationServlet.java
rename to src/main/java/com/gitblit/FederationServlet.java
diff --git a/src/com/gitblit/FileSettings.java b/src/main/java/com/gitblit/FileSettings.java
similarity index 100%
rename from src/com/gitblit/FileSettings.java
rename to src/main/java/com/gitblit/FileSettings.java
diff --git a/src/main/java/com/gitblit/FileUserService.java b/src/main/java/com/gitblit/FileUserService.java
new file mode 100644
index 0000000..32c24cc
--- /dev/null
+++ b/src/main/java/com/gitblit/FileUserService.java
@@ -0,0 +1,1146 @@
+/*
+ * Copyright 2011 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.gitblit.Constants.AccessPermission;
+import com.gitblit.models.TeamModel;
+import com.gitblit.models.UserModel;
+import com.gitblit.utils.ArrayUtils;
+import com.gitblit.utils.DeepCopier;
+import com.gitblit.utils.StringUtils;
+
+/**
+ * FileUserService is Gitblit's original default user service implementation.
+ * 
+ * Users and their repository memberships are stored in a simple properties file
+ * which is cached and dynamically reloaded when modified.
+ * 
+ * This class was deprecated in Gitblit 0.8.0 in favor of ConfigUserService
+ * which is still a human-readable, editable, plain-text file but it is more
+ * flexible for storing additional fields.
+ * 
+ * @author James Moger
+ * 
+ */
+@Deprecated
+public class FileUserService extends FileSettings implements IUserService {
+
+	private final Logger logger = LoggerFactory.getLogger(FileUserService.class);
+
+	private final Map<String, String> cookies = new ConcurrentHashMap<String, String>();
+
+	private final Map<String, TeamModel> teams = new ConcurrentHashMap<String, TeamModel>();
+
+	public FileUserService(File realmFile) {
+		super(realmFile.getAbsolutePath());
+	}
+
+	/**
+	 * Setup the user service.
+	 * 
+	 * @param settings
+	 * @since 0.7.0
+	 */
+	@Override
+	public void setup(IStoredSettings settings) {
+	}
+
+	/**
+	 * Does the user service support changes to credentials?
+	 * 
+	 * @return true or false
+	 * @since 1.0.0
+	 */
+	@Override
+	public boolean supportsCredentialChanges() {
+		return true;
+	}
+
+	/**
+	 * Does the user service support changes to user display name?
+	 * 
+	 * @return true or false
+	 * @since 1.0.0
+	 */
+	@Override
+	public boolean supportsDisplayNameChanges() {
+		return false;
+	}
+
+	/**
+	 * Does the user service support changes to user email address?
+	 * 
+	 * @return true or false
+	 * @since 1.0.0
+	 */
+	@Override
+	public boolean supportsEmailAddressChanges() {
+		return false;
+	}
+
+	/**
+	 * Does the user service support changes to team memberships?
+	 * 
+	 * @return true or false
+	 * @since 1.0.0
+	 */	
+	public boolean supportsTeamMembershipChanges() {
+		return true;
+	}
+
+	/**
+	 * Does the user service support cookie authentication?
+	 * 
+	 * @return true or false
+	 */
+	@Override
+	public boolean supportsCookies() {
+		return true;
+	}
+
+	/**
+	 * Returns the cookie value for the specified user.
+	 * 
+	 * @param model
+	 * @return cookie value
+	 */
+	@Override
+	public String getCookie(UserModel model) {
+		if (!StringUtils.isEmpty(model.cookie)) {
+			return model.cookie;
+		}
+		Properties allUsers = super.read();
+		String value = allUsers.getProperty(model.username);
+		String[] roles = value.split(",");
+		String password = roles[0];
+		String cookie = StringUtils.getSHA1(model.username + password);
+		return cookie;
+	}
+
+	/**
+	 * Authenticate a user based on their cookie.
+	 * 
+	 * @param cookie
+	 * @return a user object or null
+	 */
+	@Override
+	public UserModel authenticate(char[] cookie) {
+		String hash = new String(cookie);
+		if (StringUtils.isEmpty(hash)) {
+			return null;
+		}
+		read();
+		UserModel model = null;
+		if (cookies.containsKey(hash)) {
+			String username = cookies.get(hash);
+			model = getUserModel(username);
+		}
+		return model;
+	}
+
+	/**
+	 * Authenticate a user based on a username and password.
+	 * 
+	 * @param username
+	 * @param password
+	 * @return a user object or null
+	 */
+	@Override
+	public UserModel authenticate(String username, char[] password) {
+		Properties allUsers = read();
+		String userInfo = allUsers.getProperty(username);
+		if (StringUtils.isEmpty(userInfo)) {
+			return null;
+		}
+		UserModel returnedUser = null;
+		UserModel user = getUserModel(username);
+		if (user.password.startsWith(StringUtils.MD5_TYPE)) {
+			// password digest
+			String md5 = StringUtils.MD5_TYPE + StringUtils.getMD5(new String(password));
+			if (user.password.equalsIgnoreCase(md5)) {
+				returnedUser = user;
+			}
+		} else if (user.password.startsWith(StringUtils.COMBINED_MD5_TYPE)) {
+			// username+password digest
+			String md5 = StringUtils.COMBINED_MD5_TYPE
+					+ StringUtils.getMD5(username.toLowerCase() + new String(password));
+			if (user.password.equalsIgnoreCase(md5)) {
+				returnedUser = user;
+			}
+		} else if (user.password.equals(new String(password))) {
+			// plain-text password
+			returnedUser = user;
+		}
+		return returnedUser;
+	}
+
+	/**
+	 * Logout a user.
+	 * 
+	 * @param user
+	 */
+	@Override
+	public void logout(UserModel user) {	
+	}
+
+	/**
+	 * Retrieve the user object for the specified username.
+	 * 
+	 * @param username
+	 * @return a user object or null
+	 */
+	@Override
+	public UserModel getUserModel(String username) {
+		Properties allUsers = read();
+		String userInfo = allUsers.getProperty(username.toLowerCase());
+		if (userInfo == null) {
+			return null;
+		}
+		UserModel model = new UserModel(username.toLowerCase());
+		String[] userValues = userInfo.split(",");
+		model.password = userValues[0];
+		for (int i = 1; i < userValues.length; i++) {
+			String role = userValues[i];
+			switch (role.charAt(0)) {
+			case '#':
+				// Permissions
+				if (role.equalsIgnoreCase(Constants.ADMIN_ROLE)) {
+					model.canAdmin = true;
+				} else if (role.equalsIgnoreCase(Constants.FORK_ROLE)) {
+					model.canFork = true;
+				} else if (role.equalsIgnoreCase(Constants.CREATE_ROLE)) {
+					model.canCreate = true;
+				} else if (role.equalsIgnoreCase(Constants.NOT_FEDERATED_ROLE)) {
+					model.excludeFromFederation = true;
+				}
+				break;
+			default:
+				model.addRepositoryPermission(role);
+			}
+		}
+		// set the teams for the user
+		for (TeamModel team : teams.values()) {
+			if (team.hasUser(username)) {
+				model.teams.add(DeepCopier.copy(team));
+			}
+		}
+		return model;
+	}
+
+	/**
+	 * Updates/writes a complete user object.
+	 * 
+	 * @param model
+	 * @return true if update is successful
+	 */
+	@Override
+	public boolean updateUserModel(UserModel model) {
+		return updateUserModel(model.username, model);
+	}
+
+	/**
+	 * Updates/writes all specified user objects.
+	 * 
+	 * @param models a list of user models
+	 * @return true if update is successful
+	 * @since 1.2.0
+	 */
+	@Override
+	public boolean updateUserModels(Collection<UserModel> models) {
+		try {			
+			Properties allUsers = read();
+			for (UserModel model : models) {
+				updateUserCache(allUsers, model.username, model);
+			}
+			write(allUsers);
+			return true;
+		} catch (Throwable t) {
+			logger.error(MessageFormat.format("Failed to update {0} user models!", models.size()),
+					t);
+		}
+		return false;
+	}
+
+	/**
+	 * 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
+	 *            the user object to use for username
+	 * @return true if update is successful
+	 */
+	@Override
+	public boolean updateUserModel(String username, UserModel model) {
+		try {			
+			Properties allUsers = read();
+			updateUserCache(allUsers, username, model);
+			write(allUsers);
+			return true;
+		} catch (Throwable t) {
+			logger.error(MessageFormat.format("Failed to update user model {0}!", model.username),
+					t);
+		}
+		return false;
+	}
+	
+	/**
+	 * 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
+	 *            the user object to use for username
+	 * @return true if update is successful
+	 */
+	private boolean updateUserCache(Properties allUsers, String username, UserModel model) {
+		try {			
+			UserModel oldUser = getUserModel(username);
+			List<String> roles;
+			if (model.permissions == null) {
+				roles = new ArrayList<String>();
+			} else {
+				// discrete repository permissions
+				roles = new ArrayList<String>();
+				for (Map.Entry<String, AccessPermission> entry : model.permissions.entrySet()) {
+					if (entry.getValue().exceeds(AccessPermission.NONE)) {
+						// code:repository (e.g. RW+:~james/myrepo.git
+						roles.add(entry.getValue().asRole(entry.getKey()));
+					}
+				}
+			}
+
+			// Permissions
+			if (model.canAdmin) {
+				roles.add(Constants.ADMIN_ROLE);
+			}
+			if (model.canFork) {
+				roles.add(Constants.FORK_ROLE);
+			}
+			if (model.canCreate) {
+				roles.add(Constants.CREATE_ROLE);
+			}
+			if (model.excludeFromFederation) {
+				roles.add(Constants.NOT_FEDERATED_ROLE);
+			}
+
+			StringBuilder sb = new StringBuilder();
+			if (!StringUtils.isEmpty(model.password)) {
+				sb.append(model.password);
+			}
+			sb.append(',');
+			for (String role : roles) {
+				sb.append(role);
+				sb.append(',');
+			}
+			// trim trailing comma
+			sb.setLength(sb.length() - 1);
+			allUsers.remove(username.toLowerCase());
+			allUsers.put(model.username.toLowerCase(), sb.toString());
+
+			// null check on "final" teams because JSON-sourced UserModel
+			// can have a null teams object
+			if (model.teams != null) {
+				// update team cache
+				for (TeamModel team : model.teams) {
+					TeamModel t = getTeamModel(team.name);
+					if (t == null) {
+						// new team
+						t = team;
+					}
+					t.removeUser(username);
+					t.addUser(model.username);
+					updateTeamCache(allUsers, t.name, t);
+				}
+
+				// check for implicit team removal
+				if (oldUser != null) {
+					for (TeamModel team : oldUser.teams) {
+						if (!model.isTeamMember(team.name)) {
+							team.removeUser(username);
+							updateTeamCache(allUsers, team.name, team);
+						}
+					}
+				}
+			}
+			return true;
+		} catch (Throwable t) {
+			logger.error(MessageFormat.format("Failed to update user model {0}!", model.username),
+					t);
+		}
+		return false;
+	}
+
+	/**
+	 * Deletes the user object from the user service.
+	 * 
+	 * @param model
+	 * @return true if successful
+	 */
+	@Override
+	public boolean deleteUserModel(UserModel model) {
+		return deleteUser(model.username);
+	}
+
+	/**
+	 * Delete the user object with the specified username
+	 * 
+	 * @param username
+	 * @return true if successful
+	 */
+	@Override
+	public boolean deleteUser(String username) {
+		try {
+			// Read realm file
+			Properties allUsers = read();
+			UserModel user = getUserModel(username);
+			allUsers.remove(username);
+			for (TeamModel team : user.teams) {
+				TeamModel t = getTeamModel(team.name);
+				if (t == null) {
+					// new team
+					t = team;
+				}
+				t.removeUser(username);
+				updateTeamCache(allUsers, t.name, t);
+			}
+			write(allUsers);
+			return true;
+		} catch (Throwable t) {
+			logger.error(MessageFormat.format("Failed to delete user {0}!", username), t);
+		}
+		return false;
+	}
+
+	/**
+	 * Returns the list of all users available to the login service.
+	 * 
+	 * @return list of all usernames
+	 */
+	@Override
+	public List<String> getAllUsernames() {
+		Properties allUsers = read();
+		List<String> list = new ArrayList<String>();
+		for (String user : allUsers.stringPropertyNames()) {
+			if (user.charAt(0) == '@') {
+				// skip team user definitions
+				continue;
+			}
+			list.add(user);
+		}
+		Collections.sort(list);
+		return list;
+	}
+
+	/**
+	 * Returns the list of all users available to the login service.
+	 * 
+	 * @return list of all usernames
+	 */
+	@Override
+	public List<UserModel> getAllUsers() {
+		read();
+		List<UserModel> list = new ArrayList<UserModel>();
+		for (String username : getAllUsernames()) {
+			list.add(getUserModel(username));
+		}
+		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
+	 */
+	@Override
+	public List<String> getUsernamesForRepositoryRole(String role) {
+		List<String> list = new ArrayList<String>();
+		try {
+			Properties allUsers = read();
+			for (String username : allUsers.stringPropertyNames()) {
+				if (username.charAt(0) == '@') {
+					continue;
+				}
+				String value = allUsers.getProperty(username);
+				String[] values = value.split(",");
+				// skip first value (password)
+				for (int i = 1; i < values.length; i++) {
+					String r = values[i];
+					if (r.equalsIgnoreCase(role)) {
+						list.add(username);
+						break;
+					}
+				}
+			}
+		} catch (Throwable t) {
+			logger.error(MessageFormat.format("Failed to get usernames for role {0}!", role), t);
+		}
+		Collections.sort(list);
+		return list;
+	}
+
+	/**
+	 * Sets the list of all users who are allowed to bypass the access
+	 * restriction placed on the specified repository.
+	 * 
+	 * @param role
+	 *            the repository name
+	 * @param usernames
+	 * @return true if successful
+	 */
+	@Override
+	public boolean setUsernamesForRepositoryRole(String role, List<String> usernames) {
+		try {
+			Set<String> specifiedUsers = new HashSet<String>(usernames);
+			Set<String> needsAddRole = new HashSet<String>(specifiedUsers);
+			Set<String> needsRemoveRole = new HashSet<String>();
+
+			// identify users which require add and remove role
+			Properties allUsers = read();
+			for (String username : allUsers.stringPropertyNames()) {
+				String value = allUsers.getProperty(username);
+				String[] values = value.split(",");
+				// skip first value (password)
+				for (int i = 1; i < values.length; i++) {
+					String r = values[i];
+					if (r.equalsIgnoreCase(role)) {
+						// user has role, check against revised user list
+						if (specifiedUsers.contains(username)) {
+							needsAddRole.remove(username);
+						} else {
+							// remove role from user
+							needsRemoveRole.add(username);
+						}
+						break;
+					}
+				}
+			}
+
+			// add roles to users
+			for (String user : needsAddRole) {
+				String userValues = allUsers.getProperty(user);
+				userValues += "," + role;
+				allUsers.put(user, userValues);
+			}
+
+			// remove role from user
+			for (String user : needsRemoveRole) {
+				String[] values = allUsers.getProperty(user).split(",");
+				String password = values[0];
+				StringBuilder sb = new StringBuilder();
+				sb.append(password);
+				sb.append(',');
+
+				// skip first value (password)
+				for (int i = 1; i < values.length; i++) {
+					String value = values[i];
+					if (!value.equalsIgnoreCase(role)) {
+						sb.append(value);
+						sb.append(',');
+					}
+				}
+				sb.setLength(sb.length() - 1);
+
+				// update properties
+				allUsers.put(user, sb.toString());
+			}
+
+			// persist changes
+			write(allUsers);
+			return true;
+		} catch (Throwable t) {
+			logger.error(MessageFormat.format("Failed to set usernames for role {0}!", role), t);
+		}
+		return false;
+	}
+
+	/**
+	 * Renames a repository role.
+	 * 
+	 * @param oldRole
+	 * @param newRole
+	 * @return true if successful
+	 */
+	@Override
+	public boolean renameRepositoryRole(String oldRole, String newRole) {
+		try {
+			Properties allUsers = read();
+			Set<String> needsRenameRole = new HashSet<String>();
+
+			// identify users which require role rename
+			for (String username : allUsers.stringPropertyNames()) {
+				String value = allUsers.getProperty(username);
+				String[] roles = value.split(",");
+				// skip first value (password)
+				for (int i = 1; i < roles.length; i++) {
+					String repository = AccessPermission.repositoryFromRole(roles[i]);
+					if (repository.equalsIgnoreCase(oldRole)) {
+						needsRenameRole.add(username);
+						break;
+					}
+				}
+			}
+
+			// rename role for identified users
+			for (String user : needsRenameRole) {
+				String userValues = allUsers.getProperty(user);
+				String[] values = userValues.split(",");
+				String password = values[0];
+				StringBuilder sb = new StringBuilder();
+				sb.append(password);
+				sb.append(',');
+				sb.append(newRole);
+				sb.append(',');
+
+				// skip first value (password)
+				for (int i = 1; i < values.length; i++) {
+					String repository = AccessPermission.repositoryFromRole(values[i]);
+					if (repository.equalsIgnoreCase(oldRole)) {
+						AccessPermission permission = AccessPermission.permissionFromRole(values[i]);
+						sb.append(permission.asRole(newRole));
+						sb.append(',');
+					} else {
+						sb.append(values[i]);
+						sb.append(',');
+					}
+				}
+				sb.setLength(sb.length() - 1);
+
+				// update properties
+				allUsers.put(user, sb.toString());
+			}
+
+			// persist changes
+			write(allUsers);
+			return true;
+		} catch (Throwable t) {
+			logger.error(
+					MessageFormat.format("Failed to rename role {0} to {1}!", oldRole, newRole), t);
+		}
+		return false;
+	}
+
+	/**
+	 * Removes a repository role from all users.
+	 * 
+	 * @param role
+	 * @return true if successful
+	 */
+	@Override
+	public boolean deleteRepositoryRole(String role) {
+		try {
+			Properties allUsers = read();
+			Set<String> needsDeleteRole = new HashSet<String>();
+
+			// identify users which require role rename
+			for (String username : allUsers.stringPropertyNames()) {
+				String value = allUsers.getProperty(username);
+				String[] roles = value.split(",");
+				// skip first value (password)
+				for (int i = 1; i < roles.length; i++) {					
+					String repository = AccessPermission.repositoryFromRole(roles[i]);
+					if (repository.equalsIgnoreCase(role)) {
+						needsDeleteRole.add(username);
+						break;
+					}
+				}
+			}
+
+			// delete role for identified users
+			for (String user : needsDeleteRole) {
+				String userValues = allUsers.getProperty(user);
+				String[] values = userValues.split(",");
+				String password = values[0];
+				StringBuilder sb = new StringBuilder();
+				sb.append(password);
+				sb.append(',');
+				// skip first value (password)
+				for (int i = 1; i < values.length; i++) {					
+					String repository = AccessPermission.repositoryFromRole(values[i]);
+					if (!repository.equalsIgnoreCase(role)) {
+						sb.append(values[i]);
+						sb.append(',');
+					}
+				}
+				sb.setLength(sb.length() - 1);
+
+				// update properties
+				allUsers.put(user, sb.toString());
+			}
+
+			// persist changes
+			write(allUsers);
+			return true;
+		} catch (Throwable t) {
+			logger.error(MessageFormat.format("Failed to delete role {0}!", role), t);
+		}
+		return false;
+	}
+
+	/**
+	 * Writes the properties file.
+	 * 
+	 * @param properties
+	 * @throws IOException
+	 */
+	private void write(Properties properties) throws IOException {
+		// Write a temporary copy of the users file
+		File realmFileCopy = new File(propertiesFile.getAbsolutePath() + ".tmp");
+		FileWriter writer = new FileWriter(realmFileCopy);
+		properties
+				.store(writer,
+						" Gitblit realm file format:\n   username=password,\\#permission,repository1,repository2...\n   @teamname=!username1,!username2,!username3,repository1,repository2...");
+		writer.close();
+		// If the write is successful, delete the current file and rename
+		// the temporary copy to the original filename.
+		if (realmFileCopy.exists() && realmFileCopy.length() > 0) {
+			if (propertiesFile.exists()) {
+				if (!propertiesFile.delete()) {
+					throw new IOException(MessageFormat.format("Failed to delete {0}!",
+							propertiesFile.getAbsolutePath()));
+				}
+			}
+			if (!realmFileCopy.renameTo(propertiesFile)) {
+				throw new IOException(MessageFormat.format("Failed to rename {0} to {1}!",
+						realmFileCopy.getAbsolutePath(), propertiesFile.getAbsolutePath()));
+			}
+		} else {
+			throw new IOException(MessageFormat.format("Failed to save {0}!",
+					realmFileCopy.getAbsolutePath()));
+		}
+	}
+
+	/**
+	 * Reads the properties file and rebuilds the in-memory cookie lookup table.
+	 */
+	@Override
+	protected synchronized Properties read() {
+		long lastRead = lastModified();
+		boolean reload = forceReload();
+		Properties allUsers = super.read();
+		if (reload || (lastRead != lastModified())) {
+			// reload hash cache
+			cookies.clear();
+			teams.clear();
+
+			for (String username : allUsers.stringPropertyNames()) {
+				String value = allUsers.getProperty(username);
+				String[] roles = value.split(",");
+				if (username.charAt(0) == '@') {
+					// team definition
+					TeamModel team = new TeamModel(username.substring(1));
+					List<String> repositories = new ArrayList<String>();
+					List<String> users = new ArrayList<String>();
+					List<String> mailingLists = new ArrayList<String>();
+					List<String> preReceive = new ArrayList<String>();
+					List<String> postReceive = new ArrayList<String>();
+					for (String role : roles) {
+						if (role.charAt(0) == '!') {
+							users.add(role.substring(1));
+						} else if (role.charAt(0) == '&') {
+							mailingLists.add(role.substring(1));
+						} else if (role.charAt(0) == '^') {
+							preReceive.add(role.substring(1));
+						} else if (role.charAt(0) == '%') {
+							postReceive.add(role.substring(1));
+						} else {
+							switch (role.charAt(0)) {
+							case '#':
+								// Permissions
+								if (role.equalsIgnoreCase(Constants.ADMIN_ROLE)) {
+									team.canAdmin = true;
+								} else if (role.equalsIgnoreCase(Constants.FORK_ROLE)) {
+									team.canFork = true;
+								} else if (role.equalsIgnoreCase(Constants.CREATE_ROLE)) {
+									team.canCreate = true;
+								}
+								break;
+							default:
+								repositories.add(role);
+							}
+							repositories.add(role);
+						}
+					}
+					if (!team.canAdmin) {
+						// only read permissions for non-admin teams
+						team.addRepositoryPermissions(repositories);
+					}
+					team.addUsers(users);
+					team.addMailingLists(mailingLists);
+					team.preReceiveScripts.addAll(preReceive);
+					team.postReceiveScripts.addAll(postReceive);
+					teams.put(team.name.toLowerCase(), team);
+				} else {
+					// user definition
+					String password = roles[0];
+					cookies.put(StringUtils.getSHA1(username.toLowerCase() + password), username.toLowerCase());
+				}
+			}
+		}
+		return allUsers;
+	}
+
+	@Override
+	public String toString() {
+		return getClass().getSimpleName() + "(" + propertiesFile.getAbsolutePath() + ")";
+	}
+
+	/**
+	 * Returns the list of all teams available to the login service.
+	 * 
+	 * @return list of all teams
+	 * @since 0.8.0
+	 */
+	@Override
+	public List<String> getAllTeamNames() {
+		List<String> list = new ArrayList<String>(teams.keySet());
+		Collections.sort(list);
+		return list;
+	}
+
+	/**
+	 * Returns the list of all teams available to the login service.
+	 * 
+	 * @return list of all teams
+	 * @since 0.8.0
+	 */
+	@Override
+	public List<TeamModel> getAllTeams() {
+		List<TeamModel> list = new ArrayList<TeamModel>(teams.values());
+		list = DeepCopier.copy(list);
+		Collections.sort(list);
+		return list;
+	}
+
+	/**
+	 * Returns the list of all teams who are allowed to bypass the access
+	 * restriction placed on the specified repository.
+	 * 
+	 * @param role
+	 *            the repository name
+	 * @return list of all teamnames that can bypass the access restriction
+	 */
+	@Override
+	public List<String> getTeamnamesForRepositoryRole(String role) {
+		List<String> list = new ArrayList<String>();
+		try {
+			Properties allUsers = read();
+			for (String team : allUsers.stringPropertyNames()) {
+				if (team.charAt(0) != '@') {
+					// skip users
+					continue;
+				}
+				String value = allUsers.getProperty(team);
+				String[] values = value.split(",");
+				for (int i = 0; i < values.length; i++) {
+					String r = values[i];
+					if (r.equalsIgnoreCase(role)) {
+						// strip leading @
+						list.add(team.substring(1));
+						break;
+					}
+				}
+			}
+		} catch (Throwable t) {
+			logger.error(MessageFormat.format("Failed to get teamnames for role {0}!", role), t);
+		}
+		Collections.sort(list);
+		return list;
+	}
+
+	/**
+	 * 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
+	 * @return true if successful
+	 */
+	@Override
+	public boolean setTeamnamesForRepositoryRole(String role, List<String> teamnames) {
+		try {
+			Set<String> specifiedTeams = new HashSet<String>(teamnames);
+			Set<String> needsAddRole = new HashSet<String>(specifiedTeams);
+			Set<String> needsRemoveRole = new HashSet<String>();
+
+			// identify teams which require add and remove role
+			Properties allUsers = read();
+			for (String team : allUsers.stringPropertyNames()) {
+				if (team.charAt(0) != '@') {
+					// skip users
+					continue;
+				}
+				String name = team.substring(1);
+				String value = allUsers.getProperty(team);
+				String[] values = value.split(",");
+				for (int i = 0; i < values.length; i++) {
+					String r = values[i];
+					if (r.equalsIgnoreCase(role)) {
+						// team has role, check against revised team list
+						if (specifiedTeams.contains(name)) {
+							needsAddRole.remove(name);
+						} else {
+							// remove role from team
+							needsRemoveRole.add(name);
+						}
+						break;
+					}
+				}
+			}
+
+			// add roles to teams
+			for (String name : needsAddRole) {
+				String team = "@" + name;
+				String teamValues = allUsers.getProperty(team);
+				teamValues += "," + role;
+				allUsers.put(team, teamValues);
+			}
+
+			// remove role from team
+			for (String name : needsRemoveRole) {
+				String team = "@" + name;
+				String[] values = allUsers.getProperty(team).split(",");
+				StringBuilder sb = new StringBuilder();
+				for (int i = 0; i < values.length; i++) {
+					String value = values[i];
+					if (!value.equalsIgnoreCase(role)) {
+						sb.append(value);
+						sb.append(',');
+					}
+				}
+				sb.setLength(sb.length() - 1);
+
+				// update properties
+				allUsers.put(team, sb.toString());
+			}
+
+			// persist changes
+			write(allUsers);
+			return true;
+		} catch (Throwable t) {
+			logger.error(MessageFormat.format("Failed to set teamnames for role {0}!", role), t);
+		}
+		return false;
+	}
+
+	/**
+	 * Retrieve the team object for the specified team name.
+	 * 
+	 * @param teamname
+	 * @return a team object or null
+	 * @since 0.8.0
+	 */
+	@Override
+	public TeamModel getTeamModel(String teamname) {
+		read();
+		TeamModel team = teams.get(teamname.toLowerCase());
+		if (team != null) {
+			// clone the model, otherwise all changes to this object are
+			// live and unpersisted
+			team = DeepCopier.copy(team);
+		}
+		return team;
+	}
+
+	/**
+	 * Updates/writes a complete team object.
+	 * 
+	 * @param model
+	 * @return true if update is successful
+	 * @since 0.8.0
+	 */
+	@Override
+	public boolean updateTeamModel(TeamModel model) {
+		return updateTeamModel(model.name, model);
+	}
+	
+	/**
+	 * Updates/writes all specified team objects.
+	 * 
+	 * @param models a list of team models
+	 * @return true if update is successful
+	 * @since 1.2.0
+	 */
+	public boolean updateTeamModels(Collection<TeamModel> models) {
+		try {
+			Properties allUsers = read();
+			for (TeamModel model : models) {
+				updateTeamCache(allUsers, model.name, model);
+			}
+			write(allUsers);
+			return true;
+		} catch (Throwable t) {
+			logger.error(MessageFormat.format("Failed to update {0} team models!", models.size()), t);
+		}
+		return false;
+	}
+
+	/**
+	 * 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
+	 *            the team object to use for teamname
+	 * @return true if update is successful
+	 * @since 0.8.0
+	 */
+	@Override
+	public boolean updateTeamModel(String teamname, TeamModel model) {
+		try {
+			Properties allUsers = read();
+			updateTeamCache(allUsers, teamname, model);
+			write(allUsers);
+			return true;
+		} catch (Throwable t) {
+			logger.error(MessageFormat.format("Failed to update team model {0}!", model.name), t);
+		}
+		return false;
+	}
+
+	private void updateTeamCache(Properties allUsers, String teamname, TeamModel model) {
+		StringBuilder sb = new StringBuilder();
+		List<String> roles;
+		if (model.permissions == null) {
+			// legacy, use repository list
+			if (model.repositories != null) {
+				roles = new ArrayList<String>(model.repositories);
+			} else {
+				roles = new ArrayList<String>();
+			}
+		} else {
+			// discrete repository permissions
+			roles = new ArrayList<String>();
+			for (Map.Entry<String, AccessPermission> entry : model.permissions.entrySet()) {
+				if (entry.getValue().exceeds(AccessPermission.NONE)) {
+					// code:repository (e.g. RW+:~james/myrepo.git
+					roles.add(entry.getValue().asRole(entry.getKey()));
+				}
+			}
+		}
+		
+		// Permissions
+		if (model.canAdmin) {
+			roles.add(Constants.ADMIN_ROLE);
+		}
+		if (model.canFork) {
+			roles.add(Constants.FORK_ROLE);
+		}
+		if (model.canCreate) {
+			roles.add(Constants.CREATE_ROLE);
+		}
+
+		for (String role : roles) {
+				sb.append(role);
+				sb.append(',');
+		}
+		
+		if (!ArrayUtils.isEmpty(model.users)) {
+			for (String user : model.users) {
+				sb.append('!');
+				sb.append(user);
+				sb.append(',');
+			}
+		}
+		if (!ArrayUtils.isEmpty(model.mailingLists)) {
+			for (String address : model.mailingLists) {
+				sb.append('&');
+				sb.append(address);
+				sb.append(',');
+			}
+		}
+		if (!ArrayUtils.isEmpty(model.preReceiveScripts)) {
+			for (String script : model.preReceiveScripts) {
+				sb.append('^');
+				sb.append(script);
+				sb.append(',');
+			}
+		}
+		if (!ArrayUtils.isEmpty(model.postReceiveScripts)) {
+			for (String script : model.postReceiveScripts) {
+				sb.append('%');
+				sb.append(script);
+				sb.append(',');
+			}
+		}
+		// trim trailing comma
+		sb.setLength(sb.length() - 1);
+		allUsers.remove("@" + teamname);
+		allUsers.put("@" + model.name, sb.toString());
+
+		// update team cache
+		teams.remove(teamname.toLowerCase());
+		teams.put(model.name.toLowerCase(), model);
+	}
+
+	/**
+	 * Deletes the team object from the user service.
+	 * 
+	 * @param model
+	 * @return true if successful
+	 * @since 0.8.0
+	 */
+	@Override
+	public boolean deleteTeamModel(TeamModel model) {
+		return deleteTeam(model.name);
+	}
+
+	/**
+	 * Delete the team object with the specified teamname
+	 * 
+	 * @param teamname
+	 * @return true if successful
+	 * @since 0.8.0
+	 */
+	@Override
+	public boolean deleteTeam(String teamname) {
+		Properties allUsers = read();
+		teams.remove(teamname.toLowerCase());
+		allUsers.remove("@" + teamname);
+		try {
+			write(allUsers);
+			return true;
+		} catch (Throwable t) {
+			logger.error(MessageFormat.format("Failed to delete team {0}!", teamname), t);
+		}
+		return false;
+	}
+}
diff --git a/src/main/java/com/gitblit/GCExecutor.java b/src/main/java/com/gitblit/GCExecutor.java
new file mode 100644
index 0000000..0a0c8ad
--- /dev/null
+++ b/src/main/java/com/gitblit/GCExecutor.java
@@ -0,0 +1,239 @@
+/*
+ * 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.lang.reflect.Field;
+import java.text.MessageFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.Map;
+import java.util.Properties;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.eclipse.jgit.api.GarbageCollectCommand;
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.lib.Repository;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.gitblit.models.RepositoryModel;
+import com.gitblit.utils.FileUtils;
+
+/**
+ * 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();
+		}
+	}
+	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) {
+		this.settings = settings;
+	}
+
+	/**
+	 * 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
+	 */
+	private boolean setGCStatus(String repositoryName, GCStatus status) {
+		String key = repositoryName.toLowerCase();
+		if (gcCache.containsKey(key)) {
+			if (gcCache.get(key).exceeds(GCStatus.READY)) {
+				// already collecting or blocked
+				return false;
+			}
+		}
+		gcCache.put(key, status);
+		return true;
+	}
+
+	/**
+	 * Returns true if Gitblit is actively collecting garbage in this repository.
+	 * 
+	 * @param repositoryName
+	 * @return true if actively collecting garbage
+	 */
+	public boolean isCollectingGarbage(String repositoryName) {
+		String key = repositoryName.toLowerCase();
+		return gcCache.containsKey(key) && GCStatus.COLLECTING.equals(gcCache.get(key));
+	}
+
+	/**
+	 * 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);
+	}
+
+	@Override
+	public void run() {
+		if (!isReady()) {
+			return;
+		}
+		
+		running.set(true);		
+		Date now = new Date();
+
+		for (String repositoryName : GitBlit.self().getRepositoryList()) {
+			if (forceClose.get()) {
+				break;
+			}
+			if (isCollectingGarbage(repositoryName)) {
+				logger.warn(MessageFormat.format("Already collecting garbage from {0}?!?", repositoryName));
+				continue;
+			}
+			boolean garbageCollected = false;
+			RepositoryModel model = null;
+			Repository repository = null;
+			try {
+				model = GitBlit.self().getRepositoryModel(repositoryName);
+				repository = GitBlit.self().getRepository(repositoryName);
+				if (repository == null) {
+					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;
+				}
+
+				// By setting the GCStatus to COLLECTING we are
+				// disabling *all* access to this repository from Gitblit.
+				// Think of this as a clutch in a manual transmission vehicle.
+				if (!setGCStatus(repositoryName, GCStatus.COLLECTING)) {
+					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);
+				cal.set(Calendar.HOUR_OF_DAY, 0);
+				cal.set(Calendar.MINUTE, 0);
+				cal.set(Calendar.SECOND, 0);
+				cal.set(Calendar.MILLISECOND, 0);
+				cal.add(Calendar.DATE, model.gcPeriod);
+				Date gcDate = cal.getTime();
+				boolean shouldCollectGarbage = now.after(gcDate);
+
+				// determine if filesize triggered GC
+				long gcThreshold = FileUtils.convertSizeToLong(model.gcThreshold, 500*1024L);
+				long sizeOfLooseObjects = (Long) stats.get("sizeOfLooseObjects");
+				boolean hasEnoughGarbage = sizeOfLooseObjects >= gcThreshold;
+
+				// if we satisfy one of the requirements, GC
+				boolean hasGarbage = sizeOfLooseObjects > 0;
+				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) {
+				logger.error("Error collecting garbage in " + repositoryName, e);
+			} finally {
+				// cleanup
+				if (repository != null) {
+					if (garbageCollected) {
+						// update the last GC date
+						model.lastGC = new Date();
+						GitBlit.self().updateConfiguration(repository, model);
+					}
+				
+					repository.close();
+				}
+				
+				// 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.
+			// An idle use count is 2:
+			// +1 for being in the cache
+			// +1 for the repository parameter in this method
+			Field useCnt = Repository.class.getDeclaredField("useCnt");
+			useCnt.setAccessible(true);
+			int useCount = ((AtomicInteger) useCnt.get(repository)).get();
+			return useCount == 2;
+		} catch (Exception e) {
+			logger.warn(MessageFormat
+					.format("Failed to reflectively determine use count for repository {0}",
+							repository.getDirectory().getPath()), e);
+		}
+		return false;
+	}
+}
diff --git a/src/main/java/com/gitblit/GitBlit.java b/src/main/java/com/gitblit/GitBlit.java
new file mode 100644
index 0000000..efdaad1
--- /dev/null
+++ b/src/main/java/com/gitblit/GitBlit.java
@@ -0,0 +1,3889 @@
+/*
+ * Copyright 2011 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileFilter;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.lang.reflect.Field;
+import java.lang.reflect.Type;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.charset.Charset;
+import java.security.Principal;
+import java.text.MessageFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.TimeZone;
+import java.util.TreeMap;
+import java.util.TreeSet;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+
+import javax.mail.Message;
+import javax.mail.MessagingException;
+import javax.mail.internet.MimeBodyPart;
+import javax.mail.internet.MimeMultipart;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.wicket.RequestCycle;
+import org.apache.wicket.protocol.http.WebResponse;
+import org.apache.wicket.resource.ContextRelativeResource;
+import org.apache.wicket.util.resource.ResourceStreamNotFoundException;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.RepositoryCache;
+import org.eclipse.jgit.lib.RepositoryCache.FileKey;
+import org.eclipse.jgit.lib.StoredConfig;
+import org.eclipse.jgit.storage.file.FileBasedConfig;
+import org.eclipse.jgit.storage.file.WindowCacheConfig;
+import org.eclipse.jgit.util.FS;
+import org.eclipse.jgit.util.FileUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.gitblit.Constants.AccessPermission;
+import com.gitblit.Constants.AccessRestrictionType;
+import com.gitblit.Constants.AccountType;
+import com.gitblit.Constants.AuthenticationType;
+import com.gitblit.Constants.AuthorizationControl;
+import com.gitblit.Constants.FederationRequest;
+import com.gitblit.Constants.FederationStrategy;
+import com.gitblit.Constants.FederationToken;
+import com.gitblit.Constants.PermissionType;
+import com.gitblit.Constants.RegistrantType;
+import com.gitblit.fanout.FanoutNioService;
+import com.gitblit.fanout.FanoutService;
+import com.gitblit.fanout.FanoutSocketService;
+import com.gitblit.git.GitDaemon;
+import com.gitblit.models.FederationModel;
+import com.gitblit.models.FederationProposal;
+import com.gitblit.models.FederationSet;
+import com.gitblit.models.ForkModel;
+import com.gitblit.models.GitClientApplication;
+import com.gitblit.models.Metric;
+import com.gitblit.models.ProjectModel;
+import com.gitblit.models.RefModel;
+import com.gitblit.models.RegistrantAccessPermission;
+import com.gitblit.models.RepositoryModel;
+import com.gitblit.models.RepositoryUrl;
+import com.gitblit.models.SearchResult;
+import com.gitblit.models.ServerSettings;
+import com.gitblit.models.ServerStatus;
+import com.gitblit.models.SettingModel;
+import com.gitblit.models.TeamModel;
+import com.gitblit.models.UserModel;
+import com.gitblit.utils.ArrayUtils;
+import com.gitblit.utils.Base64;
+import com.gitblit.utils.ByteFormat;
+import com.gitblit.utils.CommitCache;
+import com.gitblit.utils.ContainerUtils;
+import com.gitblit.utils.DeepCopier;
+import com.gitblit.utils.FederationUtils;
+import com.gitblit.utils.HttpUtils;
+import com.gitblit.utils.JGitUtils;
+import com.gitblit.utils.JGitUtils.LastChange;
+import com.gitblit.utils.JsonUtils;
+import com.gitblit.utils.MetricUtils;
+import com.gitblit.utils.ObjectCache;
+import com.gitblit.utils.StringUtils;
+import com.gitblit.utils.TimeUtils;
+import com.gitblit.utils.X509Utils.X509Metadata;
+import com.gitblit.wicket.GitBlitWebSession;
+import com.gitblit.wicket.WicketUtils;
+import com.google.gson.Gson;
+import com.google.gson.JsonIOException;
+import com.google.gson.JsonSyntaxException;
+import com.google.gson.reflect.TypeToken;
+
+/**
+ * GitBlit is the servlet context listener singleton that acts as the core for
+ * 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>();
+
+	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;
+
+	private IUserService userService;
+
+	private IStoredSettings settings;
+
+	private ServerSettings settingsModel;
+
+	private ServerStatus serverStatus;
+
+	private MailExecutor mailExecutor;
+	
+	private LuceneExecutor luceneExecutor;
+	
+	private GCExecutor gcExecutor;
+	
+	private TimeZone timezone;
+	
+	private FileBasedConfig projectConfigs;
+	
+	private FanoutService fanoutService;
+
+	private GitDaemon gitDaemon;
+
+	public GitBlit() {
+		if (gitblit == null) {
+			// set the static singleton reference
+			gitblit = this;
+		}
+	}
+
+	public GitBlit(final IUserService userService) {
+		this.userService = userService;
+		gitblit = this;
+	}
+
+	/**
+	 * Returns the Gitblit singleton.
+	 * 
+	 * @return gitblit singleton
+	 */
+	public static GitBlit self() {
+		if (gitblit == null) {
+			new GitBlit();
+		}
+		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() {
+		Date date = null;
+		for (String name : self().getRepositoryList()) {
+			Repository r = self().getRepository(name);
+			Date lastChange = JGitUtils.getLastChange(r).when;
+			r.close();
+			if (lastChange != null && (date == null || lastChange.after(date))) {
+				date = lastChange;
+			}
+		}
+		return date;
+	}
+
+	/**
+	 * 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() {
+		return getBoolean(Keys.git.enableGitServlet, true) || (getInteger(Keys.git.daemonPort, 0) > 0);
+	}
+
+	/**
+	 * 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() {
+		return self().mailExecutor.isReady();
+	}
+
+	/**
+	 * Returns the preferred timezone for the Gitblit instance.
+	 * 
+	 * @return a timezone
+	 */
+	public static TimeZone getTimezone() {
+		if (self().timezone == null) {
+			String tzid = getString("web.timezone", null);
+			if (StringUtils.isEmpty(tzid)) {
+				self().timezone = TimeZone.getDefault();
+				return self().timezone;
+			}
+			self().timezone = TimeZone.getTimeZone(tzid);
+		}
+		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
+	 * @return key value or defaultValue
+	 */
+	public static boolean getBoolean(String key, boolean defaultValue) {
+		return self().settings.getBoolean(key, defaultValue);
+	}
+
+	/**
+	 * 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
+	 * @return key value or defaultValue
+	 */
+	public static int getInteger(String key, int defaultValue) {
+		return self().settings.getInteger(key, defaultValue);
+	}
+
+	/**
+	 * 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
+	 */
+	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
+	 * @return key value or defaultValue
+	 */
+	public static int getFilesize(String key, int defaultValue) {
+		return self().settings.getFilesize(key, defaultValue);
+	}
+
+	/**
+	 * 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
+	 * @return key value or defaultValue
+	 */
+	public static long getFilesize(String key, long defaultValue) {
+		return self().settings.getFilesize(key, defaultValue);
+	}
+
+	/**
+	 * 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
+	 * @return key value or defaultValue
+	 */
+	public static char getChar(String key, char defaultValue) {
+		return self().settings.getChar(key, defaultValue);
+	}
+
+	/**
+	 * 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
+	 * @return key value or defaultValue
+	 */
+	public static String getString(String key, String defaultValue) {
+		return self().settings.getString(key, defaultValue);
+	}
+
+	/**
+	 * Returns a list of space-separated strings from the specified key.
+	 * 
+	 * @see IStoredSettings.getStrings(String key)
+	 * @param n
+	 * @return list of strings
+	 */
+	public static List<String> getStrings(String key) {
+		return self().settings.getStrings(key);
+	}
+
+	/**
+	 * 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
+	 */
+	public static Map<String, String> getMap(String key) {
+		return self().settings.getMap(key);
+	}
+
+	/**
+	 * 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
+	 */
+
+	public static List<String> getAllKeys(String startingWith) {
+		return self().settings.getAllKeys(startingWith);
+	}
+
+	/**
+	 * Is Gitblit running in debug mode?
+	 * 
+	 * @return true if Gitblit is running in debug mode
+	 */
+	public static boolean isDebugMode() {
+		return self().settings.getBoolean(Keys.web.debugMode, false);
+	}
+
+	/**
+	 * Returns the file object for the specified configuration key.
+	 * 
+	 * @return the file
+	 */
+	public static File getFileOrFolder(String key, String defaultFileOrFolder) {
+		String fileOrFolder = GitBlit.getString(key, defaultFileOrFolder);
+		return getFileOrFolder(fileOrFolder);
+	}
+
+	/**
+	 * Returns the file object which may have it's base-path determined by
+	 * environment variables for running on a cloud hosting service. All Gitblit
+	 * 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) {
+		return com.gitblit.utils.FileUtils.resolveParameter(Constants.baseFolder$,
+				self().baseFolder, fileOrFolder);
+	}
+
+	/**
+	 * 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() {
+		return getFileOrFolder(Keys.git.repositoriesFolder, "${baseFolder}/git");
+	}
+
+	/**
+	 * 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() {
+		return getFileOrFolder(Keys.federation.proposalsFolder, "${baseFolder}/proposals");
+	}
+
+	/**
+	 * 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
+	 */
+	public boolean updateSettings(Map<String, String> updatedSettings) {
+		return settings.saveSettings(updatedSettings);
+	}
+
+	public ServerStatus getStatus() {
+		// update heap memory status
+		serverStatus.heapAllocated = Runtime.getRuntime().totalMemory();
+		serverStatus.heapFree = Runtime.getRuntime().freeMemory();
+		return serverStatus;
+	}
+	
+	/**
+	 * Returns a list of repository URLs and the user access permission.
+	 * 
+	 * @param request
+	 * @param user
+	 * @param repository
+	 * @return a list of repository urls
+	 */
+	public List<RepositoryUrl> getRepositoryUrls(HttpServletRequest request, UserModel user, RepositoryModel repository) {
+		if (user == null) {
+			user = UserModel.ANONYMOUS;
+		}
+		String username = encodeUsername(UserModel.ANONYMOUS.equals(user) ? "" : user.username);
+
+		List<RepositoryUrl> list = new ArrayList<RepositoryUrl>();
+		// http/https url
+		if (settings.getBoolean(Keys.git.enableGitServlet, true)) {
+			AccessPermission permission = user.getRepositoryPermission(repository).permission;
+			if (permission.exceeds(AccessPermission.NONE)) {
+				list.add(new RepositoryUrl(getRepositoryUrl(request, username, repository), permission));
+			}
+		}
+
+		// git daemon url
+		String gitDaemonUrl = getGitDaemonUrl(request, user, repository);
+		if (!StringUtils.isEmpty(gitDaemonUrl)) {
+			AccessPermission permission = getGitDaemonAccessPermission(user, repository);
+			if (permission.exceeds(AccessPermission.NONE)) {
+				list.add(new RepositoryUrl(gitDaemonUrl, permission));
+			}
+		}
+
+		// add all other urls
+		// {0} = repository
+		// {1} = username
+		for (String url : settings.getStrings(Keys.web.otherUrls)) {
+			if (url.contains("{1}")) {
+				// external url requires username, only add url IF we have one
+				if(!StringUtils.isEmpty(username)) {
+					list.add(new RepositoryUrl(MessageFormat.format(url, repository.name, username), null));
+				}
+			} else {
+				// external url does not require username
+				list.add(new RepositoryUrl(MessageFormat.format(url, repository.name), null));
+			}
+		}
+		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)) {
+			sb.insert(sb.indexOf("://") + 3, username + "@");
+		}
+		return sb.toString();
+	}
+	
+	protected String getGitDaemonUrl(HttpServletRequest request, UserModel user, RepositoryModel repository) {
+		if (gitDaemon != null) {
+			String bindInterface = settings.getString(Keys.git.daemonBindInterface, "localhost");
+			if (bindInterface.equals("localhost")
+					&& (!request.getServerName().equals("localhost") && !request.getServerName().equals("127.0.0.1"))) {
+				// git daemon is bound to localhost and the request is from elsewhere
+				return null;
+			}
+			if (user.canClone(repository)) {
+				String servername = request.getServerName();
+				String url = gitDaemon.formatUrl(servername, repository.name);
+				return url;
+			}
+		}
+		return null;
+	}
+	
+	protected AccessPermission getGitDaemonAccessPermission(UserModel user, RepositoryModel repository) {
+		if (gitDaemon != null && user.canClone(repository)) {
+			AccessPermission gitDaemonPermission = user.getRepositoryPermission(repository).permission;
+			if (gitDaemonPermission.atLeast(AccessPermission.CLONE)) {
+				if (repository.accessRestriction.atLeast(AccessRestrictionType.CLONE)) {
+					// can not authenticate clone via anonymous git protocol
+					gitDaemonPermission = AccessPermission.NONE;
+				} else if (repository.accessRestriction.atLeast(AccessRestrictionType.PUSH)) {
+					// can not authenticate push via anonymous git protocol
+					gitDaemonPermission = AccessPermission.CLONE;
+				} else {
+					// normal user permission
+				}
+			}
+			return gitDaemonPermission;
+		}
+		return AccessPermission.NONE;
+	}
+
+	/**
+	 * 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() {
+		// prefer user definitions, if they exist
+		File userDefs = new File(baseFolder, "clientapps.json");
+		if (userDefs.exists()) {
+			Date lastModified = new Date(userDefs.lastModified());
+			if (clientApplications.hasCurrent("user", lastModified)) {
+				return clientApplications.getObject("user");
+			} else {
+				// (re)load user definitions
+				try {
+					InputStream is = new FileInputStream(userDefs);
+					Collection<GitClientApplication> clients = readClientApplications(is);
+					is.close();
+					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 {
+				InputStream is = getClass().getResourceAsStream("/clientapps.json");
+				Collection<GitClientApplication> clients = readClientApplications(is);
+				is.close();
+				if (clients != null) {
+					clientApplications.updateObject("system", new Date(0), clients);
+				}
+			} catch (IOException e) {
+				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>>() {
+			}.getType();
+			InputStreamReader reader = new InputStreamReader(is);
+			Gson gson = JsonUtils.gson();
+			Collection<GitClientApplication> links = gson.fromJson(reader, type);
+			return links;
+		} catch (JsonIOException e) {
+			logger.error("Error deserializing client applications!", e);
+		} catch (JsonSyntaxException e) {
+			logger.error("Error deserializing client applications!", e);
+		}
+		return null;
+	}
+
+	/**
+	 * Set the user service. The user service authenticates all users and is
+	 * responsible for managing user permissions.
+	 * 
+	 * @param userService
+	 */
+	public void setUserService(IUserService userService) {
+		logger.info("Setting up user service " + userService.toString());
+		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
+	 */
+	public boolean supportsCredentialChanges(UserModel user) {
+		if (user == null) {
+			return false;
+		} else if (AccountType.LOCAL.equals(user.accountType)) {
+			// local account, we can change credentials
+			return true;
+		} else {
+			// external account, ask user service
+			return userService.supportsCredentialChanges();
+		}
+	}
+
+	/**
+	 * Returns true if the user's display name can be changed.
+	 * 
+	 * @param user
+	 * @return true if the user service supports display name changes
+	 */
+	public boolean supportsDisplayNameChanges(UserModel user) {
+		return (user != null && user.isLocalAccount()) || userService.supportsDisplayNameChanges();
+	}
+
+	/**
+	 * Returns true if the user's email address can be changed.
+	 * 
+	 * @param user
+	 * @return true if the user service supports email address changes
+	 */
+	public boolean supportsEmailAddressChanges(UserModel user) {
+		return (user != null && user.isLocalAccount()) || userService.supportsEmailAddressChanges();
+	}
+
+	/**
+	 * Returns true if the user's team memberships can be changed.
+	 * 
+	 * @param user
+	 * @return true if the user service supports team membership changes
+	 */
+	public boolean supportsTeamMembershipChanges(UserModel user) {
+		return (user != null && user.isLocalAccount()) || userService.supportsTeamMembershipChanges();
+	}
+
+	/**
+	 * Returns true if the username represents an internal account
+	 * 
+	 * @param username
+	 * @return true if the specified username represents an internal account
+	 */
+	protected boolean isInternalAccount(String username) {
+		return !StringUtils.isEmpty(username)
+				&& (username.equalsIgnoreCase(Constants.FEDERATION_USER)
+						|| username.equalsIgnoreCase(UserModel.ANONYMOUS.username));
+	}
+
+	/**
+	 * Authenticate a user based on a username and password.
+	 * 
+	 * @see IUserService.authenticate(String, char[])
+	 * @param username
+	 * @param password
+	 * @return a user object or null
+	 */
+	public UserModel authenticate(String username, char[] password) {
+		if (StringUtils.isEmpty(username)) {
+			// can not authenticate empty username
+			return null;
+		}
+		String usernameDecoded = decodeUsername(username);
+		String pw = new String(password);
+		if (StringUtils.isEmpty(pw)) {
+			// can not authenticate empty password
+			return null;
+		}
+
+		// check to see if this is the federation user
+		if (canFederate()) {
+			if (usernameDecoded.equalsIgnoreCase(Constants.FEDERATION_USER)) {
+				List<String> tokens = getFederationTokens();
+				if (tokens.contains(pw)) {
+					return getFederationUser();
+				}
+			}
+		}
+
+		// delegate authentication to the user service
+		if (userService == null) {
+			return null;
+		}
+		return userService.authenticate(usernameDecoded, password);
+	}
+
+	/**
+	 * Authenticate a user based on their cookie.
+	 * 
+	 * @param cookies
+	 * @return a user object or null
+	 */
+	protected UserModel authenticate(Cookie[] cookies) {
+		if (userService == null) {
+			return null;
+		}
+		if (userService.supportsCookies()) {
+			if (cookies != null && cookies.length > 0) {
+				for (Cookie cookie : cookies) {
+					if (cookie.getName().equals(Constants.NAME)) {
+						String value = cookie.getValue();
+						return userService.authenticate(value.toCharArray());
+					}
+				}
+			}
+		}
+		return null;
+	}
+
+	/**
+	 * 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
+	 */
+	public UserModel authenticate(HttpServletRequest httpRequest, boolean requiresCertificate) {
+		// try to authenticate by certificate
+		boolean checkValidity = settings.getBoolean(Keys.git.enforceCertificateValidity, true);
+		String [] oids = getStrings(Keys.git.certificateUsernameOIDs).toArray(new String[0]);
+		UserModel model = HttpUtils.getUserModelFromCertificate(httpRequest, checkValidity, oids);
+		if (model != null) {
+			// grab real user model and preserve certificate serial number
+			UserModel user = getUserModel(model.username);
+			X509Metadata metadata = HttpUtils.getCertificateMetadata(httpRequest);
+			if (user != null) {
+				flagWicketSession(AuthenticationType.CERTIFICATE);
+				logger.debug(MessageFormat.format("{0} authenticated by client certificate {1} from {2}",
+						user.username, metadata.serialNumber, httpRequest.getRemoteAddr()));
+				return user;
+			} else {
+				logger.warn(MessageFormat.format("Failed to find UserModel for {0}, attempted client certificate ({1}) authentication from {2}",
+						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) {
+			String username = principal.getName();
+			if (!StringUtils.isEmpty(username)) {
+				boolean internalAccount = isInternalAccount(username);
+				UserModel user = getUserModel(username);
+				if (user != null) {
+					// existing user
+					flagWicketSession(AuthenticationType.CONTAINER);
+					logger.debug(MessageFormat.format("{0} authenticated by servlet container principal from {1}",
+							user.username, httpRequest.getRemoteAddr()));
+					return user;
+				} else if (settings.getBoolean(Keys.realm.container.autoCreateAccounts, false)
+						&& !internalAccount) {
+					// auto-create user from an authenticated container principal
+					user = new UserModel(username.toLowerCase());
+					user.displayName = username;
+					user.password = Constants.EXTERNAL_ACCOUNT;
+					userService.updateUserModel(user);
+					flagWicketSession(AuthenticationType.CONTAINER);
+					logger.debug(MessageFormat.format("{0} authenticated and created by servlet container principal from {1}",
+							user.username, httpRequest.getRemoteAddr()));
+					return user;
+				} else if (!internalAccount) {
+					logger.warn(MessageFormat.format("Failed to find UserModel for {0}, attempted servlet container authentication from {1}",
+							principal.getName(), httpRequest.getRemoteAddr()));
+				}
+			}
+		}
+		
+		// try to authenticate by cookie
+		if (allowCookieAuthentication()) {
+			UserModel user = authenticate(httpRequest.getCookies());
+			if (user != null) {
+				flagWicketSession(AuthenticationType.COOKIE);
+				logger.debug(MessageFormat.format("{0} authenticated by cookie from {1}",
+						user.username, httpRequest.getRemoteAddr()));
+				return user;
+			}
+		}
+		
+		// try to authenticate by BASIC
+		final String authorization = httpRequest.getHeader("Authorization");
+		if (authorization != null && authorization.startsWith("Basic")) {
+			// Authorization: Basic base64credentials
+			String base64Credentials = authorization.substring("Basic".length()).trim();
+			String credentials = new String(Base64.decode(base64Credentials),
+					Charset.forName("UTF-8"));
+			// credentials = username:password
+			final String[] values = credentials.split(":",2);
+
+			if (values.length == 2) {
+				String username = values[0];
+				char[] password = values[1].toCharArray();
+				UserModel user = authenticate(username, password);
+				if (user != null) {
+					flagWicketSession(AuthenticationType.CREDENTIALS);
+					logger.debug(MessageFormat.format("{0} authenticated by BASIC request header from {1}",
+							user.username, httpRequest.getRemoteAddr()));
+					return user;
+				} else {
+					logger.warn(MessageFormat.format("Failed login attempt for {0}, invalid credentials ({1}) from {2}", 
+							username, credentials, httpRequest.getRemoteAddr()));
+				}
+			}
+		}
+		return null;
+	}
+	
+	protected void flagWicketSession(AuthenticationType authenticationType) {
+		RequestCycle requestCycle = RequestCycle.get();
+		if (requestCycle != null) {
+			// flag the Wicket session, if this is a Wicket request
+			GitBlitWebSession session = GitBlitWebSession.get();
+			session.authenticationType = authenticationType;
+		}
+	}
+
+	/**
+	 * Open a file resource using the Servlet container.
+	 * @param file to open
+	 * @return InputStream of the opened file
+	 * @throws ResourceStreamNotFoundException
+	 */
+	public InputStream getResourceAsStream(String file) throws ResourceStreamNotFoundException {
+		ContextRelativeResource res = WicketUtils.getResource(file);
+		return res.getResourceStream().getInputStream();
+	}
+
+	/**
+	 * Sets a cookie for the specified user.
+	 * 
+	 * @param response
+	 * @param user
+	 */
+	public void setCookie(WebResponse response, UserModel user) {
+		if (userService == null) {
+			return;
+		}
+		GitBlitWebSession session = GitBlitWebSession.get();
+		boolean standardLogin = session.authenticationType.isStandard();
+
+		if (userService.supportsCookies() && standardLogin) {
+			Cookie userCookie;
+			if (user == null) {
+				// clear cookie for logout
+				userCookie = new Cookie(Constants.NAME, "");
+			} else {
+				// set cookie for login
+				String cookie = userService.getCookie(user);
+				if (StringUtils.isEmpty(cookie)) {
+					// create empty cookie
+					userCookie = new Cookie(Constants.NAME, "");
+				} else {
+					// create real cookie
+					userCookie = new Cookie(Constants.NAME, cookie);
+					userCookie.setMaxAge(Integer.MAX_VALUE);
+				}
+			}
+			userCookie.setPath("/");
+			response.addCookie(userCookie);
+		}
+	}
+	
+	/**
+	 * Logout a user.
+	 * 
+	 * @param user
+	 */
+	public void logout(UserModel user) {
+		if (userService == null) {
+			return;
+		}
+		userService.logout(user);
+	}
+
+	/**
+	 * 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");	
+	}
+
+	/**
+	 * 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
+	 */
+	public List<String> getAllUsernames() {
+		List<String> names = new ArrayList<String>(userService.getAllUsernames());
+		return names;
+	}
+
+	/**
+	 * Returns the list of all users available to the login service.
+	 * 
+	 * @see IUserService.getAllUsernames()
+	 * @return list of all usernames
+	 */
+	public List<UserModel> getAllUsers() {
+		List<UserModel> users = userService.getAllUsers();
+		return users;
+	}
+
+	/**
+	 * Delete the user object with the specified username
+	 * 
+	 * @see IUserService.deleteUser(String)
+	 * @param username
+	 * @return true if successful
+	 */
+	public boolean deleteUser(String username) {
+		if (StringUtils.isEmpty(username)) {
+			return false;
+		}
+		String usernameDecoded = decodeUsername(username);
+		return userService.deleteUser(usernameDecoded);
+	}
+	
+	protected UserModel getFederationUser() {
+		// the federation user is an administrator
+		UserModel federationUser = new UserModel(Constants.FEDERATION_USER);
+		federationUser.canAdmin = true;
+		return federationUser;
+	}
+
+	/**
+	 * Retrieve the user object for the specified username.
+	 * 
+	 * @see IUserService.getUserModel(String)
+	 * @param username
+	 * @return a user object or null
+	 */
+	public UserModel getUserModel(String username) {
+		if (StringUtils.isEmpty(username)) {
+			return null;
+		}
+		String usernameDecoded = decodeUsername(username);
+		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
+	 */
+	public List<RegistrantAccessPermission> getUserAccessPermissions(UserModel user) {
+		if (StringUtils.isEmpty(user.username)) {
+			// new user
+			return new ArrayList<RegistrantAccessPermission>();
+		}
+		Set<RegistrantAccessPermission> set = new LinkedHashSet<RegistrantAccessPermission>();
+		set.addAll(user.getRepositoryPermissions());
+		// Flag missing repositories
+		for (RegistrantAccessPermission permission : set) {
+			if (permission.mutable && PermissionType.EXPLICIT.equals(permission.permissionType)) {
+				RepositoryModel rm = GitBlit.self().getRepositoryModel(permission.registrant);
+				if (rm == null) {
+					permission.permissionType = PermissionType.MISSING;
+					permission.mutable = false;
+					continue;
+				}
+			}
+		}
+
+		// TODO reconsider ownership as a user property
+		// manually specify personal repository ownerships
+		for (RepositoryModel rm : repositoryListCache.values()) {
+			if (rm.isUsersPersonalRepository(user.username) || rm.isOwner(user.username)) {
+				RegistrantAccessPermission rp = new RegistrantAccessPermission(rm.name, AccessPermission.REWIND,
+						PermissionType.OWNER, RegistrantType.REPOSITORY, null, false);
+				// user may be owner of a repository to which they've inherited
+				// a team permission, replace any existing perm with owner perm
+				set.remove(rp);
+				set.add(rp);
+			}
+		}
+		
+		List<RegistrantAccessPermission> list = new ArrayList<RegistrantAccessPermission>(set);
+		Collections.sort(list);
+		return list;
+	}
+
+	/**
+	 * 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
+	 */
+	public List<RegistrantAccessPermission> getUserAccessPermissions(RepositoryModel repository) {
+		List<RegistrantAccessPermission> list = new ArrayList<RegistrantAccessPermission>();
+		if (AccessRestrictionType.NONE.equals(repository.accessRestriction)) {
+			// no permissions needed, REWIND for everyone!
+			return list;
+		}
+		if (AuthorizationControl.AUTHENTICATED.equals(repository.authorizationControl)) {
+			// no permissions needed, REWIND for authenticated!
+			return list;
+		}
+		// NAMED users and teams
+		for (UserModel user : userService.getAllUsers()) {
+			RegistrantAccessPermission ap = user.getRepositoryPermission(repository);
+			if (ap.permission.exceeds(AccessPermission.NONE)) {
+				list.add(ap);
+			}
+		}
+		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
+	 */
+	public boolean setUserAccessPermissions(RepositoryModel repository, Collection<RegistrantAccessPermission> permissions) {
+		List<UserModel> users = new ArrayList<UserModel>();
+		for (RegistrantAccessPermission up : permissions) {
+			if (up.mutable) {
+				// only set editable defined permissions
+				UserModel user = userService.getUserModel(up.registrant);
+				user.setRepositoryPermission(repository.name, up.permission);
+				users.add(user);
+			}
+		}
+		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
+	 */
+	public List<String> getRepositoryUsers(RepositoryModel repository) {
+		return userService.getUsernamesForRepositoryRole(repository.name);
+	}
+
+	/**
+	 * 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
+	 * @return true if successful
+	 */
+	@Deprecated
+	public boolean setRepositoryUsers(RepositoryModel repository, List<String> repositoryUsers) {
+		// rejects all changes since 1.2.0 because this would elevate
+		// all discrete access permissions to RW+
+		return false;
+	}
+
+	/**
+	 * 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
+	 * @param isCreate
+	 * @throws GitBlitException
+	 */
+	public void updateUserModel(String username, UserModel user, boolean isCreate)
+			throws GitBlitException {
+		if (!username.equalsIgnoreCase(user.username)) {
+			if (userService.getUserModel(user.username) != null) {
+				throw new GitBlitException(MessageFormat.format(
+						"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)) {
+					// personal repository
+					model.addOwner(user.username);
+					String oldRepositoryName = model.name;
+					model.name = "~" + user.username + model.name.substring(model.projectPath.length());
+					model.projectPath = "~" + user.username;
+					updateRepositoryModel(oldRepositoryName, model, false);
+				} else if (model.isOwner(username)) {
+					// common/shared repo
+					model.addOwner(user.username);
+					updateRepositoryModel(model.name, model, false);
+				}
+			}
+		}
+		if (!userService.updateUserModel(username, user)) {
+			throw new GitBlitException(isCreate ? "Failed to add user!" : "Failed to update user!");
+		}
+	}
+
+	/**
+	 * Returns the list of available teams that a user or repository may be
+	 * assigned to.
+	 * 
+	 * @return the list of teams
+	 */
+	public List<String> getAllTeamnames() {
+		List<String> teams = new ArrayList<String>(userService.getAllTeamNames());
+		return teams;
+	}
+
+	/**
+	 * Returns the list of available teams that a user or repository may be
+	 * assigned to.
+	 * 
+	 * @return the list of teams
+	 */
+	public List<TeamModel> getAllTeams() {
+		List<TeamModel> teams = userService.getAllTeams();
+		return teams;
+	}
+
+	/**
+	 * 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
+	 */
+	public List<RegistrantAccessPermission> getTeamAccessPermissions(RepositoryModel repository) {
+		List<RegistrantAccessPermission> list = new ArrayList<RegistrantAccessPermission>();
+		for (TeamModel team : userService.getAllTeams()) {
+			RegistrantAccessPermission ap = team.getRepositoryPermission(repository);
+			if (ap.permission.exceeds(AccessPermission.NONE)) {
+				list.add(ap);
+			}
+		}
+		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
+	 */
+	public boolean setTeamAccessPermissions(RepositoryModel repository, Collection<RegistrantAccessPermission> permissions) {
+		List<TeamModel> teams = new ArrayList<TeamModel>();
+		for (RegistrantAccessPermission tp : permissions) {
+			if (tp.mutable) {
+				// only set explicitly defined access permissions
+				TeamModel team = userService.getTeamModel(tp.registrant);
+				team.setRepositoryPermission(repository.name, tp.permission);
+				teams.add(team);
+			}
+		}
+		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
+	 */
+	public List<String> getRepositoryTeams(RepositoryModel repository) {
+		return userService.getTeamnamesForRepositoryRole(repository.name);
+	}
+
+	/**
+	 * 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
+	 * @return true if successful
+	 */
+	@Deprecated
+	public boolean setRepositoryTeams(RepositoryModel repository, List<String> repositoryTeams) {
+		// rejects all changes since 1.2.0 because this would elevate
+		// all discrete access permissions to RW+
+		return false;
+	}
+
+	/**
+	 * Updates the TeamModel object for the specified name.
+	 * 
+	 * @param teamname
+	 * @param team
+	 * @param isCreate
+	 */
+	public void updateTeamModel(String teamname, TeamModel team, boolean isCreate)
+			throws GitBlitException {
+		if (!teamname.equalsIgnoreCase(team.name)) {
+			if (userService.getTeamModel(team.name) != null) {
+				throw new GitBlitException(MessageFormat.format(
+						"Failed to rename ''{0}'' because ''{1}'' already exists.", teamname,
+						team.name));
+			}
+		}
+		if (!userService.updateTeamModel(teamname, team)) {
+			throw new GitBlitException(isCreate ? "Failed to add team!" : "Failed to update team!");
+		}
+	}
+
+	/**
+	 * Delete the team object with the specified teamname
+	 * 
+	 * @see IUserService.deleteTeam(String)
+	 * @param teamname
+	 * @return true if successful
+	 */
+	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)) {
+					RepositoryModel origin = repositoryListCache.get(model.originRepository);
+					origin.addFork(model.name);
+				}
+			}
+		}
+	}
+	
+	/**
+	 * Removes the repository from the list of cached repositories.
+	 * 
+	 * @param name
+	 * @return the model being removed
+	 */
+	private RepositoryModel removeFromCachedRepositoryList(String name) {
+		if (StringUtils.isEmpty(name)) {
+			return null;
+		}
+		return repositoryListCache.remove(name.toLowerCase());
+	}
+
+	/**
+	 * 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
+	 */
+	private String getRepositoryListSettingsChecksum() {
+		StringBuilder ns = new StringBuilder();
+		ns.append(settings.getString(Keys.git.cacheRepositoryList, "")).append('\n');
+		ns.append(settings.getString(Keys.git.onlyAccessBareRepositories, "")).append('\n');
+		ns.append(settings.getString(Keys.git.searchRepositoriesSubfolders, "")).append('\n');
+		ns.append(settings.getString(Keys.git.searchRecursionDepth, "")).append('\n');
+		ns.append(settings.getString(Keys.git.searchExclusions, "")).append('\n');
+		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() {
+		String newChecksum = getRepositoryListSettingsChecksum();
+		boolean valid = newChecksum.equals(repositoryListSettingsChecksum.get());
+		repositoryListSettingsChecksum.set(newChecksum);
+		if (!valid && settings.getBoolean(Keys.git.cacheRepositoryList,  true)) {
+			logger.info("Repository list settings have changed. Clearing repository list cache.");
+			repositoryListCache.clear();
+		}
+		return valid;
+	}
+
+	/**
+	 * 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, 
+					settings.getBoolean(Keys.git.onlyAccessBareRepositories, false),
+					settings.getBoolean(Keys.git.searchRepositoriesSubfolders, true),
+					settings.getInteger(Keys.git.searchRecursionDepth, -1),
+					settings.getStrings(Keys.git.searchExclusions));
+
+			if (!settings.getBoolean(Keys.git.cacheRepositoryList,  true)) {
+				// we are not caching
+				StringUtils.sortRepositorynames(repositories);
+				return repositories;
+			} else {
+				// we are caching this list
+				String msg = "{0} repositories identified in {1} msecs";
+
+				// optionally (re)calculate repository sizes
+				if (getBoolean(Keys.web.showRepositorySizes, true)) {
+					ByteFormat byteFormat = new ByteFormat();
+					msg = "{0} repositories identified with calculated folder sizes in {1} msecs";
+					for (String repository : repositories) {
+						RepositoryModel model = getRepositoryModel(repository);
+						if (!model.skipSizeCalculation) {
+							model.size = byteFormat.format(calculateSize(model));
+						}
+					}
+				} else {
+					// update cache
+					for (String repository : repositories) {
+						getRepositoryModel(repository);
+					}
+				}
+				
+				// rebuild fork networks
+				for (RepositoryModel model : repositoryListCache.values()) {
+					if (!StringUtils.isEmpty(model.originRepository)) {
+						if (repositoryListCache.containsKey(model.originRepository)) {
+							RepositoryModel origin = repositoryListCache.get(model.originRepository);
+							origin.addFork(model.name);
+						}
+					}
+				}
+				
+				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()) {
+			list.add(model.name);
+		}
+		StringUtils.sortRepositorynames(list);
+		return list;
+	}
+
+	/**
+	 * Returns the JGit repository for the specified name.
+	 * 
+	 * @param repositoryName
+	 * @return repository or null
+	 */
+	public Repository getRepository(String repositoryName) {
+		return getRepository(repositoryName, true);
+	}
+
+	/**
+	 * Returns the JGit repository for the specified name.
+	 * 
+	 * @param repositoryName
+	 * @param logError
+	 * @return repository or null
+	 */
+	public Repository getRepository(String repositoryName, boolean logError) {
+		if (isCollectingGarbage(repositoryName)) {
+			logger.warn(MessageFormat.format("Rejecting request for {0}, busy collecting garbage!", repositoryName));
+			return null;
+		}
+
+		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);
+			r = RepositoryCache.open(key, true);
+		} catch (IOException e) {
+			if (logError) {
+				logger.error("GitBlit.getRepository(String) failed to find "
+						+ new File(repositoriesFolder, repositoryName).getAbsolutePath());
+			}
+		}
+		return r;
+	}
+
+	/**
+	 * Returns the list of repository models that are accessible to the user.
+	 * 
+	 * @param user
+	 * @return list of repository models accessible to user
+	 */
+	public List<RepositoryModel> getRepositoryModels(UserModel user) {
+		long methodStart = System.currentTimeMillis();
+		List<String> list = getRepositoryList();
+		List<RepositoryModel> repositories = new ArrayList<RepositoryModel>();
+		for (String repo : list) {
+			RepositoryModel model = getRepositoryModel(user, repo);
+			if (model != null) {
+				if (!model.hasCommits) {
+					// only add empty repositories that user can push to
+					if (UserModel.ANONYMOUS.canPush(model)
+							|| user != null && user.canPush(model)) {
+						repositories.add(model);
+					}
+				} else {
+					repositories.add(model);
+				}
+			}
+		}
+		if (getBoolean(Keys.web.showRepositorySizes, true)) {
+			int repoCount = 0;
+			long startTime = System.currentTimeMillis();
+			ByteFormat byteFormat = new ByteFormat();
+			for (RepositoryModel model : repositories) {
+				if (!model.skipSizeCalculation) {
+					repoCount++;
+					model.size = byteFormat.format(calculateSize(model));
+				}
+			}
+			long duration = System.currentTimeMillis() - startTime;
+			if (duration > 250) {
+				// only log calcualtion time if > 250 msecs
+				logger.info(MessageFormat.format("{0} repository sizes calculated in {1} msecs",
+					repoCount, duration));
+			}
+		}
+		long duration = System.currentTimeMillis() - methodStart;
+		logger.info(MessageFormat.format("{0} repository models loaded for {1} in {2} msecs",
+				repositories.size(), user == null ? "anonymous" : user.username, duration));
+		return repositories;
+	}
+
+	/**
+	 * Returns a repository model if the repository exists and the user may
+	 * access the repository.
+	 * 
+	 * @param user
+	 * @param repositoryName
+	 * @return repository model or null
+	 */
+	public RepositoryModel getRepositoryModel(UserModel user, String repositoryName) {
+		RepositoryModel model = getRepositoryModel(repositoryName);
+		if (model == null) {
+			return null;
+		}
+		if (user == null) {
+			user = UserModel.ANONYMOUS;
+		}
+		if (user.canView(model)) {
+			return model;
+		}
+		return null;
+	}
+
+	/**
+	 * Returns the repository model for the specified repository. This method
+	 * does not consider user access permissions.
+	 * 
+	 * @param repositoryName
+	 * @return repository model or null
+	 */
+	public RepositoryModel getRepositoryModel(String repositoryName) {
+		if (!repositoryListCache.containsKey(repositoryName)) {
+			RepositoryModel model = loadRepositoryModel(repositoryName);
+			if (model == null) {
+				return null;
+			}
+			addToCachedRepositoryList(model);
+			return model;
+		}
+		
+		// cached model
+		RepositoryModel model = repositoryListCache.get(repositoryName.toLowerCase());
+
+		if (gcExecutor.isCollectingGarbage(model.name)) {
+			// Gitblit is busy collecting garbage, use our cached model
+			RepositoryModel rm = DeepCopier.copy(model);
+			rm.isCollectingGarbage = true;
+			return rm;
+		}
+
+		// check for updates
+		Repository r = getRepository(model.name);
+		if (r == null) {
+			// repository is missing
+			removeFromCachedRepositoryList(repositoryName);
+			logger.error(MessageFormat.format("Repository \"{0}\" is missing! Removing from cache.", repositoryName));
+			return null;
+		}
+		
+		FileBasedConfig config = (FileBasedConfig) getRepositoryConfig(r);
+		if (config.isOutdated()) {
+			// reload model
+			logger.debug(MessageFormat.format("Config for \"{0}\" has changed. Reloading model and updating cache.", repositoryName));
+			model = loadRepositoryModel(model.name);
+			removeFromCachedRepositoryList(model.name);
+			addToCachedRepositoryList(model);
+		} else {
+			// update a few repository parameters 
+			if (!model.hasCommits) {
+				// update hasCommits, assume a repository only gains commits :)
+				model.hasCommits = JGitUtils.hasCommits(r);
+			}
+
+			LastChange lc = JGitUtils.getLastChange(r);
+			model.lastChange = lc.when;
+			model.lastChangeAuthor = lc.who;
+			if (!model.skipSizeCalculation) {
+				ByteFormat byteFormat = new ByteFormat();
+				model.size = byteFormat.format(calculateSize(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
+	 */
+	public long getStarCount(RepositoryModel repository) {
+		long count = 0;
+		for (UserModel user : getAllUsers()) {
+			if (user.getPreferences().isStarredRepository(repository.name)) {
+				count++;
+			}
+		}
+		return count;
+	}
+	
+	private void reloadProjectMarkdown(ProjectModel project) {
+		// project markdown
+		File pmkd = new File(getRepositoriesFolder(), (project.isRoot ? "" : project.name) + "/project.mkd");
+		if (pmkd.exists()) {
+			Date lm = new Date(pmkd.lastModified());
+			if (!projectMarkdownCache.hasCurrent(project.name, lm)) {
+				String mkd = com.gitblit.utils.FileUtils.readContent(pmkd,  "\n");
+				projectMarkdownCache.updateObject(project.name, lm, mkd);
+			}
+			project.projectMarkdown = projectMarkdownCache.getObject(project.name);
+		}
+		
+		// project repositories markdown
+		File rmkd = new File(getRepositoriesFolder(), (project.isRoot ? "" : project.name) + "/repositories.mkd");
+		if (rmkd.exists()) {
+			Date lm = new Date(rmkd.lastModified());
+			if (!projectRepositoriesMarkdownCache.hasCurrent(project.name, lm)) {
+				String mkd = com.gitblit.utils.FileUtils.readContent(rmkd,  "\n");
+				projectRepositoriesMarkdownCache.updateObject(project.name, lm, mkd);
+			}
+			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) {
+			}
+
+			// project configs
+			String rootName = GitBlit.getString(Keys.web.repositoryRootGroupName, "main");
+			ProjectModel rootProject = new ProjectModel(rootName, true);
+
+			Map<String, ProjectModel> configs = new HashMap<String, ProjectModel>();
+			// cache the root project under its alias and an empty path
+			configs.put("", rootProject);
+			configs.put(rootProject.name.toLowerCase(), rootProject);
+
+			for (String name : projectConfigs.getSubsections("project")) {
+				ProjectModel project;
+				if (name.equalsIgnoreCase(rootName)) {
+					project = rootProject;
+				} else {
+					project = new ProjectModel(name);
+				}
+				project.title = projectConfigs.getString("project", name, "title");
+				project.description = projectConfigs.getString("project", name, "description");
+				
+				reloadProjectMarkdown(project);
+				
+				configs.put(name.toLowerCase(), project);
+			}
+			projectCache.clear();
+			projectCache.putAll(configs);
+		}
+		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
+	 */
+	public List<ProjectModel> getProjectModels(UserModel user, boolean includeUsers) {
+		Map<String, ProjectModel> configs = getProjectConfigs();
+
+		// per-user project lists, this accounts for security and visibility
+		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();			
+			if (!map.containsKey(rootPath)) {
+				ProjectModel project;
+				if (configs.containsKey(rootPath)) {
+					// clone the project model because it's repository list will
+					// be tailored for the requesting user
+					project = DeepCopier.copy(configs.get(rootPath));
+				} else {
+					project = new ProjectModel(rootPath);
+				}
+				map.put(rootPath, project);
+			}
+			map.get(rootPath).addRepository(model);
+		}
+		
+		// sort projects, root project first
+		List<ProjectModel> projects;
+		if (includeUsers) {
+			// all projects
+			projects = new ArrayList<ProjectModel>(map.values());
+			Collections.sort(projects);
+			projects.remove(map.get(""));
+			projects.add(0, map.get(""));
+		} else {
+			// all non-user projects
+			projects = new ArrayList<ProjectModel>();
+			ProjectModel root = map.remove("");
+			for (ProjectModel model : map.values()) {
+				if (!model.isUserProject()) {
+					projects.add(model);
+				}
+			}
+			Collections.sort(projects);
+			projects.add(0, root);
+		}
+		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
+	 */
+	public ProjectModel getProjectModel(String name, UserModel user) {
+		for (ProjectModel project : getProjectModels(user, true)) {
+			if (project.name.equalsIgnoreCase(name)) {
+				return project;
+			}
+		}
+		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
+	 */
+	public ProjectModel getProjectModel(String name) {
+		Map<String, ProjectModel> configs = getProjectConfigs();
+		ProjectModel project = configs.get(name.toLowerCase());
+		if (project == null) {
+			project = new ProjectModel(name);
+			if (name.length() > 0 && name.charAt(0) == '~') {
+				UserModel user = getUserModel(name.substring(1));
+				if (user != null) {
+					project.title = user.getDisplayName();
+					project.description = "personal repositories";
+				}
+			}
+		} else {
+			// clone the object
+			project = DeepCopier.copy(project);
+		}
+		if (StringUtils.isEmpty(name)) {
+			// get root repositories
+			for (String repository : getRepositoryList()) {
+				if (repository.indexOf('/') == -1) {
+					project.addRepository(repository);
+				}
+			}
+		} else {
+			// get repositories in subfolder
+			String folder = name.toLowerCase() + "/";
+			for (String repository : getRepositoryList()) {
+				if (repository.toLowerCase().startsWith(folder)) {
+					project.addRepository(repository);
+				}
+			}
+		}
+		if (project.repositories.size() == 0) {
+			// 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
+	 */
+	public List<ProjectModel> getProjectModels(List<RepositoryModel> repositoryModels, boolean includeUsers) {
+		Map<String, ProjectModel> projects = new LinkedHashMap<String, ProjectModel>();
+		for (RepositoryModel repository : repositoryModels) {
+			if (!includeUsers && repository.isPersonalRepository()) {
+				// exclude personal repositories
+				continue;
+			}
+			if (!projects.containsKey(repository.projectPath)) {
+				ProjectModel project = getProjectModel(repository.projectPath);
+				if (project == null) {
+					logger.warn(MessageFormat.format("excluding project \"{0}\" from project list because it is empty!",
+							repository.projectPath));
+					continue;
+				}
+				projects.put(repository.projectPath, project);
+				// clear the repo list in the project because that is the system
+				// list, not the user-accessible list and start building the
+				// user-accessible list
+				project.repositories.clear();
+				project.repositories.add(repository.name);
+				project.lastChange = repository.lastChange;
+			} else {
+				// update the user-accessible list
+				// this is used for repository count
+				ProjectModel project = projects.get(repository.projectPath);
+				project.repositories.add(repository.name);
+				if (project.lastChange.before(repository.lastChange)) {
+					project.lastChange = repository.lastChange;
+				}
+			}
+		}
+		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.
+	 * If I use the stock JGit method to get the config it already reloads the
+	 * 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
+	 */
+	private StoredConfig getRepositoryConfig(Repository r) {
+		try {
+			Field f = r.getClass().getDeclaredField("repoConfig");
+			f.setAccessible(true);
+			StoredConfig config = (StoredConfig) f.get(r);
+			return config;
+		} catch (Exception e) {
+			logger.error("Failed to retrieve \"repoConfig\" via reflection", e);
+		}
+		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
+	 */
+	private RepositoryModel loadRepositoryModel(String repositoryName) {
+		Repository r = getRepository(repositoryName);
+		if (r == null) {
+			return null;
+		}
+		RepositoryModel model = new RepositoryModel();
+		model.isBare = r.isBare();
+		File basePath = getFileOrFolder(Keys.git.repositoriesFolder, "${baseFolder}/git");
+		if (model.isBare) {
+			model.name = com.gitblit.utils.FileUtils.getRelativePath(basePath, r.getDirectory());
+		} else {
+			model.name = com.gitblit.utils.FileUtils.getRelativePath(basePath, r.getDirectory().getParentFile());
+		}
+		if (StringUtils.isEmpty(model.name)) {
+			// Repository is NOT located relative to the base folder because it
+			// is symlinked.  Use the provided repository name.
+			model.name = repositoryName;
+		}
+		model.hasCommits = JGitUtils.hasCommits(r);
+		LastChange lc = JGitUtils.getLastChange(r);
+		model.lastChange = lc.when;
+		model.lastChangeAuthor = lc.who;
+		model.projectPath = StringUtils.getFirstPathElement(repositoryName);
+		
+		StoredConfig config = r.getConfig();
+		boolean hasOrigin = !StringUtils.isEmpty(config.getString("remote", "origin", "url"));
+		
+		if (config != null) {
+			model.description = getConfig(config, "description", "");
+			model.originRepository = getConfig(config, "originRepository", null);
+			model.addOwners(ArrayUtils.fromString(getConfig(config, "owner", "")));
+			model.useTickets = getConfig(config, "useTickets", false);
+			model.useDocs = getConfig(config, "useDocs", false);
+			model.useIncrementalPushTags = getConfig(config, "useIncrementalPushTags", false);
+			model.incrementalPushTagPrefix = getConfig(config, "incrementalPushTagPrefix", null);
+			model.allowForks = getConfig(config, "allowForks", true);
+			model.accessRestriction = AccessRestrictionType.fromName(getConfig(config,
+					"accessRestriction", settings.getString(Keys.git.defaultAccessRestriction, null)));
+			model.authorizationControl = AuthorizationControl.fromName(getConfig(config,
+					"authorizationControl", settings.getString(Keys.git.defaultAuthorizationControl, null)));
+			model.verifyCommitter = getConfig(config, "verifyCommitter", false);
+			model.showRemoteBranches = getConfig(config, "showRemoteBranches", hasOrigin);
+			model.isFrozen = getConfig(config, "isFrozen", false);
+			model.showReadme = getConfig(config, "showReadme", false);
+			model.skipSizeCalculation = getConfig(config, "skipSizeCalculation", false);
+			model.skipSummaryMetrics = getConfig(config, "skipSummaryMetrics", false);
+			model.federationStrategy = FederationStrategy.fromName(getConfig(config,
+					"federationStrategy", null));
+			model.federationSets = new ArrayList<String>(Arrays.asList(config.getStringList(
+					Constants.CONFIG_GITBLIT, null, "federationSets")));
+			model.isFederated = getConfig(config, "isFederated", false);
+			model.gcThreshold = getConfig(config, "gcThreshold", settings.getString(Keys.git.defaultGarbageCollectionThreshold, "500KB"));
+			model.gcPeriod = getConfig(config, "gcPeriod", settings.getInteger(Keys.git.defaultGarbageCollectionPeriod, 7));
+			try {
+				model.lastGC = new SimpleDateFormat(Constants.ISO8601).parse(getConfig(config, "lastGC", "1970-01-01'T'00:00:00Z"));
+			} catch (Exception e) {
+				model.lastGC = new Date(0);
+			}
+			model.maxActivityCommits = getConfig(config, "maxActivityCommits", settings.getInteger(Keys.web.maxActivityCommits, 0));
+			model.origin = config.getString("remote", "origin", "url");
+			if (model.origin != null) {
+				model.origin = model.origin.replace('\\', '/');
+			}
+			model.preReceiveScripts = new ArrayList<String>(Arrays.asList(config.getStringList(
+					Constants.CONFIG_GITBLIT, null, "preReceiveScript")));
+			model.postReceiveScripts = new ArrayList<String>(Arrays.asList(config.getStringList(
+					Constants.CONFIG_GITBLIT, null, "postReceiveScript")));
+			model.mailingLists = new ArrayList<String>(Arrays.asList(config.getStringList(
+					Constants.CONFIG_GITBLIT, null, "mailingList")));
+			model.indexedBranches = new ArrayList<String>(Arrays.asList(config.getStringList(
+					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)) {
+				model.customFields.put(aProperty, config.getString(Constants.CONFIG_GITBLIT, Constants.CONFIG_CUSTOM_FIELDS, aProperty));
+			}
+		}
+		model.HEAD = JGitUtils.getHEADRef(r);
+		model.availableRefs = JGitUtils.getAvailableHeadTargets(r);
+		model.sparkleshareId = JGitUtils.getSparkleshareId(r);
+		r.close();
+		
+		if (StringUtils.isEmpty(model.originRepository) && model.origin != null && model.origin.startsWith("file://")) {
+			// repository was cloned locally... perhaps as a fork
+			try {
+				File folder = new File(new URI(model.origin));
+				String originRepo = com.gitblit.utils.FileUtils.getRelativePath(getRepositoriesFolder(), folder);
+				if (!StringUtils.isEmpty(originRepo)) {
+					// ensure origin still exists
+					File repoFolder = new File(getRepositoriesFolder(), originRepo);
+					if (repoFolder.exists()) {
+						model.originRepository = originRepo.toLowerCase();
+						
+						// persist the fork origin
+						updateConfiguration(r, model);
+					}
+				}
+			} catch (URISyntaxException e) {
+				logger.error("Failed to determine fork for " + model, e);
+			}
+		}
+		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
+	 */
+	public boolean hasRepository(String repositoryName, boolean caseSensitiveCheck) {
+		if (!caseSensitiveCheck && settings.getBoolean(Keys.git.cacheRepositoryList, true)) {
+			// if we are caching use the cache to determine availability
+			// otherwise we end up adding a phantom repository to the cache
+			return repositoryListCache.containsKey(repositoryName.toLowerCase());
+		}		
+		Repository r = getRepository(repositoryName, false);
+		if (r == null) {
+			return false;
+		}
+		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
+	 */
+	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
+	 */
+	public String getFork(String username, String origin) {
+		String userProject = "~" + username.toLowerCase();
+		if (settings.getBoolean(Keys.git.cacheRepositoryList, true)) {
+			String userPath = userProject + "/";
+
+			// collect all origin nodes in fork network
+			Set<String> roots = new HashSet<String>();
+			roots.add(origin);
+			RepositoryModel originModel = repositoryListCache.get(origin);
+			while (originModel != null) {
+				if (!ArrayUtils.isEmpty(originModel.forks)) {
+					for (String fork : originModel.forks) {
+						if (!fork.startsWith(userPath)) {
+							roots.add(fork);
+						}
+					}
+				}
+				
+				if (originModel.originRepository != null) {
+					roots.add(originModel.originRepository);
+					originModel = repositoryListCache.get(originModel.originRepository);
+				} else {
+					// break
+					originModel = null;
+				}
+			}
+			
+			for (String repository : repositoryListCache.keySet()) {
+				if (repository.startsWith(userPath)) {
+					RepositoryModel model = repositoryListCache.get(repository);
+					if (!StringUtils.isEmpty(model.originRepository)) {
+						if (roots.contains(model.originRepository)) {
+							// user has a fork in this graph
+							return model.name;
+						}
+					}
+				}
+			}
+		} else {
+			// not caching
+			ProjectModel project = getProjectModel(userProject);
+			if (project == null) {
+				return null;
+			}
+			for (String repository : project.repositories) {
+				if (repository.startsWith(userProject)) {
+					RepositoryModel model = getRepositoryModel(repository);
+					if (model.originRepository.equalsIgnoreCase(origin)) {
+						// user has a fork
+						return model.name;
+					}
+				}
+			}
+		}
+		// 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
+	 */
+	public ForkModel getForkNetwork(String repository) {
+		if (settings.getBoolean(Keys.git.cacheRepositoryList, true)) {
+			// find the root, cached
+			RepositoryModel model = repositoryListCache.get(repository.toLowerCase());
+			while (model.originRepository != null) {
+				model = repositoryListCache.get(model.originRepository);
+			}
+			ForkModel root = getForkModelFromCache(model.name);
+			return root;
+		} else {
+			// find the root, non-cached
+			RepositoryModel model = getRepositoryModel(repository.toLowerCase());
+			while (model.originRepository != null) {
+				model = getRepositoryModel(model.originRepository);
+			}
+			ForkModel root = getForkModel(model.name);
+			return root;
+		}
+	}
+	
+	private ForkModel getForkModelFromCache(String repository) {
+		RepositoryModel model = repositoryListCache.get(repository.toLowerCase());
+		if (model == null) {
+			return null;
+		}
+		ForkModel fork = new ForkModel(model);
+		if (!ArrayUtils.isEmpty(model.forks)) {
+			for (String aFork : model.forks) {
+				ForkModel fm = getForkModelFromCache(aFork);
+				if (fm != null) {
+					fork.forks.add(fm);
+				}
+			}
+		}
+		return fork;
+	}
+	
+	private ForkModel getForkModel(String repository) {
+		RepositoryModel model = getRepositoryModel(repository.toLowerCase());
+		if (model == null) {
+			return null;
+		}
+		ForkModel fork = new ForkModel(model);
+		if (!ArrayUtils.isEmpty(model.forks)) {
+			for (String aFork : model.forks) {
+				ForkModel fm = getForkModel(aFork);
+				if (fm != null) {
+					fork.forks.add(fm);
+				}
+			}
+		}
+		return fork;
+	}
+
+	/**
+	 * Returns the size in bytes of the 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
+	 */
+	public long calculateSize(RepositoryModel model) {
+		if (repositorySizeCache.hasCurrent(model.name, model.lastChange)) {
+			return repositorySizeCache.getObject(model.name);
+		}
+		File gitDir = FileKey.resolve(new File(repositoriesFolder, model.name), FS.DETECTED);
+		long size = com.gitblit.utils.FileUtils.folderSize(gitDir);
+		repositorySizeCache.updateObject(model.name, model.lastChange, size);
+		return size;
+	}
+
+	/**
+	 * Ensure that a cached repository is completely closed and its resources
+	 * are properly released.
+	 * 
+	 * @param repositoryName
+	 */
+	private void closeRepository(String repositoryName) {
+		Repository repository = getRepository(repositoryName);
+		if (repository == null) {
+			return;
+		}
+		RepositoryCache.close(repository);
+
+		// assume 2 uses in case reflection fails
+		int uses = 2;
+		try {
+			// The FileResolver caches repositories which is very useful
+			// for performance until you want to delete a repository.
+			// I have to use reflection to call close() the correct
+			// number of times to ensure that the object and ref databases
+			// are properly closed before I can delete the repository from
+			// the filesystem.
+			Field useCnt = Repository.class.getDeclaredField("useCnt");
+			useCnt.setAccessible(true);
+			uses = ((AtomicInteger) useCnt.get(repository)).get();
+		} catch (Exception e) {
+			logger.warn(MessageFormat
+					.format("Failed to reflectively determine use count for repository {0}",
+							repositoryName), e);
+		}
+		if (uses > 0) {
+			logger.info(MessageFormat
+					.format("{0}.useCnt={1}, calling close() {2} time(s) to close object and ref databases",
+							repositoryName, uses, uses));
+			for (int i = 0; i < uses; i++) {
+				repository.close();
+			}
+		}
+		
+		// close any open index writer/searcher in the Lucene executor
+		luceneExecutor.close(repositoryName);
+	}
+
+	/**
+	 * Returns the metrics for the default branch of the specified repository.
+	 * 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
+	 */
+	public List<Metric> getRepositoryDefaultMetrics(RepositoryModel model, Repository repository) {
+		if (repositoryMetricsCache.hasCurrent(model.name, model.lastChange)) {
+			return new ArrayList<Metric>(repositoryMetricsCache.getObject(model.name));
+		}
+		List<Metric> metrics = MetricUtils.getDateMetrics(repository, null, true, null, getTimezone());
+		repositoryMetricsCache.updateObject(model.name, model.lastChange, metrics);
+		return new ArrayList<Metric>(metrics);
+	}
+
+	/**
+	 * Returns the gitblit string value for the specified key. If key is not
+	 * set, returns defaultValue.
+	 * 
+	 * @param config
+	 * @param field
+	 * @param defaultValue
+	 * @return field value or defaultValue
+	 */
+	private String getConfig(StoredConfig config, String field, String defaultValue) {
+		String value = config.getString(Constants.CONFIG_GITBLIT, null, field);
+		if (StringUtils.isEmpty(value)) {
+			return defaultValue;
+		}
+		return value;
+	}
+
+	/**
+	 * Returns the gitblit boolean value for the specified key. If key is not
+	 * set, returns defaultValue.
+	 * 
+	 * @param config
+	 * @param field
+	 * @param defaultValue
+	 * @return field value or defaultValue
+	 */
+	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
+	 * @return field value or defaultValue
+	 */
+	private int getConfig(StoredConfig config, String field, int defaultValue) {
+		String value = config.getString(Constants.CONFIG_GITBLIT, null, field);
+		if (StringUtils.isEmpty(value)) {
+			return defaultValue;
+		}
+		try {
+			return Integer.parseInt(value);
+		} catch (Exception e) {
+		}
+		return defaultValue;
+	}
+
+	/**
+	 * 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
+	 * @throws GitBlitException
+	 */
+	public void updateRepositoryModel(String repositoryName, RepositoryModel repository,
+			boolean isCreate) throws GitBlitException {
+		if (gcExecutor.isCollectingGarbage(repositoryName)) {
+			throw new GitBlitException(MessageFormat.format("sorry, Gitblit is busy collecting garbage in {0}",
+					repositoryName));
+		}
+		Repository r = null;
+		String projectPath = StringUtils.getFirstPathElement(repository.name);
+		if (!StringUtils.isEmpty(projectPath)) {
+			if (projectPath.equalsIgnoreCase(getString(Keys.web.repositoryRootGroupName, "main"))) {
+				// strip leading group name
+				repository.name = repository.name.substring(projectPath.length() + 1);
+			}
+		}
+		if (isCreate) {
+			// ensure created repository name ends with .git
+			if (!repository.name.toLowerCase().endsWith(org.eclipse.jgit.lib.Constants.DOT_GIT_EXT)) {
+				repository.name += org.eclipse.jgit.lib.Constants.DOT_GIT_EXT;
+			}
+			if (hasRepository(repository.name)) {
+				throw new GitBlitException(MessageFormat.format(
+						"Can not create repository ''{0}'' because it already exists.",
+						repository.name));
+			}
+			// create repository
+			logger.info("create repository " + repository.name);
+			r = JGitUtils.createRepository(repositoriesFolder, repository.name);
+		} else {
+			// rename repository
+			if (!repositoryName.equalsIgnoreCase(repository.name)) {
+				if (!repository.name.toLowerCase().endsWith(
+						org.eclipse.jgit.lib.Constants.DOT_GIT_EXT)) {
+					repository.name += org.eclipse.jgit.lib.Constants.DOT_GIT_EXT;
+				}
+				if (new File(repositoriesFolder, repository.name).exists()) {
+					throw new GitBlitException(MessageFormat.format(
+							"Failed to rename ''{0}'' because ''{1}'' already exists.",
+							repositoryName, repository.name));
+				}
+				closeRepository(repositoryName);
+				File folder = new File(repositoriesFolder, repositoryName);
+				File destFolder = new File(repositoriesFolder, repository.name);
+				if (destFolder.exists()) {
+					throw new GitBlitException(
+							MessageFormat
+									.format("Can not rename repository ''{0}'' to ''{1}'' because ''{1}'' already exists.",
+											repositoryName, repository.name));
+				}
+				File parentFile = destFolder.getParentFile();
+				if (!parentFile.exists() && !parentFile.mkdirs()) {
+					throw new GitBlitException(MessageFormat.format(
+							"Failed to create folder ''{0}''", parentFile.getAbsolutePath()));
+				}
+				if (!folder.renameTo(destFolder)) {
+					throw new GitBlitException(MessageFormat.format(
+							"Failed to rename repository ''{0}'' to ''{1}''.", repositoryName,
+							repository.name));
+				}
+				// rename the roles
+				if (!userService.renameRepositoryRole(repositoryName, repository.name)) {
+					throw new GitBlitException(MessageFormat.format(
+							"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) {
+						Repository rf = getRepository(fork);
+						try {
+							StoredConfig config = rf.getConfig();
+							String origin = config.getString("remote", "origin", "url");
+							origin = origin.replace(repositoryName, repository.name);
+							config.setString("remote", "origin", "url", origin);
+							config.setString(Constants.CONFIG_GITBLIT, null, "originRepository", repository.name);
+							config.save();
+						} catch (Exception e) {
+							logger.error("Failed to update repository fork config for " + fork, e);
+						}
+						rf.close();
+					}
+				}
+				
+				// update this repository's origin's fork list
+				if (!StringUtils.isEmpty(repository.originRepository)) {
+					RepositoryModel origin = repositoryListCache.get(repository.originRepository);
+					if (origin != null && !ArrayUtils.isEmpty(origin.forks)) {
+						origin.forks.remove(repositoryName);
+						origin.forks.add(repository.name);
+					}
+				}
+
+				// clear the cache
+				clearRepositoryMetadataCache(repositoryName);
+				repository.resetDisplayName();
+			}
+
+			// load repository
+			logger.info("edit repository " + repository.name);
+			r = getRepository(repository.name);
+		}
+
+		// update settings
+		if (r != null) {
+			updateConfiguration(r, repository);
+			// 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}", 
+						repository.name, currentRef, repository.HEAD));
+				if (JGitUtils.setHEADtoRef(r, repository.HEAD)) {
+					// clear the cache
+					clearRepositoryMetadataCache(repository.name);
+				}
+			}
+
+			// close the repository object
+			r.close();
+		}
+		
+		// update repository cache
+		removeFromCachedRepositoryList(repositoryName);
+		// 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
+	 *            the Gitblit repository model
+	 */
+	public void updateConfiguration(Repository r, RepositoryModel repository) {
+		StoredConfig config = r.getConfig();
+		config.setString(Constants.CONFIG_GITBLIT, null, "description", repository.description);
+		config.setString(Constants.CONFIG_GITBLIT, null, "originRepository", repository.originRepository);
+		config.setString(Constants.CONFIG_GITBLIT, null, "owner", ArrayUtils.toString(repository.owners));
+		config.setBoolean(Constants.CONFIG_GITBLIT, null, "useTickets", repository.useTickets);
+		config.setBoolean(Constants.CONFIG_GITBLIT, null, "useDocs", repository.useDocs);
+		config.setBoolean(Constants.CONFIG_GITBLIT, null, "useIncrementalPushTags", repository.useIncrementalPushTags);
+		if (StringUtils.isEmpty(repository.incrementalPushTagPrefix) ||
+				repository.incrementalPushTagPrefix.equals(settings.getString(Keys.git.defaultIncrementalPushTagPrefix, "r"))) {
+			config.unset(Constants.CONFIG_GITBLIT, null, "incrementalPushTagPrefix");
+		} else {
+			config.setString(Constants.CONFIG_GITBLIT, null, "incrementalPushTagPrefix", repository.incrementalPushTagPrefix);
+		}
+		config.setBoolean(Constants.CONFIG_GITBLIT, null, "allowForks", repository.allowForks);
+		config.setString(Constants.CONFIG_GITBLIT, null, "accessRestriction", repository.accessRestriction.name());
+		config.setString(Constants.CONFIG_GITBLIT, null, "authorizationControl", repository.authorizationControl.name());
+		config.setBoolean(Constants.CONFIG_GITBLIT, null, "verifyCommitter", repository.verifyCommitter);
+		config.setBoolean(Constants.CONFIG_GITBLIT, null, "showRemoteBranches", repository.showRemoteBranches);
+		config.setBoolean(Constants.CONFIG_GITBLIT, null, "isFrozen", repository.isFrozen);
+		config.setBoolean(Constants.CONFIG_GITBLIT, null, "showReadme", repository.showReadme);
+		config.setBoolean(Constants.CONFIG_GITBLIT, null, "skipSizeCalculation", repository.skipSizeCalculation);
+		config.setBoolean(Constants.CONFIG_GITBLIT, null, "skipSummaryMetrics", repository.skipSummaryMetrics);
+		config.setString(Constants.CONFIG_GITBLIT, null, "federationStrategy",
+				repository.federationStrategy.name());
+		config.setBoolean(Constants.CONFIG_GITBLIT, null, "isFederated", repository.isFederated);
+		config.setString(Constants.CONFIG_GITBLIT, null, "gcThreshold", repository.gcThreshold);
+		if (repository.gcPeriod == settings.getInteger(Keys.git.defaultGarbageCollectionPeriod, 7)) {
+			// use default from config
+			config.unset(Constants.CONFIG_GITBLIT, null, "gcPeriod");
+		} else {
+			config.setInt(Constants.CONFIG_GITBLIT, null, "gcPeriod", repository.gcPeriod);
+		}
+		if (repository.lastGC != null) {
+			config.setString(Constants.CONFIG_GITBLIT, null, "lastGC", new SimpleDateFormat(Constants.ISO8601).format(repository.lastGC));
+		}
+		if (repository.maxActivityCommits == settings.getInteger(Keys.web.maxActivityCommits, 0)) {
+			// use default from config
+			config.unset(Constants.CONFIG_GITBLIT, null, "maxActivityCommits");
+		} else {
+			config.setInt(Constants.CONFIG_GITBLIT, null, "maxActivityCommits", repository.maxActivityCommits);
+		}
+
+		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) {
+				// clear section
+				config.unsetSection(Constants.CONFIG_GITBLIT, Constants.CONFIG_CUSTOM_FIELDS);
+			} else {
+				for (Entry<String, String> property : repository.customFields.entrySet()) {
+					// set field
+					String key = property.getKey();
+					String value = property.getValue();
+					config.setString(Constants.CONFIG_GITBLIT, Constants.CONFIG_CUSTOM_FIELDS, key, value);
+				}
+			}
+		}
+
+		try {
+			config.save();
+		} catch (IOException e) {
+			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
+		if (list == null) {
+			return;
+		}
+		if (ArrayUtils.isEmpty(list)) {
+			config.unset(Constants.CONFIG_GITBLIT, null, field);
+		} else {
+			config.setStringList(Constants.CONFIG_GITBLIT, null, field, list);
+		}
+	}
+
+	/**
+	 * Deletes the repository from the file system and removes the repository
+	 * permission from all repository users.
+	 * 
+	 * @param model
+	 * @return true if successful
+	 */
+	public boolean deleteRepositoryModel(RepositoryModel model) {
+		return deleteRepository(model.name);
+	}
+
+	/**
+	 * Deletes the repository from the file system and removes the repository
+	 * permission from all repository users.
+	 * 
+	 * @param repositoryName
+	 * @return true if successful
+	 */
+	public boolean deleteRepository(String repositoryName) {
+		try {
+			closeRepository(repositoryName);
+			// clear the repository cache
+			clearRepositoryMetadataCache(repositoryName);
+			
+			RepositoryModel model = removeFromCachedRepositoryList(repositoryName);
+			if (model != null && !ArrayUtils.isEmpty(model.forks)) {
+				resetRepositoryListCache();
+			}
+
+			File folder = new File(repositoriesFolder, repositoryName);
+			if (folder.exists() && folder.isDirectory()) {
+				FileUtils.delete(folder, FileUtils.RECURSIVE | FileUtils.RETRY);
+				if (userService.deleteRepositoryRole(repositoryName)) {
+					logger.info(MessageFormat.format("Repository \"{0}\" deleted", repositoryName));
+					return true;
+				}
+			}
+		} catch (Throwable t) {
+			logger.error(MessageFormat.format("Failed to delete repository {0}", repositoryName), t);
+		}
+		return false;
+	}
+
+	/**
+	 * Returns an html version of the commit message with any global or
+	 * repository-specific regular expression substitution applied.
+	 * 
+	 * @param repositoryName
+	 * @param text
+	 * @return html version of the commit message
+	 */
+	public String processCommitMessage(String repositoryName, String text) {
+		String html = StringUtils.breakLinesForHtml(text);
+		Map<String, String> map = new HashMap<String, String>();
+		// global regex keys
+		if (settings.getBoolean(Keys.regex.global, false)) {
+			for (String key : settings.getAllKeys(Keys.regex.global)) {
+				if (!key.equals(Keys.regex.global)) {
+					String subKey = key.substring(key.lastIndexOf('.') + 1);
+					map.put(subKey, settings.getString(key, ""));
+				}
+			}
+		}
+
+		// repository-specific regex keys
+		List<String> keys = settings.getAllKeys(Keys.regex._ROOT + "."
+				+ repositoryName.toLowerCase());
+		for (String key : keys) {
+			String subKey = key.substring(key.lastIndexOf('.') + 1);
+			map.put(subKey, settings.getString(key, ""));
+		}
+
+		for (Entry<String, String> entry : map.entrySet()) {
+			String definition = entry.getValue().trim();
+			String[] chunks = definition.split("!!!");
+			if (chunks.length == 2) {
+				html = html.replaceAll(chunks[0], chunks[1]);
+			} else {
+				logger.warn(entry.getKey()
+						+ " improperly formatted.  Use !!! to separate match from replacement: "
+						+ definition);
+			}
+		}
+		return html;
+	}
+
+	/**
+	 * Returns Gitblit's scheduled executor service for scheduling tasks.
+	 * 
+	 * @return scheduledExecutor
+	 */
+	public ScheduledExecutorService executor() {
+		return scheduledExecutor;
+	}
+
+	public static boolean canFederate() {
+		String passphrase = getString(Keys.federation.passphrase, "");
+		return !StringUtils.isEmpty(passphrase);
+	}
+
+	/**
+	 * Configures this Gitblit instance to pull any registered federated gitblit
+	 * instances.
+	 */
+	private void configureFederation() {
+		boolean validPassphrase = true;
+		String passphrase = settings.getString(Keys.federation.passphrase, "");
+		if (StringUtils.isEmpty(passphrase)) {
+			logger.warn("Federation passphrase is blank! This server can not be PULLED from.");
+			validPassphrase = false;
+		}
+		if (validPassphrase) {
+			// standard tokens
+			for (FederationToken tokenType : FederationToken.values()) {
+				logger.info(MessageFormat.format("Federation {0} token = {1}", tokenType.name(),
+						getFederationToken(tokenType)));
+			}
+
+			// federation set tokens
+			for (String set : settings.getStrings(Keys.federation.sets)) {
+				logger.info(MessageFormat.format("Federation Set {0} token = {1}", set,
+						getFederationToken(set)));
+			}
+		}
+
+		// Schedule the federation executor
+		List<FederationModel> registrations = getFederationRegistrations();
+		if (registrations.size() > 0) {
+			FederationPullExecutor executor = new FederationPullExecutor(registrations, true);
+			scheduledExecutor.schedule(executor, 1, TimeUnit.MINUTES);
+		}
+	}
+
+	/**
+	 * Returns the list of federated gitblit instances that this instance will
+	 * try to pull.
+	 * 
+	 * @return list of registered gitblit instances
+	 */
+	public List<FederationModel> getFederationRegistrations() {
+		if (federationRegistrations.isEmpty()) {
+			federationRegistrations.addAll(FederationUtils.getFederationRegistrations(settings));
+		}
+		return federationRegistrations;
+	}
+
+	/**
+	 * Retrieve the specified federation registration.
+	 * 
+	 * @param name
+	 *            the name of the registration
+	 * @return a federation registration
+	 */
+	public FederationModel getFederationRegistration(String url, String name) {
+		// check registrations
+		for (FederationModel r : getFederationRegistrations()) {
+			if (r.name.equals(name) && r.url.equals(url)) {
+				return r;
+			}
+		}
+
+		// check the results
+		for (FederationModel r : getFederationResultRegistrations()) {
+			if (r.name.equals(name) && r.url.equals(url)) {
+				return r;
+			}
+		}
+		return null;
+	}
+
+	/**
+	 * Returns the list of federation sets.
+	 * 
+	 * @return list of federation sets
+	 */
+	public List<FederationSet> getFederationSets(String gitblitUrl) {
+		List<FederationSet> list = new ArrayList<FederationSet>();
+		// generate standard tokens
+		for (FederationToken type : FederationToken.values()) {
+			FederationSet fset = new FederationSet(type.toString(), type, getFederationToken(type));
+			fset.repositories = getRepositories(gitblitUrl, fset.token);
+			list.add(fset);
+		}
+		// generate tokens for federation sets
+		for (String set : settings.getStrings(Keys.federation.sets)) {
+			FederationSet fset = new FederationSet(set, FederationToken.REPOSITORIES,
+					getFederationToken(set));
+			fset.repositories = getRepositories(gitblitUrl, fset.token);
+			list.add(fset);
+		}
+		return list;
+	}
+
+	/**
+	 * Returns the list of possible federation tokens for this Gitblit instance.
+	 * 
+	 * @return list of federation tokens
+	 */
+	public List<String> getFederationTokens() {
+		List<String> tokens = new ArrayList<String>();
+		// generate standard tokens
+		for (FederationToken type : FederationToken.values()) {
+			tokens.add(getFederationToken(type));
+		}
+		// generate tokens for federation sets
+		for (String set : settings.getStrings(Keys.federation.sets)) {
+			tokens.add(getFederationToken(set));
+		}
+		return tokens;
+	}
+
+	/**
+	 * Returns the specified federation token for this Gitblit instance.
+	 * 
+	 * @param type
+	 * @return a federation token
+	 */
+	public String getFederationToken(FederationToken type) {
+		return getFederationToken(type.name());
+	}
+
+	/**
+	 * Returns the specified federation token for this Gitblit instance.
+	 * 
+	 * @param value
+	 * @return a federation token
+	 */
+	public String getFederationToken(String value) {
+		String passphrase = settings.getString(Keys.federation.passphrase, "");
+		return StringUtils.getSHA1(passphrase + "-" + value);
+	}
+
+	/**
+	 * 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
+	 */
+	public boolean validateFederationRequest(FederationRequest req, String token) {
+		String all = getFederationToken(FederationToken.ALL);
+		String unr = getFederationToken(FederationToken.USERS_AND_REPOSITORIES);
+		String jur = getFederationToken(FederationToken.REPOSITORIES);
+		switch (req) {
+		case PULL_REPOSITORIES:
+			return token.equals(all) || token.equals(unr) || token.equals(jur);
+		case PULL_USERS:
+		case PULL_TEAMS:
+			return token.equals(all) || token.equals(unr);
+		case PULL_SETTINGS:
+		case PULL_SCRIPTS:
+			return token.equals(all);
+		default:
+			break;
+		}
+		return false;
+	}
+
+	/**
+	 * Acknowledge and cache the status of a remote Gitblit instance.
+	 * 
+	 * @param identification
+	 *            the identification of the pulling Gitblit instance
+	 * @param registration
+	 *            the registration from the pulling Gitblit instance
+	 * @return true if acknowledged
+	 */
+	public boolean acknowledgeFederationStatus(String identification, FederationModel registration) {
+		// reset the url to the identification of the pulling Gitblit instance
+		registration.url = identification;
+		String id = identification;
+		if (!StringUtils.isEmpty(registration.folder)) {
+			id += "-" + registration.folder;
+		}
+		federationPullResults.put(id, registration);
+		return true;
+	}
+
+	/**
+	 * Returns the list of registration results.
+	 * 
+	 * @return the list of registration results
+	 */
+	public List<FederationModel> getFederationResultRegistrations() {
+		return new ArrayList<FederationModel>(federationPullResults.values());
+	}
+
+	/**
+	 * Submit a federation proposal. The proposal is cached locally and the
+	 * Gitblit administrator(s) are notified via email.
+	 * 
+	 * @param proposal
+	 *            the proposal
+	 * @param gitblitUrl
+	 *            the url of your gitblit instance to send an email to
+	 *            administrators
+	 * @return true if the proposal was submitted
+	 */
+	public boolean submitFederationProposal(FederationProposal proposal, String gitblitUrl) {
+		// convert proposal to json
+		String json = JsonUtils.toJsonString(proposal);
+
+		try {
+			// make the proposals folder
+			File proposalsFolder = getProposalsFolder();
+			proposalsFolder.mkdirs();
+
+			// cache json to a file
+			File file = new File(proposalsFolder, proposal.token + Constants.PROPOSAL_EXT);
+			com.gitblit.utils.FileUtils.writeContent(file, json);
+		} catch (Exception e) {
+			logger.error(MessageFormat.format("Failed to cache proposal from {0}", proposal.url), e);
+		}
+
+		// send an email, if possible
+		sendMailToAdministrators("Federation proposal from " + proposal.url,
+				"Please review the proposal @ " + gitblitUrl + "/proposal/" + proposal.token);
+		return true;
+	}
+
+	/**
+	 * Returns the list of pending federation proposals
+	 * 
+	 * @return list of federation proposals
+	 */
+	public List<FederationProposal> getPendingFederationProposals() {
+		List<FederationProposal> list = new ArrayList<FederationProposal>();
+		File folder = getProposalsFolder();
+		if (folder.exists()) {
+			File[] files = folder.listFiles(new FileFilter() {
+				@Override
+				public boolean accept(File file) {
+					return file.isFile()
+							&& file.getName().toLowerCase().endsWith(Constants.PROPOSAL_EXT);
+				}
+			});
+			for (File file : files) {
+				String json = com.gitblit.utils.FileUtils.readContent(file, null);
+				FederationProposal proposal = JsonUtils.fromJsonString(json,
+						FederationProposal.class);
+				list.add(proposal);
+			}
+		}
+		return list;
+	}
+
+	/**
+	 * Get repositories for the specified token.
+	 * 
+	 * @param gitblitUrl
+	 *            the base url of this gitblit instance
+	 * @param token
+	 *            the federation token
+	 * @return a map of <cloneurl, RepositoryModel>
+	 */
+	public Map<String, RepositoryModel> getRepositories(String gitblitUrl, String token) {
+		Map<String, String> federationSets = new HashMap<String, String>();
+		for (String set : getStrings(Keys.federation.sets)) {
+			federationSets.put(getFederationToken(set), set);
+		}
+
+		// Determine the Gitblit clone url
+		StringBuilder sb = new StringBuilder();
+		sb.append(gitblitUrl);
+		sb.append(Constants.GIT_PATH);
+		sb.append("{0}");
+		String cloneUrl = sb.toString();
+
+		// Retrieve all available repositories
+		UserModel user = getFederationUser();
+		List<RepositoryModel> list = getRepositoryModels(user);
+
+		// create the [cloneurl, repositoryModel] map
+		Map<String, RepositoryModel> repositories = new HashMap<String, RepositoryModel>();
+		for (RepositoryModel model : list) {
+			// by default, setup the url for THIS repository
+			String url = MessageFormat.format(cloneUrl, model.name);
+			switch (model.federationStrategy) {
+			case EXCLUDE:
+				// skip this repository
+				continue;
+			case FEDERATE_ORIGIN:
+				// federate the origin, if it is defined
+				if (!StringUtils.isEmpty(model.origin)) {
+					url = model.origin;
+				}
+				break;
+			default:
+				break;
+			}
+
+			if (federationSets.containsKey(token)) {
+				// include repositories only for federation set
+				String set = federationSets.get(token);
+				if (model.federationSets.contains(set)) {
+					repositories.put(url, model);
+				}
+			} else {
+				// standard federation token for ALL
+				repositories.put(url, model);
+			}
+		}
+		return repositories;
+	}
+
+	/**
+	 * Creates a proposal from the token.
+	 * 
+	 * @param gitblitUrl
+	 *            the url of this Gitblit instance
+	 * @param token
+	 * @return a potential proposal
+	 */
+	public FederationProposal createFederationProposal(String gitblitUrl, String token) {
+		FederationToken tokenType = FederationToken.REPOSITORIES;
+		for (FederationToken type : FederationToken.values()) {
+			if (token.equals(getFederationToken(type))) {
+				tokenType = type;
+				break;
+			}
+		}
+		Map<String, RepositoryModel> repositories = getRepositories(gitblitUrl, token);
+		FederationProposal proposal = new FederationProposal(gitblitUrl, tokenType, token,
+				repositories);
+		return proposal;
+	}
+
+	/**
+	 * Returns the proposal identified by the supplied token.
+	 * 
+	 * @param token
+	 * @return the specified proposal or null
+	 */
+	public FederationProposal getPendingFederationProposal(String token) {
+		List<FederationProposal> list = getPendingFederationProposals();
+		for (FederationProposal proposal : list) {
+			if (proposal.token.equals(token)) {
+				return proposal;
+			}
+		}
+		return null;
+	}
+
+	/**
+	 * Deletes a pending federation proposal.
+	 * 
+	 * @param a
+	 *            proposal
+	 * @return true if the proposal was deleted
+	 */
+	public boolean deletePendingFederationProposal(FederationProposal proposal) {
+		File folder = getProposalsFolder();
+		File file = new File(folder, proposal.token + Constants.PROPOSAL_EXT);
+		return file.delete();
+	}
+
+	/**
+	 * 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() {
+		File groovyFolder = getGroovyScriptsFolder();
+		File[] files = groovyFolder.listFiles(new FileFilter() {
+			@Override
+			public boolean accept(File pathname) {
+				return pathname.isFile() && pathname.getName().endsWith(".groovy");
+			}
+		});
+		List<String> scripts = new ArrayList<String>();
+		if (files != null) {
+			for (File file : files) {
+				String script = file.getName().substring(0, file.getName().lastIndexOf('.'));
+				scripts.add(script);
+			}
+		}
+		return scripts;
+	}
+
+	/**
+	 * 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
+	 */
+	public List<String> getPreReceiveScriptsInherited(RepositoryModel repository) {
+		Set<String> scripts = new LinkedHashSet<String>();
+		// Globals
+		for (String script : getStrings(Keys.groovy.preReceiveScripts)) {
+			if (script.endsWith(".groovy")) {
+				scripts.add(script.substring(0, script.lastIndexOf('.')));
+			} else {
+				scripts.add(script);
+			}
+		}
+
+		// Team Scripts
+		if (repository != null) {
+			for (String teamname : userService.getTeamnamesForRepositoryRole(repository.name)) {
+				TeamModel team = userService.getTeamModel(teamname);
+				if (!ArrayUtils.isEmpty(team.preReceiveScripts)) {
+					scripts.addAll(team.preReceiveScripts);
+				}
+			}
+		}
+		return new ArrayList<String>(scripts);
+	}
+
+	/**
+	 * 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
+	 */
+	public List<String> getPreReceiveScriptsUnused(RepositoryModel repository) {
+		Set<String> inherited = new TreeSet<String>(getPreReceiveScriptsInherited(repository));
+
+		// create list of available scripts by excluding inherited scripts
+		List<String> scripts = new ArrayList<String>();
+		for (String script : getAllScripts()) {
+			if (!inherited.contains(script)) {
+				scripts.add(script);
+			}
+		}
+		return scripts;
+	}
+
+	/**
+	 * 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
+	 */
+	public List<String> getPostReceiveScriptsInherited(RepositoryModel repository) {
+		Set<String> scripts = new LinkedHashSet<String>();
+		// Global Scripts
+		for (String script : getStrings(Keys.groovy.postReceiveScripts)) {
+			if (script.endsWith(".groovy")) {
+				scripts.add(script.substring(0, script.lastIndexOf('.')));
+			} else {
+				scripts.add(script);
+			}
+		}
+		// Team Scripts
+		if (repository != null) {
+			for (String teamname : userService.getTeamnamesForRepositoryRole(repository.name)) {
+				TeamModel team = userService.getTeamModel(teamname);
+				if (!ArrayUtils.isEmpty(team.postReceiveScripts)) {
+					scripts.addAll(team.postReceiveScripts);
+				}
+			}
+		}
+		return new ArrayList<String>(scripts);
+	}
+
+	/**
+	 * 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
+	 */
+	public List<String> getPostReceiveScriptsUnused(RepositoryModel repository) {
+		Set<String> inherited = new TreeSet<String>(getPostReceiveScriptsInherited(repository));
+
+		// create list of available scripts by excluding inherited scripts
+		List<String> scripts = new ArrayList<String>();
+		for (String script : getAllScripts()) {
+			if (!inherited.contains(script)) {
+				scripts.add(script);
+			}
+		}
+		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) {		
+		List<SearchResult> srs = luceneExecutor.search(query, page, pageSize, repositories);
+		return srs;
+	}
+
+	/**
+	 * Notify the administrators by email.
+	 * 
+	 * @param subject
+	 * @param message
+	 */
+	public void sendMailToAdministrators(String subject, String message) {
+		List<String> toAddresses = settings.getStrings(Keys.mail.adminAddresses);
+		sendMail(subject, message, toAddresses);
+	}
+
+	/**
+	 * Notify users by email of something.
+	 * 
+	 * @param subject
+	 * @param message
+	 * @param toAddresses
+	 */
+	public void sendMail(String subject, String message, Collection<String> toAddresses) {
+		this.sendMail(subject, message, toAddresses.toArray(new String[0]));
+	}
+
+	/**
+	 * Notify users by email of something.
+	 * 
+	 * @param subject
+	 * @param message
+	 * @param toAddresses
+	 */
+	public void sendMail(String subject, String message, String... toAddresses) {
+		if (toAddresses == null || toAddresses.length == 0) {
+			logger.debug(MessageFormat.format("Dropping message {0} because there are no recipients", subject));
+			return;
+		}
+		try {
+			Message mail = mailExecutor.createMessage(toAddresses);
+			if (mail != null) {
+				mail.setSubject(subject);
+				
+				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) {
+			logger.error("Messaging error", e);
+		}
+	}
+
+	/**
+	 * Notify users by email of something.
+	 * 
+	 * @param subject
+	 * @param message
+	 * @param toAddresses
+	 */
+	public void sendHtmlMail(String subject, String message, Collection<String> toAddresses) {
+		this.sendHtmlMail(subject, message, toAddresses.toArray(new String[0]));
+	}
+
+	/**
+	 * Notify users by email of something.
+	 * 
+	 * @param subject
+	 * @param message
+	 * @param toAddresses
+	 */
+	public void sendHtmlMail(String subject, String message, String... toAddresses) {
+		if (toAddresses == null || toAddresses.length == 0) {
+			logger.debug(MessageFormat.format("Dropping message {0} because there are no recipients", subject));
+			return;
+		}
+		try {
+			Message mail = mailExecutor.createMessage(toAddresses);
+			if (mail != null) {
+				mail.setSubject(subject);
+				
+				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);
+
+				mailExecutor.queue(mail);
+			}
+		} catch (MessagingException e) {
+			logger.error("Messaging error", e);
+		}
+	}
+
+	/**
+	 * Returns the descriptions/comments of the Gitblit config settings.
+	 * 
+	 * @return SettingsModel
+	 */
+	public ServerSettings getSettingsModel() {
+		// ensure that the current values are updated in the setting models
+		for (String key : settings.getAllKeys(null)) {
+			SettingModel setting = settingsModel.get(key);
+			if (setting == null) {
+				// unreferenced setting, create a setting model
+				setting = new SettingModel();
+				setting.name = key;
+				settingsModel.add(setting);
+			}
+			setting.currentValue = settings.getString(key, "");			
+		}
+		settingsModel.pushScripts = getAllScripts();
+		return settingsModel;
+	}
+
+	/**
+	 * 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() {
+		ServerSettings settingsModel = new ServerSettings();
+		settingsModel.supportsCredentialChanges = userService.supportsCredentialChanges();
+		settingsModel.supportsDisplayNameChanges = userService.supportsDisplayNameChanges();
+		settingsModel.supportsEmailAddressChanges = userService.supportsEmailAddressChanges();
+		settingsModel.supportsTeamMembershipChanges = userService.supportsTeamMembershipChanges();
+		try {
+			// Read bundled Gitblit properties to extract setting descriptions.
+			// This copy is pristine and only used for populating the setting
+			// models map.
+			InputStream is = getClass().getResourceAsStream("/reference.properties");
+			BufferedReader propertiesReader = new BufferedReader(new InputStreamReader(is));
+			StringBuilder description = new StringBuilder();
+			SettingModel setting = new SettingModel();
+			String line = null;
+			while ((line = propertiesReader.readLine()) != null) {
+				if (line.length() == 0) {
+					description.setLength(0);
+					setting = new SettingModel();
+				} else {
+					if (line.charAt(0) == '#') {
+						if (line.length() > 1) {
+							String text = line.substring(1).trim();
+							if (SettingModel.CASE_SENSITIVE.equals(text)) {
+								setting.caseSensitive = true;
+							} else if (SettingModel.RESTART_REQUIRED.equals(text)) {
+								setting.restartRequired = true;
+							} else if (SettingModel.SPACE_DELIMITED.equals(text)) {
+								setting.spaceDelimited = true;
+							} else if (text.startsWith(SettingModel.SINCE)) {
+								try {
+									setting.since = text.split(" ")[1];
+								} catch (Exception e) {
+									setting.since = text;
+								}
+							} else {
+								description.append(text);
+								description.append('\n');
+							}
+						}
+					} else {
+						String[] kvp = line.split("=", 2);
+						String key = kvp[0].trim();
+						setting.name = key;
+						setting.defaultValue = kvp[1].trim();
+						setting.currentValue = setting.defaultValue;
+						setting.description = description.toString().trim();
+						settingsModel.add(setting);
+						description.setLength(0);
+						setting = new SettingModel();
+					}
+				}
+			}
+			propertiesReader.close();
+		} catch (NullPointerException e) {
+			logger.error("Failed to find resource copy of gitblit.properties");
+		} catch (IOException e) {
+			logger.error("Failed to load resource copy of gitblit.properties");
+		}
+		return settingsModel;
+	}
+
+	/**
+	 * 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) {
+		this.settings = settings;
+		this.baseFolder = folder;
+
+		repositoriesFolder = getRepositoriesFolder();
+
+		logger.info("Gitblit base folder     = " + folder.getAbsolutePath());
+		logger.info("Git repositories folder = " + repositoriesFolder.getAbsolutePath());
+		logger.info("Gitblit settings        = " + settings.toString());
+
+		// prepare service executors
+		mailExecutor = new MailExecutor(settings);
+		luceneExecutor = new LuceneExecutor(settings, repositoriesFolder);
+		gcExecutor = new GCExecutor(settings);
+		
+		// calculate repository list settings checksum for future config changes
+		repositoryListSettingsChecksum.set(getRepositoryListSettingsChecksum());
+
+		// build initial repository list
+		if (settings.getBoolean(Keys.git.cacheRepositoryList,  true)) {
+			logger.info("Identifying available repositories...");
+			getRepositoryList();
+		}
+		
+		logTimezone("JVM", TimeZone.getDefault());
+		logTimezone(Constants.NAME, getTimezone());
+
+		serverStatus = new ServerStatus(isGO());
+
+		if (this.userService == null) {
+			String realm = settings.getString(Keys.realm.userService, "${baseFolder}/users.properties");
+			IUserService loginService = null;
+			try {
+				// check to see if this "file" is a login service class
+				Class<?> realmClass = Class.forName(realm);
+				loginService = (IUserService) realmClass.newInstance();
+			} catch (Throwable t) {
+				loginService = new GitblitUserService();
+			}
+			setUserService(loginService);
+		}
+		
+		// load and cache the project metadata
+		projectConfigs = new FileBasedConfig(getFileOrFolder(Keys.web.projectsFile, "${baseFolder}/projects.conf"), FS.detect());
+		getProjectConfigs();
+		
+		configureMailExecutor();		
+		configureLuceneIndexing();
+		configureGarbageCollector();
+		if (startFederation) {
+			configureFederation();
+		}
+		configureJGit();
+		configureFanout();
+		configureGitDaemon();
+		configureCommitCache();
+
+		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.");
+			scheduledExecutor.scheduleAtFixedRate(mailExecutor, 1, 2, TimeUnit.MINUTES);
+		} else {
+			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()) {
+			logger.info("GC executor is scheduled to scan repositories every 24 hours.");
+			Calendar c = Calendar.getInstance();
+			c.set(Calendar.HOUR_OF_DAY, settings.getInteger(Keys.git.garbageCollectionHour, 0));
+			c.set(Calendar.MINUTE, 0);
+			c.set(Calendar.SECOND, 0);
+			c.set(Calendar.MILLISECOND, 0);
+			Date cd = c.getTime();
+			Date now = new Date();
+			int delay = 0;
+			if (cd.before(now)) {
+				c.add(Calendar.DATE, 1);
+				cd = c.getTime();
+			}
+			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);
+			}
+			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();
+
+		cfg.setPackedGitWindowSize(settings.getFilesize(Keys.git.packedGitWindowSize, cfg.getPackedGitWindowSize()));
+		cfg.setPackedGitLimit(settings.getFilesize(Keys.git.packedGitLimit, cfg.getPackedGitLimit()));
+		cfg.setDeltaBaseCacheLimit(settings.getFilesize(Keys.git.deltaBaseCacheLimit, cfg.getDeltaBaseCacheLimit()));
+		cfg.setPackedGitOpenFiles(settings.getFilesize(Keys.git.packedGitOpenFiles, cfg.getPackedGitOpenFiles()));
+		cfg.setStreamFileThreshold(settings.getFilesize(Keys.git.streamFileThreshold, cfg.getStreamFileThreshold()));
+		cfg.setPackedGitMMAP(settings.getBoolean(Keys.git.packedGitMmap, cfg.isPackedGitMMAP()));
+
+		try {
+			cfg.install();
+			logger.debug(MessageFormat.format("{0} = {1,number,0}", Keys.git.packedGitWindowSize, cfg.getPackedGitWindowSize()));
+			logger.debug(MessageFormat.format("{0} = {1,number,0}", Keys.git.packedGitLimit, cfg.getPackedGitLimit()));
+			logger.debug(MessageFormat.format("{0} = {1,number,0}", Keys.git.deltaBaseCacheLimit, cfg.getDeltaBaseCacheLimit()));
+			logger.debug(MessageFormat.format("{0} = {1,number,0}", Keys.git.packedGitOpenFiles, cfg.getPackedGitOpenFiles()));
+			logger.debug(MessageFormat.format("{0} = {1,number,0}", Keys.git.streamFileThreshold, cfg.getStreamFileThreshold()));
+			logger.debug(MessageFormat.format("{0} = {1}", Keys.git.packedGitMmap, cfg.isPackedGitMMAP()));
+		} catch (IllegalArgumentException e) {
+			logger.error("Failed to configure JGit parameters!", e);
+		}
+	}
+	
+	protected void configureFanout() {
+		// startup Fanout PubSub service
+		if (settings.getInteger(Keys.fanout.port, 0) > 0) {
+			String bindInterface = settings.getString(Keys.fanout.bindInterface, null);
+			int port = settings.getInteger(Keys.fanout.port, FanoutService.DEFAULT_PORT);
+			boolean useNio = settings.getBoolean(Keys.fanout.useNio, true);
+			int limit = settings.getInteger(Keys.fanout.connectionLimit, 0);
+
+			if (useNio) {
+				if (StringUtils.isEmpty(bindInterface)) {
+					fanoutService = new FanoutNioService(port);
+				} else {
+					fanoutService = new FanoutNioService(bindInterface, port);
+				}
+			} else {
+				if (StringUtils.isEmpty(bindInterface)) {
+					fanoutService = new FanoutSocketService(port);
+				} else {
+					fanoutService = new FanoutSocketService(bindInterface, port);
+				}
+			}
+
+			fanoutService.setConcurrentConnectionLimit(limit);
+			fanoutService.setAllowAllChannelAnnouncements(false);
+			fanoutService.start();
+		}
+	}
+	
+	protected void configureGitDaemon() {
+		int port = settings.getInteger(Keys.git.daemonPort, 0);
+		String bindInterface = settings.getString(Keys.git.daemonBindInterface, "localhost");
+		if (port > 0) {
+			try {
+				gitDaemon = new GitDaemon(bindInterface, port, getRepositoriesFolder());
+				gitDaemon.start();
+			} catch (IOException e) {
+				gitDaemon = null;
+				logger.error(MessageFormat.format("Failed to start Git daemon on {0}:{1,number,0}", bindInterface, port), e);
+			}
+		}
+	}
+	
+	protected void configureCommitCache() {
+		int daysToCache = settings.getInteger(Keys.web.activityCacheDays, 14);
+		if (daysToCache <= 0) {
+			logger.info("commit cache disabled");
+		} else {
+			long start = System.nanoTime();
+			long repoCount = 0;
+			long commitCount = 0;
+			logger.info(MessageFormat.format("preparing {0} day commit cache. please wait...", daysToCache));
+			CommitCache.instance().setCacheDays(daysToCache);
+			Date cutoff = CommitCache.instance().getCutoffDate();
+			for (String repositoryName : getRepositoryList()) {
+				RepositoryModel model = getRepositoryModel(repositoryName);
+				if (model.hasCommits && model.lastChange.after(cutoff)) {
+					repoCount++;
+					Repository repository = getRepository(repositoryName);
+					for (RefModel ref : JGitUtils.getLocalBranches(repository, true, -1)) {
+						if (!ref.getDate().after(cutoff)) {
+							// branch not recently updated
+							continue;
+						}
+						List<?> commits = CommitCache.instance().getCommits(repositoryName, repository, ref.getName());
+						if (commits.size() > 0) {
+							logger.info(MessageFormat.format("  cached {0} commits for {1}:{2}",
+									commits.size(), repositoryName, ref.getName()));
+							commitCount += commits.size();
+						}
+					}
+					repository.close();
+				}
+			}
+			logger.info(MessageFormat.format("built {0} day commit cache of {1} commits across {2} repositories in {3} msecs",
+					daysToCache, commitCount, repoCount, TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start)));
+		}
+	}
+	
+	protected final Logger getLogger() {
+		return logger;
+	}
+	
+	protected final ScheduledExecutorService getScheduledExecutor() {
+		return scheduledExecutor;
+	}
+
+	protected final LuceneExecutor getLuceneExecutor() {
+		return luceneExecutor;
+	}
+	
+	private void logTimezone(String type, TimeZone zone) {
+		SimpleDateFormat df = new SimpleDateFormat("z Z");
+		df.setTimeZone(zone);
+		String offset = df.format(new Date());
+		logger.info(type + " timezone is " + zone.getID() + " (" + offset + ")");
+	}
+
+	/**
+	 * Configure Gitblit from the web.xml, if no configuration has already been
+	 * specified.
+	 * 
+	 * @see ServletContextListener.contextInitialize(ServletContextEvent)
+	 */
+	@Override
+	public void contextInitialized(ServletContextEvent contextEvent) {
+		servletContext = contextEvent.getServletContext();
+		if (settings == null) {
+			// Gitblit is running in a servlet container
+			ServletContext context = contextEvent.getServletContext();
+			WebXmlSettings webxmlSettings = new WebXmlSettings(context);
+			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);
+				logger.info("EXPRESS contextFolder is " + contextFolder.getAbsolutePath());
+
+				// 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);
+				if (!localScripts.exists()) {
+					File warScripts = new File(contextFolder, "/WEB-INF/data/groovy");
+					if (!warScripts.equals(localScripts)) {
+						try {
+							com.gitblit.utils.FileUtils.copy(localScripts, warScripts.listFiles());
+						} catch (IOException e) {
+							logger.error(MessageFormat.format(
+									"Failed to copy included Groovy scripts from {0} to {1}",
+									warScripts, localScripts));
+						}
+					}
+				}
+				
+				// 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("");
+					logger.error(MessageFormat.format("\"{0}\" depends on \"{1}\" but \"{2}\" is returning NULL for \"{1}\"!",
+							Constants.baseFolder, Constants.contextFolder$, context.getServerInfo()));
+					logger.error(MessageFormat.format("Please specify a non-parameterized path for <context-param> {0} in web.xml!!", Constants.baseFolder));
+					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();
+
+				// try to extract the data folder resource to the baseFolder
+				File localSettings = new File(base, "gitblit.properties");
+				if (!localSettings.exists()) {
+					extractResources(context, "/WEB-INF/data/", base);
+				}
+
+				// delegate all config to baseFolder/gitblit.properties file
+				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
+			File f = new File(toDir, resource.substring(path.length()));
+			if (!f.exists()) {
+				InputStream is = null;
+				OutputStream os = null;
+				try {
+					if (resource.charAt(resource.length() - 1) == '/') {
+						// directory
+						f.mkdirs();
+						extractResources(context, resource, f);
+					} else {
+						// file
+						f.getParentFile().mkdirs();
+						is = context.getResourceAsStream(resource);
+						os = new FileOutputStream(f);
+						byte [] buffer = new byte[4096];
+						int len = 0;
+						while ((len = is.read(buffer)) > -1) {
+							os.write(buffer, 0, len);
+						}
+					}
+				} catch (FileNotFoundException e) {
+					logger.error("Failed to find resource \"" + resource + "\"", e);
+				} catch (IOException e) {
+					logger.error("Failed to copy resource \"" + resource + "\" to " + f, e);
+				} finally {
+					if (is != null) {
+						try {
+							is.close();
+						} catch (IOException e) {
+							// ignore
+						}
+					}
+					if (os != null) {
+						try {
+							os.close();
+						} catch (IOException e) {
+							// ignore
+						}
+					}
+				}
+			}
+		}
+	}
+
+	/**
+	 * Gitblit is being shutdown either because the servlet container is
+	 * shutting down or because the servlet container is re-deploying Gitblit.
+	 */
+	@Override
+	public void contextDestroyed(ServletContextEvent contextEvent) {
+		logger.info("Gitblit context destroyed by servlet container.");
+		scheduledExecutor.shutdownNow();
+		luceneExecutor.close();
+		gcExecutor.close();
+		if (fanoutService != null) {
+			fanoutService.stop();
+		}
+		if (gitDaemon != null) {
+			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
+	 */
+	public boolean isCollectingGarbage(String repositoryName) {
+		return gcExecutor.isCollectingGarbage(repositoryName);
+	}
+
+	/**
+	 * 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. 
+	 * 
+	 * @param repository
+	 * @param user
+	 * @return the repository model of the fork, if successful
+	 * @throws GitBlitException
+	 */
+	public RepositoryModel fork(RepositoryModel repository, UserModel user) throws GitBlitException {
+		String cloneName = MessageFormat.format("~{0}/{1}.git", user.username, StringUtils.stripDotGit(StringUtils.getLastPathElement(repository.name)));
+		String fromUrl = MessageFormat.format("file://{0}/{1}", repositoriesFolder.getAbsolutePath(), repository.name);
+
+		// clone the repository
+		try {
+			JGitUtils.cloneRepository(repositoriesFolder, cloneName, fromUrl, true, null);
+		} catch (Exception e) {
+			throw new GitBlitException(e);
+		}
+
+		// create a Gitblit repository model for the clone
+		RepositoryModel cloneModel = repository.cloneAs(cloneName);
+		// owner has REWIND/RW+ permissions
+		cloneModel.addOwner(user.username);
+		updateRepositoryModel(cloneName, cloneModel, false);
+
+		// add the owner of the source repository to the clone's access list
+		if (!ArrayUtils.isEmpty(repository.owners)) {
+			for (String owner : repository.owners) {
+				UserModel originOwner = getUserModel(owner);
+				if (originOwner != null) {
+					originOwner.setRepositoryPermission(cloneName, AccessPermission.CLONE);
+					updateUserModel(originOwner.username, originOwner, false);
+				}
+			}
+		}
+
+		// grant origin's user list clone permission to fork
+		List<String> users = getRepositoryUsers(repository);
+		List<UserModel> cloneUsers = new ArrayList<UserModel>();
+		for (String name : users) {
+			if (!name.equalsIgnoreCase(user.username)) {
+				UserModel cloneUser = getUserModel(name);
+				if (cloneUser.canClone(repository)) {
+					// origin user can clone origin, grant clone access to fork
+					cloneUser.setRepositoryPermission(cloneName, AccessPermission.CLONE);
+				}
+				cloneUsers.add(cloneUser);
+			}
+		}
+		userService.updateUserModels(cloneUsers);
+
+		// grant origin's team list clone permission to fork
+		List<String> teams = getRepositoryTeams(repository);
+		List<TeamModel> cloneTeams = new ArrayList<TeamModel>();
+		for (String name : teams) {
+			TeamModel cloneTeam = getTeamModel(name);
+			if (cloneTeam.canClone(repository)) {
+				// origin team can clone origin, grant clone access to fork
+				cloneTeam.setRepositoryPermission(cloneName, AccessPermission.CLONE);
+			}
+			cloneTeams.add(cloneTeam);
+		}
+		userService.updateTeamModels(cloneTeams);			
+
+		// add this clone to the cached model
+		addToCachedRepositoryList(cloneModel);
+		return cloneModel;
+	}
+
+	/**
+	 * Allow to understand if GitBlit supports and is configured to allow
+	 * cookie-based authentication.
+	 * 
+	 * @return status of Cookie authentication enablement.
+	 */
+	public boolean allowCookieAuthentication() {
+		return GitBlit.getBoolean(Keys.web.allowCookieAuthentication, true) && userService.supportsCookies();
+	}
+}
diff --git a/src/com/gitblit/GitBlitException.java b/src/main/java/com/gitblit/GitBlitException.java
similarity index 100%
rename from src/com/gitblit/GitBlitException.java
rename to src/main/java/com/gitblit/GitBlitException.java
diff --git a/src/main/java/com/gitblit/GitBlitServer.java b/src/main/java/com/gitblit/GitBlitServer.java
new file mode 100644
index 0000000..ce05995
--- /dev/null
+++ b/src/main/java/com/gitblit/GitBlitServer.java
@@ -0,0 +1,642 @@
+/*
+ * Copyright 2011 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.URI;
+import java.net.URL;
+import java.net.UnknownHostException;
+import java.security.ProtectionDomain;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Scanner;
+
+import org.eclipse.jetty.ajp.Ajp13SocketConnector;
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.bio.SocketConnector;
+import org.eclipse.jetty.server.nio.SelectChannelConnector;
+import org.eclipse.jetty.server.session.HashSessionManager;
+import org.eclipse.jetty.server.ssl.SslConnector;
+import org.eclipse.jetty.server.ssl.SslSelectChannelConnector;
+import org.eclipse.jetty.server.ssl.SslSocketConnector;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
+import org.eclipse.jetty.webapp.WebAppContext;
+import org.eclipse.jgit.storage.file.FileBasedConfig;
+import org.eclipse.jgit.util.FS;
+import org.eclipse.jgit.util.FileUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.beust.jcommander.JCommander;
+import com.beust.jcommander.Parameter;
+import com.beust.jcommander.ParameterException;
+import com.beust.jcommander.Parameters;
+import com.gitblit.authority.GitblitAuthority;
+import com.gitblit.authority.NewCertificateConfig;
+import com.gitblit.utils.StringUtils;
+import com.gitblit.utils.TimeUtils;
+import com.gitblit.utils.X509Utils;
+import com.gitblit.utils.X509Utils.X509Log;
+import com.gitblit.utils.X509Utils.X509Metadata;
+import com.unboundid.ldap.listener.InMemoryDirectoryServer;
+import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig;
+import com.unboundid.ldap.listener.InMemoryListenerConfig;
+import com.unboundid.ldif.LDIFReader;
+
+/**
+ * GitBlitServer is the embedded Jetty server for Gitblit GO. This class starts
+ * and stops an instance of Jetty that is configured from a combination of the
+ * gitblit.properties file and command line parameters. JCommander is used to
+ * 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 {
+
+	private static Logger logger;
+
+	public static void main(String... args) {
+		GitBlitServer server = new GitBlitServer();
+		
+		// filter out the baseFolder parameter
+		List<String> filtered = new ArrayList<String>();
+		String folder = "data";
+		for (int i = 0; i< args.length; i++) {
+			String arg = args[i];
+			if (arg.equals("--baseFolder")) {
+				if (i + 1 == args.length) {
+					System.out.println("Invalid --baseFolder parameter!");
+					System.exit(-1);
+				} else if (args[i + 1] != ".") {
+					folder = args[i + 1];
+				}
+				i = i + 1;
+			} else {
+				filtered.add(arg);
+			}
+		}
+		
+		Params.baseFolder = folder;
+		Params params = new Params();
+		JCommander jc = new JCommander(params);
+		try {
+			jc.parse(filtered.toArray(new String[filtered.size()]));
+			if (params.help) {
+				server.usage(jc, null);
+			}
+		} catch (ParameterException t) {
+			server.usage(jc, t);
+		}
+
+		if (params.stop) {
+			server.stop(params);
+		} else {
+			server.start(params);
+		}
+	}
+
+	/**
+	 * Display the command line usage of Gitblit GO.
+	 * 
+	 * @param jc
+	 * @param t
+	 */
+	protected final void usage(JCommander jc, ParameterException t) {
+		System.out.println(Constants.BORDER);
+		System.out.println(Constants.getGitBlitVersion());
+		System.out.println(Constants.BORDER);
+		System.out.println();
+		if (t != null) {
+			System.out.println(t.getMessage());
+			System.out.println();
+		}
+		if (jc != null) {
+			jc.usage();
+			System.out
+					.println("\nExample:\n  java -server -Xmx1024M -jar gitblit.jar --repositoriesFolder c:\\git --httpPort 80 --httpsPort 443");
+		}
+		System.exit(0);
+	}
+
+	/**
+	 * Stop Gitblt GO.
+	 */
+	public void stop(Params params) {
+		try {
+			Socket s = new Socket(InetAddress.getByName("127.0.0.1"), params.shutdownPort);
+			OutputStream out = s.getOutputStream();
+			System.out.println("Sending Shutdown Request to " + Constants.NAME);
+			out.write("\r\n".getBytes());
+			out.flush();
+			s.close();
+		} catch (UnknownHostException e) {
+			e.printStackTrace();
+		} catch (IOException e) {
+			e.printStackTrace();
+		}
+	}
+
+	/**
+	 * Start Gitblit GO.
+	 */
+	protected final void start(Params params) {
+		final File baseFolder = new File(Params.baseFolder).getAbsoluteFile();
+		FileSettings settings = params.FILESETTINGS;
+		if (!StringUtils.isEmpty(params.settingsfile)) {
+			if (new File(params.settingsfile).exists()) {
+				settings = new FileSettings(params.settingsfile);				
+			}
+		}
+		logger = LoggerFactory.getLogger(GitBlitServer.class);
+		logger.info(Constants.BORDER);
+		logger.info("            _____  _  _    _      _  _  _");
+		logger.info("           |  __ \\(_)| |  | |    | |(_)| |");
+		logger.info("           | |  \\/ _ | |_ | |__  | | _ | |_");
+		logger.info("           | | __ | || __|| '_ \\ | || || __|");
+		logger.info("           | |_\\ \\| || |_ | |_) || || || |_");
+		logger.info("            \\____/|_| \\__||_.__/ |_||_| \\__|");
+		int spacing = (Constants.BORDER.length() - Constants.getGitBlitVersion().length()) / 2;
+		StringBuilder sb = new StringBuilder();
+		while (spacing > 0) {
+			spacing--;
+			sb.append(' ');
+		}
+		logger.info(sb.toString() + Constants.getGitBlitVersion());
+		logger.info("");
+		logger.info(Constants.BORDER);
+
+		System.setProperty("java.awt.headless", "true");
+
+		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
+		if (params.port > 0) {
+			Connector httpConnector = createConnector(params.useNIO, params.port, settings.getInteger(Keys.server.threadPoolSize, 50));
+			String bindInterface = settings.getString(Keys.server.httpBindInterface, null);
+			if (!StringUtils.isEmpty(bindInterface)) {
+				logger.warn(MessageFormat.format("Binding connector on port {0,number,0} to {1}",
+						params.port, bindInterface));
+				httpConnector.setHost(bindInterface);
+			}
+			if (params.port < 1024 && !isWindows()) {
+				logger.warn("Gitblit needs to run with ROOT permissions for ports < 1024!");
+			}
+			connectors.add(httpConnector);
+		}
+
+		// conditionally configure the https connector
+		if (params.securePort > 0) {
+			File certificatesConf = new File(baseFolder, X509Utils.CA_CONFIG);
+			File serverKeyStore = new File(baseFolder, X509Utils.SERVER_KEY_STORE);
+			File serverTrustStore = new File(baseFolder, X509Utils.SERVER_TRUST_STORE);
+			File caRevocationList = new File(baseFolder, X509Utils.CA_REVOCATION_LIST);
+
+			// generate CA & web certificates, create certificate stores
+			X509Metadata metadata = new X509Metadata("localhost", params.storePassword);
+			// set default certificate values from config file
+			if (certificatesConf.exists()) {
+				FileBasedConfig config = new FileBasedConfig(certificatesConf, FS.detect());
+				try {
+					config.load();
+				} catch (Exception e) {
+					logger.error("Error parsing " + certificatesConf, e);
+				}
+				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
+				public void log(String message) {
+					BufferedWriter writer = null;
+					try {
+						writer = new BufferedWriter(new FileWriter(new File(baseFolder, X509Utils.CERTS + File.separator + "log.txt"), true));
+						writer.write(MessageFormat.format("{0,date,yyyy-MM-dd HH:mm}: {1}", new Date(), message));
+						writer.newLine();
+						writer.flush();
+					} catch (Exception e) {
+						LoggerFactory.getLogger(GitblitAuthority.class).error("Failed to append log entry!", e);
+					} finally {
+						if (writer != null) {
+							try {
+								writer.close();
+							} catch (IOException e) {
+							}
+						}
+					}
+				}
+			});
+
+			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);
+				if (!StringUtils.isEmpty(bindInterface)) {
+					logger.warn(MessageFormat.format(
+							"Binding ssl connector on port {0,number,0} to {1}", params.securePort,
+							bindInterface));
+					secureConnector.setHost(bindInterface);
+				}
+				if (params.securePort < 1024 && !isWindows()) {
+					logger.warn("Gitblit needs to run with ROOT permissions for ports < 1024!");
+				}
+				connectors.add(secureConnector);
+			} else {
+				logger.warn("Failed to find or load Keystore?");
+				logger.warn("SSL connector DISABLED.");
+			}
+		}
+
+		// conditionally configure the ajp connector
+		if (params.ajpPort > 0) {
+			Connector ajpConnector = createAJPConnector(params.ajpPort);
+			String bindInterface = settings.getString(Keys.server.ajpBindInterface, null);
+			if (!StringUtils.isEmpty(bindInterface)) {
+				logger.warn(MessageFormat.format("Binding connector on port {0,number,0} to {1}",
+						params.ajpPort, bindInterface));
+				ajpConnector.setHost(bindInterface);
+			}
+			if (params.ajpPort < 1024 && !isWindows()) {
+				logger.warn("Gitblit needs to run with ROOT permissions for ports < 1024!");
+			}
+			connectors.add(ajpConnector);
+		}
+
+		// 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);		
+		if (tempDir.exists()) {
+			try {
+				FileUtils.delete(tempDir, FileUtils.RECURSIVE | FileUtils.RETRY);
+			} catch (IOException x) {
+				logger.warn("Failed to delete temp dir " + tempDir.getAbsolutePath(), x);
+			}
+		}
+		if (!tempDir.mkdirs()) {
+			logger.warn("Failed to create temp dir " + tempDir.getAbsolutePath());
+		}
+
+		Server server = new Server();
+		server.setStopAtShutdown(true);
+		server.setConnectors(connectors.toArray(new Connector[connectors.size()]));
+
+		// Get the execution path of this class
+		// We use this to set the WAR path.
+		ProtectionDomain protectionDomain = GitBlitServer.class.getProtectionDomain();
+		URL location = protectionDomain.getCodeSource().getLocation();
+
+		// Root WebApp Context
+		WebAppContext rootContext = new WebAppContext();
+		rootContext.setContextPath(settings.getString(Keys.server.contextPath, "/"));
+		rootContext.setServer(server);
+		rootContext.setWar(location.toExternalForm());
+		rootContext.setTempDirectory(tempDir);
+
+		// Set cookies HttpOnly so they are not accessible to JavaScript engines
+		HashSessionManager sessionManager = new HashSessionManager();
+		sessionManager.setHttpOnly(true);
+		// Use secure cookies if only serving https
+		sessionManager.setSecureCookies(params.port <= 0 && params.securePort > 0);
+		rootContext.getSessionHandler().setSessionManager(sessionManager);
+
+		// Ensure there is a defined User Service
+		String realmUsers = params.userService;
+		if (StringUtils.isEmpty(realmUsers)) {
+			logger.error(MessageFormat.format("PLEASE SPECIFY {0}!!", Keys.realm.userService));
+			return;
+		}
+
+		// Override settings from the command-line
+		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) {
+				File ldifFile = new File(params.ldapLdifFile);
+				if (ldifFile != null && ldifFile.exists()) {
+					URI ldapUrl = new URI(settings.getRequiredString(Keys.realm.ldap.server));
+					String firstLine = new Scanner(ldifFile).nextLine();
+					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);
+				}
+			}
+		} catch (Exception e) {
+			// Completely optional, just show a warning
+			logger.warn("Unable to start LDAP server", e);
+		}
+
+		// Set the server's contexts
+		server.setHandler(rootContext);
+
+		// Setup the GitBlit context
+		GitBlit gitblit = getGitBlitInstance();
+		gitblit.configureContext(settings, baseFolder, true);
+		rootContext.addEventListener(gitblit);
+
+		try {
+			// start the shutdown monitor
+			if (params.shutdownPort > 0) {
+				Thread shutdownMonitor = new ShutdownMonitorThread(server, params);
+				shutdownMonitor.start();
+			}
+
+			// start Jetty
+			server.start();
+			server.join();
+		} catch (Exception e) {
+			e.printStackTrace();
+			System.exit(100);
+		}
+	}
+	
+	protected GitBlit getGitBlitInstance() {
+		return GitBlit.self();
+	}
+
+	/**
+	 * Creates an http connector.
+	 * 
+	 * @param useNIO
+	 * @param port
+	 * @param threadPoolSize
+	 * @return an http connector
+	 */
+	private Connector createConnector(boolean useNIO, int port, int threadPoolSize) {
+		Connector connector;
+		if (useNIO) {
+			logger.info("Setting up NIO SelectChannelConnector on port " + port);
+			SelectChannelConnector nioconn = new SelectChannelConnector();
+			nioconn.setSoLingerTime(-1);
+			if (threadPoolSize > 0) {
+				nioconn.setThreadPool(new QueuedThreadPool(threadPoolSize));
+			}
+			connector = nioconn;
+		} else {
+			logger.info("Setting up SocketConnector on port " + port);
+			SocketConnector sockconn = new SocketConnector();
+			if (threadPoolSize > 0) {
+				sockconn.setThreadPool(new QueuedThreadPool(threadPoolSize));
+			}
+			connector = sockconn;
+		}
+
+		connector.setPort(port);
+		connector.setMaxIdleTime(30000);
+		return connector;
+	}
+
+	/**
+	 * 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
+	 * @param storePassword
+	 * @param caRevocationList
+	 * @param useNIO
+	 * @param port
+	 * @param threadPoolSize
+	 * @param requireClientCertificates
+	 * @return an https connector
+	 */
+	private Connector createSSLConnector(String certAlias, File keyStore, File clientTrustStore,
+			String storePassword, File caRevocationList, boolean useNIO,  int port, int threadPoolSize, 
+			boolean requireClientCertificates) {
+		GitblitSslContextFactory factory = new GitblitSslContextFactory(certAlias,
+				keyStore, clientTrustStore, storePassword, caRevocationList);
+		SslConnector connector;
+		if (useNIO) {
+			logger.info("Setting up NIO SslSelectChannelConnector on port " + port);
+			SslSelectChannelConnector ssl = new SslSelectChannelConnector(factory);
+			ssl.setSoLingerTime(-1);
+			if (requireClientCertificates) {
+				factory.setNeedClientAuth(true);
+			} else {
+				factory.setWantClientAuth(true);
+			}
+			if (threadPoolSize > 0) {
+				ssl.setThreadPool(new QueuedThreadPool(threadPoolSize));
+			}
+			connector = ssl;
+		} else {
+			logger.info("Setting up NIO SslSocketConnector on port " + port);
+			SslSocketConnector ssl = new SslSocketConnector(factory);
+			if (threadPoolSize > 0) {
+				ssl.setThreadPool(new QueuedThreadPool(threadPoolSize));
+			}
+			connector = ssl;
+		}
+		connector.setPort(port);
+		connector.setMaxIdleTime(30000);
+
+		return connector;
+	}
+	
+	/**
+	 * Creates an ajp connector.
+	 * 
+	 * @param port
+	 * @return an ajp connector
+	 */
+	private Connector createAJPConnector(int port) {
+		logger.info("Setting up AJP Connector on port " + port);
+		Ajp13SocketConnector ajp = new Ajp13SocketConnector();
+		ajp.setPort(port);
+		if (port < 1024 && !isWindows()) {
+			logger.warn("Gitblit needs to run with ROOT permissions for ports < 1024!");
+		}
+		return ajp;
+	}
+
+	/**
+	 * Tests to see if the operating system is Windows.
+	 * 
+	 * @return true if this is a windows machine
+	 */
+	private boolean isWindows() {
+		return System.getProperty("os.name").toLowerCase().indexOf("windows") > -1;
+	}
+
+	/**
+	 * 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 {
+
+		private final ServerSocket socket;
+
+		private final Server server;
+
+		private final Logger logger = LoggerFactory.getLogger(ShutdownMonitorThread.class);
+
+		public ShutdownMonitorThread(Server server, Params params) {
+			this.server = server;
+			setDaemon(true);
+			setName(Constants.NAME + " Shutdown Monitor");
+			ServerSocket skt = null;
+			try {
+				skt = new ServerSocket(params.shutdownPort, 1, InetAddress.getByName("127.0.0.1"));
+			} catch (Exception e) {
+				logger.warn("Could not open shutdown monitor on port " + params.shutdownPort, e);
+			}
+			socket = skt;
+		}
+
+		@Override
+		public void run() {
+			logger.info("Shutdown Monitor listening on port " + socket.getLocalPort());
+			Socket accept;
+			try {
+				accept = socket.accept();
+				BufferedReader reader = new BufferedReader(new InputStreamReader(
+						accept.getInputStream()));
+				reader.readLine();
+				logger.info(Constants.BORDER);
+				logger.info("Stopping " + Constants.NAME);
+				logger.info(Constants.BORDER);
+				server.stop();
+				server.setStopAtShutdown(false);
+				accept.close();
+				socket.close();
+			} catch (Exception e) {
+				logger.warn("Failed to shutdown Jetty", e);
+			}
+		}
+	}
+
+	/**
+	 * JCommander Parameters class for GitBlitServer.
+	 */
+	@Parameters(separators = " ")
+	public static class Params {
+
+		public static String baseFolder;
+
+		private final FileSettings FILESETTINGS = new FileSettings(new File(baseFolder, Constants.PROPERTIES_FILE).getAbsolutePath());
+
+		/*
+		 * Server parameters
+		 */
+		@Parameter(names = { "-h", "--help" }, description = "Show this help")
+		public Boolean help = false;
+
+		@Parameter(names = { "--stop" }, description = "Stop Server")
+		public Boolean stop = false;
+
+		@Parameter(names = { "--tempFolder" }, description = "Folder for server to extract built-in webapp")
+		public String temp = FILESETTINGS.getString(Keys.server.tempFolder, "temp");
+
+		/*
+		 * GIT Servlet Parameters
+		 */
+		@Parameter(names = { "--repositoriesFolder" }, description = "Git Repositories Folder")
+		public String repositoriesFolder = FILESETTINGS.getString(Keys.git.repositoriesFolder,
+				"git");
+
+		/*
+		 * Authentication Parameters
+		 */
+		@Parameter(names = { "--userService" }, description = "Authentication and Authorization Service (filename or fully qualified classname)")
+		public String userService = FILESETTINGS.getString(Keys.realm.userService,
+				"users.conf");
+
+		/*
+		 * JETTY Parameters
+		 */
+		@Parameter(names = { "--useNio" }, description = "Use NIO Connector else use Socket Connector.")
+		public Boolean useNIO = FILESETTINGS.getBoolean(Keys.server.useNio, true);
+
+		@Parameter(names = "--httpPort", description = "HTTP port for to serve. (port <= 0 will disable this connector)")
+		public Integer port = FILESETTINGS.getInteger(Keys.server.httpPort, 0);
+
+		@Parameter(names = "--httpsPort", description = "HTTPS port to serve.  (port <= 0 will disable this connector)")
+		public Integer securePort = FILESETTINGS.getInteger(Keys.server.httpsPort, 8443);
+
+		@Parameter(names = "--ajpPort", description = "AJP port to serve.  (port <= 0 will disable this connector)")
+		public Integer ajpPort = FILESETTINGS.getInteger(Keys.server.ajpPort, 0);
+
+		@Parameter(names = "--gitPort", description = "Git Daemon port to serve.  (port <= 0 will disable this connector)")
+		public Integer gitPort = FILESETTINGS.getInteger(Keys.git.daemonPort, 9418);
+
+		@Parameter(names = "--alias", description = "Alias of SSL certificate in keystore for serving https.")
+		public String alias = FILESETTINGS.getString(Keys.server.certificateAlias, "");
+
+		@Parameter(names = "--storePassword", description = "Password for SSL (https) keystore.")
+		public String storePassword = FILESETTINGS.getString(Keys.server.storePassword, "");
+
+		@Parameter(names = "--shutdownPort", description = "Port for Shutdown Monitor to listen on. (port <= 0 will disable this monitor)")
+		public Integer shutdownPort = FILESETTINGS.getInteger(Keys.server.shutdownPort, 8081);
+
+		@Parameter(names = "--requireClientCertificates", description = "Require client X509 certificates for https connections.")
+		public Boolean requireClientCertificates = FILESETTINGS.getBoolean(Keys.server.requireClientCertificates, false);
+
+		/*
+		 * Setting overrides
+		 */
+		@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;
+
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/GitFilter.java b/src/main/java/com/gitblit/GitFilter.java
new file mode 100644
index 0000000..8c6dd80
--- /dev/null
+++ b/src/main/java/com/gitblit/GitFilter.java
@@ -0,0 +1,246 @@
+/*
+ * Copyright 2011 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit;
+
+import java.text.MessageFormat;
+
+import com.gitblit.Constants.AccessRestrictionType;
+import com.gitblit.Constants.AuthorizationControl;
+import com.gitblit.models.RepositoryModel;
+import com.gitblit.models.UserModel;
+import com.gitblit.utils.StringUtils;
+
+/**
+ * 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 {
+
+	protected static final String gitReceivePack = "/git-receive-pack";
+
+	protected static final String gitUploadPack = "/git-upload-pack";
+
+	protected static final String[] suffixes = { gitReceivePack, gitUploadPack, "/info/refs", "/HEAD",
+			"/objects" };
+
+	/**
+	 * Extract the repository name from the url.
+	 * 
+	 * @param cloneUrl
+	 * @return repository name
+	 */
+	public static String getRepositoryName(String value) {
+		String repository = value;
+		// get the repository name from the url by finding a known url suffix
+		for (String urlSuffix : suffixes) {
+			if (repository.indexOf(urlSuffix) > -1) {
+				repository = repository.substring(0, repository.indexOf(urlSuffix));
+			}
+		}
+		return repository;
+	}
+
+	/**
+	 * Extract the repository name from the url.
+	 * 
+	 * @param url
+	 * @return repository name
+	 */
+	@Override
+	protected String extractRepositoryName(String url) {
+		return GitFilter.getRepositoryName(url);
+	}
+
+	/**
+	 * 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
+	 */
+	@Override
+	protected String getUrlRequestAction(String suffix) {
+		if (!StringUtils.isEmpty(suffix)) {
+			if (suffix.startsWith(gitReceivePack)) {
+				return gitReceivePack;
+			} else if (suffix.startsWith(gitUploadPack)) {
+				return gitUploadPack;
+			} else if (suffix.contains("?service=git-receive-pack")) {
+				return gitReceivePack;
+			} else if (suffix.contains("?service=git-upload-pack")) {
+				return gitUploadPack;
+			} else {
+				return gitUploadPack;
+			}
+		}
+		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
+	 */
+	@Override
+	protected boolean isActionAllowed(RepositoryModel repository, String action) {
+		// the log here has been moved into ReceiveHook to provide clients with
+		// error messages
+		return true;
+	}
+
+	@Override
+	protected boolean requiresClientCertificate() {
+		return GitBlit.getBoolean(Keys.git.requiresClientCertificate, false);
+	}
+
+	/**
+	 * Determine if the repository requires authentication.
+	 * 
+	 * @param repository
+	 * @param action
+	 * @return true if authentication required
+	 */
+	@Override
+	protected boolean requiresAuthentication(RepositoryModel repository, String action) {
+		if (gitUploadPack.equals(action)) {
+			// send to client
+			return repository.accessRestriction.atLeast(AccessRestrictionType.CLONE);	
+		} else if (gitReceivePack.equals(action)) {
+			// receive from client
+			return repository.accessRestriction.atLeast(AccessRestrictionType.PUSH);
+		}
+		return false;
+	}
+
+	/**
+	 * 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) {
+		if (!GitBlit.getBoolean(Keys.git.enableGitServlet, true)) {
+			// Git Servlet disabled
+			return false;
+		}		
+		if (action.equals(gitReceivePack)) {
+			// Push request
+			if (user.canPush(repository)) {
+				return true;
+			} else {
+				// user is unauthorized to push to this repository
+				logger.warn(MessageFormat.format("user {0} is not authorized to push to {1}",
+						user.username, repository));
+				return false;
+			}
+		} else if (action.equals(gitUploadPack)) {
+			// Clone request
+			if (user.canClone(repository)) {
+				return true;
+			} else {
+				// user is unauthorized to clone this repository
+				logger.warn(MessageFormat.format("user {0} is not authorized to clone {1}",
+						user.username, repository));
+				return false;
+			}
+		}
+		return true;
+	}
+	
+	/**
+	 * An authenticated user with the CREATE role can create a repository on
+	 * push.
+	 * 
+	 * @param user
+	 * @param repository
+	 * @param action
+	 * @return the repository model, if it is created, null otherwise
+	 */
+	@Override
+	protected RepositoryModel createRepository(UserModel user, String repository, String action) {
+		boolean isPush = !StringUtils.isEmpty(action) && gitReceivePack.equals(action);
+		if (isPush) {
+			if (user.canCreate(repository)) {
+				// user is pushing to a new repository
+				// validate name
+				if (repository.startsWith("../")) {
+					logger.error(MessageFormat.format("Illegal relative path in repository name! {0}", repository));
+					return null;
+				}
+				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);
+				if (c != null) {
+					logger.error(MessageFormat.format("Invalid character '{0}' in repository name {1}!", c, repository));
+					return null;
+				}
+
+				// create repository
+				RepositoryModel model = new RepositoryModel();
+				model.name = repository;
+				model.addOwner(user.username);
+				model.projectPath = StringUtils.getFirstPathElement(repository);
+				if (model.isUsersPersonalRepository(user.username)) {
+					// personal repository, default to private for user
+					model.authorizationControl = AuthorizationControl.NAMED;
+					model.accessRestriction = AccessRestrictionType.VIEW;
+				} else {
+					// common repository, user default server settings
+					model.authorizationControl = AuthorizationControl.fromName(GitBlit.getString(Keys.git.defaultAuthorizationControl, ""));
+					model.accessRestriction = AccessRestrictionType.fromName(GitBlit.getString(Keys.git.defaultAccessRestriction, ""));
+				}
+
+				// create the repository
+				try {
+					GitBlit.self().updateRepositoryModel(model.name, model, true);
+					logger.info(MessageFormat.format("{0} created {1} ON-PUSH", user.username, model.name));
+					return GitBlit.self().getRepositoryModel(model.name);
+				} catch (GitBlitException e) {
+					logger.error(MessageFormat.format("{0} failed to create repository {1} ON-PUSH!", user.username, model.name), e);
+				}
+			} else {
+				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;
+	}
+}
diff --git a/src/com/gitblit/GitblitSslContextFactory.java b/src/main/java/com/gitblit/GitblitSslContextFactory.java
similarity index 100%
rename from src/com/gitblit/GitblitSslContextFactory.java
rename to src/main/java/com/gitblit/GitblitSslContextFactory.java
diff --git a/src/com/gitblit/GitblitTrustManager.java b/src/main/java/com/gitblit/GitblitTrustManager.java
similarity index 100%
rename from src/com/gitblit/GitblitTrustManager.java
rename to src/main/java/com/gitblit/GitblitTrustManager.java
diff --git a/src/main/java/com/gitblit/GitblitUserService.java b/src/main/java/com/gitblit/GitblitUserService.java
new file mode 100644
index 0000000..658404b
--- /dev/null
+++ b/src/main/java/com/gitblit/GitblitUserService.java
@@ -0,0 +1,337 @@
+/*
+ * Copyright 2011 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit;
+
+import java.io.File;
+import java.io.IOException;
+import java.text.MessageFormat;
+import java.util.Collection;
+import java.util.List;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.gitblit.Constants.AccountType;
+import com.gitblit.models.TeamModel;
+import com.gitblit.models.UserModel;
+import com.gitblit.utils.DeepCopier;
+import com.gitblit.utils.StringUtils;
+
+/**
+ * This class wraps the default user service and is recommended as the starting
+ * point for custom user service implementations.
+ * 
+ * This does seem a little convoluted, but the idea is to allow IUserService to
+ * evolve with new methods and implementations without breaking custom
+ * authentication implementations.
+ * 
+ * The most common implementation of a custom IUserService is to only override
+ * authentication and then delegate all other functionality to one of Gitblit's
+ * user services. This class optimizes that use-case.
+ * 
+ * Extending GitblitUserService allows for authentication customization without
+ * having to keep-up-with IUSerService API changes.
+ * 
+ * @author James Moger
+ * 
+ */
+public class GitblitUserService implements IUserService {
+
+	protected IUserService serviceImpl;
+	
+	private final Logger logger = LoggerFactory.getLogger(GitblitUserService.class);
+
+	public GitblitUserService() {
+	}
+
+	@Override
+	public void setup(IStoredSettings settings) {
+		File realmFile = GitBlit.getFileOrFolder(Keys.realm.userService, "${baseFolder}/users.conf");
+		serviceImpl = createUserService(realmFile);
+		logger.info("GUS delegating to " + serviceImpl.toString());
+	}
+
+	@SuppressWarnings("deprecation")
+	protected IUserService createUserService(File realmFile) {
+		IUserService service = null;
+		if (realmFile.getName().toLowerCase().endsWith(".properties")) {
+			// v0.5.0 - v0.7.0 properties-based realm file
+			service = new FileUserService(realmFile);
+		} else if (realmFile.getName().toLowerCase().endsWith(".conf")) {
+			// v0.8.0+ config-based realm file
+			service = new ConfigUserService(realmFile);
+		}
+
+		assert service != null;
+
+		if (!realmFile.exists()) {
+			// Create the Administrator account for a new realm file
+			try {
+				realmFile.createNewFile();
+			} catch (IOException x) {
+				logger.error(MessageFormat.format("COULD NOT CREATE REALM FILE {0}!", realmFile), x);
+			}
+			UserModel admin = new UserModel("admin");
+			admin.password = "admin";
+			admin.canAdmin = true;
+			admin.excludeFromFederation = true;
+			service.updateUserModel(admin);
+		}
+
+		if (service instanceof FileUserService) {
+			// automatically create a users.conf realm file from the original
+			// users.properties file
+			File usersConfig = new File(realmFile.getParentFile(), "users.conf");
+			if (!usersConfig.exists()) {
+				logger.info(MessageFormat.format("Automatically creating {0} based on {1}",
+						usersConfig.getAbsolutePath(), realmFile.getAbsolutePath()));
+				ConfigUserService configService = new ConfigUserService(usersConfig);
+				for (String username : service.getAllUsernames()) {
+					UserModel userModel = service.getUserModel(username);
+					configService.updateUserModel(userModel);
+				}
+			}
+			// issue suggestion about switching to users.conf
+			logger.warn("Please consider using \"users.conf\" instead of the deprecated \"users.properties\" file");
+		}
+		return service;
+	}
+	
+	@Override
+	public String toString() {
+		return getClass().getSimpleName();
+	}
+
+	@Override
+	public boolean supportsCredentialChanges() {
+		return serviceImpl.supportsCredentialChanges();
+	}
+
+	@Override
+	public boolean supportsDisplayNameChanges() {
+		return serviceImpl.supportsDisplayNameChanges();
+	}
+
+	@Override
+	public boolean supportsEmailAddressChanges() {
+		return serviceImpl.supportsEmailAddressChanges();
+	}
+
+	@Override
+	public boolean supportsTeamMembershipChanges() {
+		return serviceImpl.supportsTeamMembershipChanges();
+	}
+
+	@Override
+	public boolean supportsCookies() {
+		return serviceImpl.supportsCookies();
+	}
+
+	@Override
+	public String getCookie(UserModel model) {
+		return serviceImpl.getCookie(model);
+	}
+
+	@Override
+	public UserModel authenticate(char[] cookie) {
+		UserModel user = serviceImpl.authenticate(cookie);
+		setAccountType(user);
+		return user;
+	}
+
+	@Override
+	public UserModel authenticate(String username, char[] password) {
+		UserModel user = serviceImpl.authenticate(username, password);
+		setAccountType(user);
+		return user;
+	}
+	
+	@Override
+	public void logout(UserModel user) {
+		serviceImpl.logout(user);
+	}
+
+	@Override
+	public UserModel getUserModel(String username) {
+		UserModel user = serviceImpl.getUserModel(username);
+		setAccountType(user);
+		return user;
+	}
+
+	@Override
+	public boolean updateUserModel(UserModel model) {
+		return serviceImpl.updateUserModel(model);
+	}
+
+	@Override
+	public boolean updateUserModels(Collection<UserModel> models) {
+		return serviceImpl.updateUserModels(models);
+	}
+
+	@Override
+	public boolean updateUserModel(String username, UserModel model) {
+		if (model.isLocalAccount() || supportsCredentialChanges()) {
+			if (!model.isLocalAccount() && !supportsTeamMembershipChanges()) {
+				//  teams are externally controlled - copy from original model
+				UserModel existingModel = getUserModel(username);
+				
+				model = DeepCopier.copy(model);
+				model.teams.clear();
+				model.teams.addAll(existingModel.teams);
+			}
+			return serviceImpl.updateUserModel(username, model);
+		}
+		if (model.username.equals(username)) {
+			// passwords are not persisted by the backing user service
+			model.password = null;
+			if (!model.isLocalAccount() && !supportsTeamMembershipChanges()) {
+				//  teams are externally controlled- copy from original model
+				UserModel existingModel = getUserModel(username);
+				
+				model = DeepCopier.copy(model);
+				model.teams.clear();
+				model.teams.addAll(existingModel.teams);
+			}
+			return serviceImpl.updateUserModel(username, model);
+		}
+		logger.error("Users can not be renamed!");
+		return false;
+	}
+	@Override
+	public boolean deleteUserModel(UserModel model) {
+		return serviceImpl.deleteUserModel(model);
+	}
+
+	@Override
+	public boolean deleteUser(String username) {
+		return serviceImpl.deleteUser(username);
+	}
+
+	@Override
+	public List<String> getAllUsernames() {
+		return serviceImpl.getAllUsernames();
+	}
+
+	@Override
+	public List<UserModel> getAllUsers() {
+		List<UserModel> users = serviceImpl.getAllUsers();
+    	for (UserModel user : users) {
+    		setAccountType(user);
+    	}
+		return users; 
+	}
+
+	@Override
+	public List<String> getAllTeamNames() {
+		return serviceImpl.getAllTeamNames();
+	}
+
+	@Override
+	public List<TeamModel> getAllTeams() {
+		return serviceImpl.getAllTeams();
+	}
+
+	@Override
+	public List<String> getTeamnamesForRepositoryRole(String role) {
+		return serviceImpl.getTeamnamesForRepositoryRole(role);
+	}
+
+	@Override
+	@Deprecated
+	public boolean setTeamnamesForRepositoryRole(String role, List<String> teamnames) {
+		return serviceImpl.setTeamnamesForRepositoryRole(role, teamnames);
+	}
+
+	@Override
+	public TeamModel getTeamModel(String teamname) {
+		return serviceImpl.getTeamModel(teamname);
+	}
+
+	@Override
+	public boolean updateTeamModel(TeamModel model) {
+		return serviceImpl.updateTeamModel(model);
+	}
+
+	@Override
+	public boolean updateTeamModels(Collection<TeamModel> models) {
+		return serviceImpl.updateTeamModels(models);
+	}
+
+	@Override
+	public boolean updateTeamModel(String teamname, TeamModel model) {
+		if (!supportsTeamMembershipChanges()) {
+			// teams are externally controlled - copy from original model
+			TeamModel existingModel = getTeamModel(teamname);
+			
+			model = DeepCopier.copy(model);
+			model.users.clear();
+			model.users.addAll(existingModel.users);
+		}
+		return serviceImpl.updateTeamModel(teamname, model);
+	}
+
+	@Override
+	public boolean deleteTeamModel(TeamModel model) {
+		return serviceImpl.deleteTeamModel(model);
+	}
+
+	@Override
+	public boolean deleteTeam(String teamname) {
+		return serviceImpl.deleteTeam(teamname);
+	}
+
+	@Override
+	public List<String> getUsernamesForRepositoryRole(String role) {
+		return serviceImpl.getUsernamesForRepositoryRole(role);
+	}
+
+	@Override
+	@Deprecated
+	public boolean setUsernamesForRepositoryRole(String role, List<String> usernames) {
+		return serviceImpl.setUsernamesForRepositoryRole(role, usernames);
+	}
+
+	@Override
+	public boolean renameRepositoryRole(String oldRole, String newRole) {
+		return serviceImpl.renameRepositoryRole(oldRole, newRole);
+	}
+
+	@Override
+	public boolean deleteRepositoryRole(String role) {
+		return serviceImpl.deleteRepositoryRole(role);
+	}
+	
+	protected boolean isLocalAccount(String username) {
+		UserModel user = getUserModel(username);
+		return user != null && user.isLocalAccount();
+	}
+	
+	protected void setAccountType(UserModel user) {
+		if (user != null) {
+			if (!StringUtils.isEmpty(user.password)
+					&& !Constants.EXTERNAL_ACCOUNT.equalsIgnoreCase(user.password)
+					&& !"StoredInLDAP".equalsIgnoreCase(user.password)) {
+				user.accountType = AccountType.LOCAL;
+			} else {
+				user.accountType = getAccountType();
+			}
+		}
+	}
+	
+	protected AccountType getAccountType() {
+		return AccountType.LOCAL;
+	}
+}
diff --git a/src/main/java/com/gitblit/IStoredSettings.java b/src/main/java/com/gitblit/IStoredSettings.java
new file mode 100644
index 0000000..acb9fc6
--- /dev/null
+++ b/src/main/java/com/gitblit/IStoredSettings.java
@@ -0,0 +1,344 @@
+/*
+ * Copyright 2011 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.gitblit.utils.StringUtils;
+
+/**
+ * Base class for stored settings implementations.
+ * 
+ * @author James Moger
+ * 
+ */
+public abstract class IStoredSettings {
+
+	protected final Logger logger;
+
+	protected final Properties overrides = new Properties();
+
+	public IStoredSettings(Class<? extends IStoredSettings> clazz) {
+		logger = LoggerFactory.getLogger(clazz);
+	}
+
+	protected abstract Properties read();
+
+	private Properties getSettings() {
+		Properties props = read();
+		props.putAll(overrides);
+		return props;
+	}
+
+	/**
+	 * 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
+	 */
+	public List<String> getAllKeys(String startingWith) {
+		List<String> keys = new ArrayList<String>();
+		Properties props = getSettings();
+		if (StringUtils.isEmpty(startingWith)) {
+			keys.addAll(props.stringPropertyNames());
+		} else {
+			startingWith = startingWith.toLowerCase();
+			for (Object o : props.keySet()) {
+				String key = o.toString();
+				if (key.toLowerCase().startsWith(startingWith)) {
+					keys.add(key);
+				}
+			}
+		}
+		return keys;
+	}
+
+	/**
+	 * 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
+	 */
+	public boolean getBoolean(String name, boolean defaultValue) {
+		Properties props = getSettings();
+		if (props.containsKey(name)) {
+			String value = props.getProperty(name);
+			if (!StringUtils.isEmpty(value)) {
+				return Boolean.parseBoolean(value.trim());
+			}
+		}
+		return defaultValue;
+	}
+
+	/**
+	 * 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
+	 */
+	public int getInteger(String name, int defaultValue) {
+		Properties props = getSettings();
+		if (props.containsKey(name)) {
+			try {
+				String value = props.getProperty(name);
+				if (!StringUtils.isEmpty(value)) {
+					return Integer.parseInt(value.trim());
+				}
+			} catch (NumberFormatException e) {
+				logger.warn("Failed to parse integer for " + name + " using default of "
+						+ defaultValue);
+			}
+		}
+		return defaultValue;
+	}
+
+	/**
+	 * 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
+	 */
+	public long getLong(String name, long defaultValue) {
+		Properties props = getSettings();
+		if (props.containsKey(name)) {
+			try {
+				String value = props.getProperty(name);
+				if (!StringUtils.isEmpty(value)) {
+					return Long.parseLong(value.trim());
+				}
+			} catch (NumberFormatException e) {
+				logger.warn("Failed to parse long for " + name + " using default of "
+						+ defaultValue);
+			}
+		}
+		return defaultValue;
+	}
+	
+	/**
+	 * Returns an int filesize from a string value such as 50m or 50mb
+	 * @param name
+	 * @param defaultValue
+	 * @return an int filesize or defaultValue if the key does not exist or can
+	 *         not be parsed
+	 */
+	public int getFilesize(String name, int defaultValue) {
+		String val = getString(name, null);
+		if (StringUtils.isEmpty(val)) {
+			return defaultValue;
+		}
+		return com.gitblit.utils.FileUtils.convertSizeToInt(val, defaultValue);
+	}
+	
+	/**
+	 * Returns an long filesize from a string value such as 50m or 50mb
+	 * @param n
+	 * @param defaultValue
+	 * @return a long filesize or defaultValue if the key does not exist or can
+	 *         not be parsed
+	 */
+	public long getFilesize(String key, long defaultValue) {
+		String val = getString(key, null);
+		if (StringUtils.isEmpty(val)) {
+			return defaultValue;
+		}
+		return com.gitblit.utils.FileUtils.convertSizeToLong(val, defaultValue);
+	}
+
+	/**
+	 * 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
+	 */
+	public char getChar(String name, char defaultValue) {
+		Properties props = getSettings();
+		if (props.containsKey(name)) {
+			String value = props.getProperty(name);
+			if (!StringUtils.isEmpty(value)) {
+				return value.trim().charAt(0);
+			}
+		}
+		return defaultValue;
+	}
+
+	/**
+	 * 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
+	 */
+	public String getString(String name, String defaultValue) {
+		Properties props = getSettings();
+		if (props.containsKey(name)) {
+			String value = props.getProperty(name);
+			if (value != null) {
+				return value.trim();
+			}
+		}
+		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
+	 */
+	public String getRequiredString(String name) {
+		Properties props = getSettings();
+		if (props.containsKey(name)) {
+			String value = props.getProperty(name);
+			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
+	 */
+	public List<String> getStrings(String name) {
+		return getStrings(name, " ");
+	}
+
+	/**
+	 * Returns a list of strings from the specified key using the specified
+	 * string separator.
+	 * 
+	 * @param name
+	 * @param separator
+	 * @return list of strings
+	 */
+	public List<String> getStrings(String name, String separator) {
+		List<String> strings = new ArrayList<String>();
+		Properties props = getSettings();
+		if (props.containsKey(name)) {
+			String value = props.getProperty(name);
+			strings = StringUtils.getStringsFromValue(value, separator);
+		}
+		return strings;
+	}
+	
+	/**
+	 * Returns a list of space-separated integers from the specified key.
+	 * 
+	 * @param name
+	 * @return list of strings
+	 */
+	public List<Integer> getIntegers(String name) {
+		return getIntegers(name, " ");
+	}
+
+	/**
+	 * Returns a list of integers from the specified key using the specified
+	 * string separator.
+	 * 
+	 * @param name
+	 * @param separator
+	 * @return list of integers
+	 */
+	public List<Integer> getIntegers(String name, String separator) {
+		List<Integer> ints = new ArrayList<Integer>();
+		Properties props = getSettings();
+		if (props.containsKey(name)) {
+			String value = props.getProperty(name);
+			List<String> strings = StringUtils.getStringsFromValue(value, separator);
+			for (String str : strings) {
+				try {
+					int i = Integer.parseInt(str);
+					ints.add(i);
+				} catch (NumberFormatException e) {
+				}
+			}
+		}
+		return ints;
+	}
+	
+	/**
+	 * Returns a map of strings from the specified key.
+	 * 
+	 * @param name
+	 * @return map of string, string
+	 */
+	public Map<String, String> getMap(String name) {
+		Map<String, String> map = new LinkedHashMap<String, String>();
+		for (String string : getStrings(name)) {
+			String[] kvp = string.split("=", 2);
+			String key = kvp[0];
+			String value = kvp[1];				
+			map.put(key,  value);
+		}
+		return map;
+	}
+
+	/**
+	 * Override the specified key with the specified value.
+	 * 
+	 * @param key
+	 * @param value
+	 */
+	public void overrideSetting(String key, String value) {
+		overrides.put(key, value);
+	}
+
+	/**
+	 * Override the specified key with the specified value.
+	 * 
+	 * @param key
+	 * @param value
+	 */
+	public void overrideSetting(String key, int value) {
+		overrides.put(key, "" + value);
+	}
+
+	/**
+	 * Updates the values for the specified keys and persists the entire
+	 * configuration file.
+	 * 
+	 * @param map
+	 *            of key, value pairs
+	 * @return true if successful
+	 */
+	public abstract boolean saveSettings(Map<String, String> updatedSettings);
+}
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/IUserService.java b/src/main/java/com/gitblit/IUserService.java
new file mode 100644
index 0000000..a57b0da
--- /dev/null
+++ b/src/main/java/com/gitblit/IUserService.java
@@ -0,0 +1,325 @@
+/*
+ * Copyright 2011 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit;
+
+import java.util.Collection;
+import java.util.List;
+
+import com.gitblit.models.TeamModel;
+import com.gitblit.models.UserModel;
+
+/**
+ * Implementations of IUserService control all aspects of UserModel objects and
+ * user authentication.
+ * 
+ * @author James Moger
+ * 
+ */
+public interface IUserService {
+
+	/**
+	 * 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
+	 */
+	void setup(IStoredSettings settings);
+
+	/**
+	 * 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
+	 */
+	String getCookie(UserModel model);
+
+	/**
+	 * Authenticate a user based on their cookie.
+	 * 
+	 * @param cookie
+	 * @return a user object or null
+	 */
+	UserModel authenticate(char[] cookie);
+
+	/**
+	 * Authenticate a user based on a username and password.
+	 * 
+	 * @param username
+	 * @param password
+	 * @return a user object or null
+	 */
+	UserModel authenticate(String username, char[] password);
+
+	/**
+	 * 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
+	 */
+	UserModel getUserModel(String username);
+
+	/**
+	 * Updates/writes a complete user object.
+	 * 
+	 * @param model
+	 * @return true if update is successful
+	 */
+	boolean updateUserModel(UserModel model);
+
+	/**
+	 * 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
+	 *            the user object to use for username
+	 * @return true if update is successful
+	 */
+	boolean updateUserModel(String username, UserModel model);
+
+	/**
+	 * Deletes the user object from the user service.
+	 * 
+	 * @param model
+	 * @return true if successful
+	 */
+	boolean deleteUserModel(UserModel model);
+
+	/**
+	 * Delete the user object with the specified username
+	 * 
+	 * @param username
+	 * @return true if successful
+	 */
+	boolean deleteUser(String username);
+
+	/**
+	 * 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
+	 */
+	List<UserModel> getAllUsers();
+
+	/**
+	 * 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
+	 * @return true if successful
+	 * @since 0.8.0
+	 */
+	@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
+	 *            the team object to use for teamname
+	 * @return true if update is successful
+	 * @since 0.8.0
+	 */
+	boolean updateTeamModel(String teamname, TeamModel model);
+
+	/**
+	 * Deletes the team object from the user service.
+	 * 
+	 * @param model
+	 * @return true if successful
+	 * @since 0.8.0
+	 */
+	boolean deleteTeamModel(TeamModel model);
+
+	/**
+	 * 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
+	 * @since 0.8.0
+	 */
+	List<String> getUsernamesForRepositoryRole(String role);
+
+	/**
+	 * 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
+	 * @return true if successful
+	 */
+	@Deprecated
+	boolean setUsernamesForRepositoryRole(String role, List<String> usernames);
+
+	/**
+	 * Renames a repository role.
+	 * 
+	 * @param oldRole
+	 * @param newRole
+	 * @return true if successful
+	 */
+	boolean renameRepositoryRole(String oldRole, String newRole);
+
+	/**
+	 * Removes a repository role from all users.
+	 * 
+	 * @param role
+	 * @return true if successful
+	 */
+	boolean deleteRepositoryRole(String role);
+
+	/**
+	 * @See java.lang.Object.toString();
+	 * @return string representation of the login service
+	 */
+	String toString();
+}
diff --git a/src/main/java/com/gitblit/JsonServlet.java b/src/main/java/com/gitblit/JsonServlet.java
new file mode 100644
index 0000000..3ee4a27
--- /dev/null
+++ b/src/main/java/com/gitblit/JsonServlet.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright 2011 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.lang.reflect.Type;
+import java.text.MessageFormat;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.gitblit.utils.JsonUtils;
+import com.gitblit.utils.StringUtils;
+
+/**
+ * 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() {
+		super();
+		logger = LoggerFactory.getLogger(getClass());
+	}
+
+	/**
+	 * Processes an gson request.
+	 * 
+	 * @param request
+	 * @param response
+	 * @throws javax.servlet.ServletException
+	 * @throws java.io.IOException
+	 */
+	protected abstract void processRequest(HttpServletRequest request, HttpServletResponse response)
+			throws ServletException, IOException;
+
+	@Override
+	protected void doPost(HttpServletRequest request, HttpServletResponse response)
+			throws ServletException, java.io.IOException {
+		processRequest(request, response);
+	}
+
+	@Override
+	protected void doGet(HttpServletRequest request, HttpServletResponse response)
+			throws ServletException, IOException {
+		processRequest(request, response);
+	}
+
+	protected <X> X deserialize(HttpServletRequest request, HttpServletResponse response,
+			Class<X> clazz) throws IOException {
+		String json = readJson(request, response);
+		if (StringUtils.isEmpty(json)) {
+			return null;
+		}
+
+		X object = JsonUtils.fromJsonString(json.toString(), clazz);
+		return object;
+	}
+
+	protected <X> X deserialize(HttpServletRequest request, HttpServletResponse response, Type type)
+			throws IOException {
+		String json = readJson(request, response);
+		if (StringUtils.isEmpty(json)) {
+			return null;
+		}
+
+		X object = JsonUtils.fromJsonString(json.toString(), type);
+		return object;
+	}
+
+	private String readJson(HttpServletRequest request, HttpServletResponse response)
+			throws IOException {
+		BufferedReader reader = request.getReader();
+		StringBuilder json = new StringBuilder();
+		String line = null;
+		while ((line = reader.readLine()) != null) {
+			json.append(line);
+		}
+		reader.close();
+
+		if (json.length() == 0) {
+			logger.error(MessageFormat.format("Failed to receive json data from {0}",
+					request.getRemoteAddr()));
+			response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
+			return null;
+		}
+		return json.toString();
+	}
+
+	protected void serialize(HttpServletResponse response, Object o) throws IOException {
+		if (o != null) {
+			// Send JSON response
+			String json = JsonUtils.toJsonString(o);
+			response.setCharacterEncoding(Constants.ENCODING);
+			response.setContentType("application/json");
+			response.getWriter().append(json);
+		}
+	}
+}
diff --git a/src/main/java/com/gitblit/Launcher.java b/src/main/java/com/gitblit/Launcher.java
new file mode 100644
index 0000000..ed465f0
--- /dev/null
+++ b/src/main/java/com/gitblit/Launcher.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright 2011 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.security.ProtectionDomain;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Launch helper class that adds all jars found in the local "lib" & "ext"
+ * 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 {
+
+	public static final boolean DEBUG = false;
+
+	/**
+	 * Parameters of the method to add an URL to the System classes.
+	 */
+	private static final Class<?>[] PARAMETERS = new Class[] { URL.class };
+
+	public static void main(String[] args) {
+		if (DEBUG) {
+			System.out.println("jcp=" + System.getProperty("java.class.path"));
+			ProtectionDomain protectionDomain = Launcher.class.getProtectionDomain();
+			System.out.println("launcher="
+					+ protectionDomain.getCodeSource().getLocation().toExternalForm());
+		}
+
+		// Load the JARs in the lib and ext folder
+		String[] folders = new String[] { "lib", "ext" };
+		List<File> jars = new ArrayList<File>();
+		for (String folder : folders) {
+			if (folder == null) {
+				continue;
+			}
+			File libFolder = new File(folder);
+			if (!libFolder.exists()) {
+				continue;
+			}
+			List<File> found = findJars(libFolder.getAbsoluteFile());
+			jars.addAll(found);
+		}
+		// 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);
+		Collections.reverse(jars);
+
+		if (jars.size() == 0) {
+			for (String folder : folders) {
+				File libFolder = new File(folder);
+				// this is a test of adding a comment
+				// more really interesting things
+				System.err.println("Failed to find any JARs in " + libFolder.getPath());
+			}
+			System.exit(-1);
+		} else {
+			for (File jar : jars) {
+				try {
+					jar.canRead();
+					addJarFile(jar);
+				} catch (Throwable t) {
+					t.printStackTrace();
+				}
+			}
+		}
+
+		// Start Server
+		GitBlitServer.main(args);
+	}
+
+	public static List<File> findJars(File folder) {
+		List<File> jars = new ArrayList<File>();
+		if (folder.exists()) {
+			File[] libs = folder.listFiles(new FileFilter() {
+				@Override
+				public boolean accept(File file) {
+					return !file.isDirectory() && file.getName().toLowerCase().endsWith(".jar");
+				}
+			});
+			if (libs != null && libs.length > 0) {
+				jars.addAll(Arrays.asList(libs));
+				if (DEBUG) {
+					for (File jar : jars) {
+						System.out.println("found " + jar);
+					}
+				}
+			}
+		}
+
+		return jars;
+	}
+
+	/**
+	 * Adds a file to the classpath
+	 * 
+	 * @param f
+	 *            the file to be added
+	 * @throws IOException
+	 */
+	public static void addJarFile(File f) throws IOException {
+		if (f.getName().indexOf("-sources") > -1 || f.getName().indexOf("-javadoc") > -1) {
+			// don't add source or javadoc jars to runtime classpath
+			return;
+		}
+		URL u = f.toURI().toURL();
+		if (DEBUG) {
+			System.out.println("load=" + u.toExternalForm());
+		}
+		URLClassLoader sysloader = (URLClassLoader) ClassLoader.getSystemClassLoader();
+		Class<?> sysclass = URLClassLoader.class;
+		try {
+			Method method = sysclass.getDeclaredMethod("addURL", PARAMETERS);
+			method.setAccessible(true);
+			method.invoke(sysloader, new Object[] { u });
+		} catch (Throwable t) {
+			throw new IOException(MessageFormat.format(
+					"Error, could not add {0} to system classloader", f.getPath()), t);
+		}
+	}
+}
diff --git a/src/main/java/com/gitblit/LdapUserService.java b/src/main/java/com/gitblit/LdapUserService.java
new file mode 100644
index 0000000..39d564d
--- /dev/null
+++ b/src/main/java/com/gitblit/LdapUserService.java
@@ -0,0 +1,496 @@
+/*
+ * Copyright 2012 John Crygier
+ * 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;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.security.GeneralSecurityException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.gitblit.Constants.AccountType;
+import com.gitblit.models.TeamModel;
+import com.gitblit.models.UserModel;
+import com.gitblit.utils.ArrayUtils;
+import com.gitblit.utils.StringUtils;
+import com.unboundid.ldap.sdk.Attribute;
+import com.unboundid.ldap.sdk.ExtendedResult;
+import com.unboundid.ldap.sdk.LDAPConnection;
+import com.unboundid.ldap.sdk.LDAPException;
+import com.unboundid.ldap.sdk.LDAPSearchException;
+import com.unboundid.ldap.sdk.ResultCode;
+import com.unboundid.ldap.sdk.SearchResult;
+import com.unboundid.ldap.sdk.SearchResultEntry;
+import com.unboundid.ldap.sdk.SearchScope;
+import com.unboundid.ldap.sdk.extensions.StartTLSExtendedRequest;
+import com.unboundid.util.ssl.SSLUtil;
+import com.unboundid.util.ssl.TrustAllTrustManager;
+
+/**
+ * Implementation of an LDAP user service.
+ * 
+ * @author John Crygier
+ */
+public class LdapUserService extends GitblitUserService {
+
+	public static final Logger logger = LoggerFactory.getLogger(LdapUserService.class);
+
+	private IStoredSettings settings;
+    private AtomicLong lastLdapUserSync = new AtomicLong(0L);
+	
+	public LdapUserService() {
+		super();
+	}
+
+ 	private long getSynchronizationPeriod() {
+        final String cacheDuration = settings.getString(Keys.realm.ldap.ldapCachePeriod, "2 MINUTES");
+        try {
+            final String[] s = cacheDuration.split(" ", 2);
+            long duration = Long.parseLong(s[0]);
+            TimeUnit timeUnit = TimeUnit.valueOf(s[1]);
+            return timeUnit.toMillis(duration);
+        } catch (RuntimeException ex) {
+            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) {
+            if (System.currentTimeMillis() > (lastLdapUserSync.get() + getSynchronizationPeriod())) {
+            	logger.info("Synchronizing with LDAP @ " + settings.getRequiredString(Keys.realm.ldap.server));
+                final boolean deleteRemovedLdapUsers = settings.getBoolean(Keys.realm.ldap.synchronizeUsers.removeDeleted, true);
+                LDAPConnection ldapConnection = getLdapConnection();
+                if (ldapConnection != null) {
+                    try {
+                        String accountBase = settings.getString(Keys.realm.ldap.accountBase, "");
+                        String uidAttribute = settings.getString(Keys.realm.ldap.uid, "uid");
+                        String accountPattern = settings.getString(Keys.realm.ldap.accountPattern, "(&(objectClass=person)(sAMAccountName=${username}))");
+                        accountPattern = StringUtils.replace(accountPattern, "${username}", "*");
+
+                        SearchResult result = doSearch(ldapConnection, accountBase, accountPattern);
+                        if (result != null && result.getEntryCount() > 0) {
+                            final Map<String, UserModel> ldapUsers = new HashMap<String, UserModel>();
+
+                            for (SearchResultEntry loggingInUser : result.getSearchEntries()) {
+
+                                final String username = loggingInUser.getAttribute(uidAttribute).getValue();
+                                logger.debug("LDAP synchronizing: " + username);
+
+                                UserModel user = getUserModel(username);
+                                if (user == null) {
+                                    user = new UserModel(username);
+                                }
+
+                                if (!supportsTeamMembershipChanges())
+                                    getTeamsFromLdap(ldapConnection, username, loggingInUser, user);
+
+                                // Get User Attributes
+                                setUserAttributes(user, loggingInUser);
+
+                                // store in map
+                                ldapUsers.put(username.toLowerCase(), user);
+                            }
+
+                            if (deleteRemovedLdapUsers) {
+                                logger.debug("detecting removed LDAP users...");
+
+                                for (UserModel userModel : super.getAllUsers()) {
+                                    if (Constants.EXTERNAL_ACCOUNT.equals(userModel.password)) {
+                                        if (! ldapUsers.containsKey(userModel.username)) {
+                                            logger.info("deleting removed LDAP user " + userModel.username + " from backing user service");
+                                            super.deleteUser(userModel.username);
+                                        }
+                                    }
+                                }
+                            }
+
+                            super.updateUserModels(ldapUsers.values());
+
+                            if (!supportsTeamMembershipChanges()) {
+                                final Map<String, TeamModel> userTeams = new HashMap<String, TeamModel>();
+                                for (UserModel user : ldapUsers.values()) {
+                                    for (TeamModel userTeam : user.teams) {
+                                        userTeams.put(userTeam.name, userTeam);
+                                    }
+                                }
+                                updateTeamModels(userTeams.values());
+                            }
+                        }
+                        lastLdapUserSync.set(System.currentTimeMillis()); 
+                    } finally {
+                        ldapConnection.close();
+                    }
+                }
+            }
+        }
+    }
+	
+	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;
+				
+				SSLUtil sslUtil = new SSLUtil(new TrustAllTrustManager()); 
+				return new LDAPConnection(sslUtil.createSSLSocketFactory(), ldapUrl.getHost(), ldapPort, bindUserName, bindPassword);
+			} else {
+				if (ldapPort == -1)	// Default Port
+					ldapPort = 389;
+				
+				LDAPConnection conn = new LDAPConnection(ldapUrl.getHost(), ldapPort, bindUserName, bindPassword);
+
+				if (ldapUrl.getScheme().equalsIgnoreCase("ldap+tls")) {
+					SSLUtil sslUtil = new SSLUtil(new TrustAllTrustManager());
+
+					ExtendedResult extendedResult = conn.processExtendedOperation(
+						new StartTLSExtendedRequest(sslUtil.createSSLContext()));
+
+					if (extendedResult.getResultCode() != ResultCode.SUCCESS) {
+						throw new LDAPException(extendedResult.getResultCode());
+					}
+				}
+				return conn;
+			}
+		} catch (URISyntaxException e) {
+			logger.error("Bad LDAP URL, should be in the form: ldap(s|+tls)://<server>:<port>", e);
+		} catch (GeneralSecurityException e) {
+			logger.error("Unable to create SSL Connection", e);
+		} 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.
+	 *
+	 * @return false
+	 * @since 1.0.0
+	 */
+	@Override
+	public boolean supportsCredentialChanges() {
+		return false;
+	}
+	
+	/**
+	 * If no displayName pattern is defined then Gitblit can manage the display name.
+	 *
+	 * @return true if Gitblit can manage the user display name
+	 * @since 1.0.0
+	 */
+	@Override
+	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.
+	 *
+	 * @return true if Gitblit can manage the user email address
+	 * @since 1.0.0
+	 */
+	@Override
+	public boolean supportsEmailAddressChanges() {
+		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
+	 */	
+	public boolean supportsTeamMembershipChanges() {
+		return !settings.getBoolean(Keys.realm.ldap.maintainTeams, false);
+	}
+	
+	@Override
+	protected AccountType getAccountType() {
+		 return AccountType.LDAP;
+	}
+
+	@Override
+	public UserModel authenticate(String username, char[] password) {
+		if (isLocalAccount(username)) {
+			// local account, bypass LDAP authentication
+			return super.authenticate(username, password);
+		}
+		
+		String simpleUsername = getSimpleUsername(username);
+		
+		LDAPConnection ldapConnection = getLdapConnection();
+		if (ldapConnection != null) {
+			try {
+				// Find the logging in user's DN
+				String accountBase = settings.getString(Keys.realm.ldap.accountBase, "");
+				String accountPattern = settings.getString(Keys.realm.ldap.accountPattern, "(&(objectClass=person)(sAMAccountName=${username}))");
+				accountPattern = StringUtils.replace(accountPattern, "${username}", escapeLDAPSearchFilter(simpleUsername));
+
+				SearchResult result = doSearch(ldapConnection, accountBase, accountPattern);
+				if (result != null && result.getEntryCount() == 1) {
+					SearchResultEntry loggingInUser = result.getSearchEntries().get(0);
+					String loggingInUserDN = loggingInUser.getDN();
+
+					if (isAuthenticated(ldapConnection, loggingInUserDN, new String(password))) {
+						logger.debug("LDAP authenticated: " + username);
+
+						UserModel user = null;
+						synchronized (this) {
+							user = getUserModel(simpleUsername);
+							if (user == null)	// create user object for new authenticated user
+								user = new UserModel(simpleUsername);
+
+							// create a user cookie
+							if (StringUtils.isEmpty(user.cookie) && !ArrayUtils.isEmpty(password)) {
+								user.cookie = StringUtils.getSHA1(user.username + new String(password));
+							}
+
+							if (!supportsTeamMembershipChanges())
+								getTeamsFromLdap(ldapConnection, simpleUsername, loggingInUser, user);
+
+							// Get User Attributes
+							setUserAttributes(user, loggingInUser);
+
+							// Push the ldap looked up values to backing file
+							super.updateUserModel(user);
+							if (!supportsTeamMembershipChanges()) {
+								for (TeamModel userTeam : user.teams)
+									updateTeamModel(userTeam);
+							}
+						}
+						
+						return user;
+					}
+				}
+			} finally {
+				ldapConnection.close();
+			}
+		}
+		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) {
+		if (!supportsTeamMembershipChanges()) {
+			List<String> admins = settings.getStrings(Keys.realm.ldap.admins);
+			// if we have defined administrative teams, then set admin flag
+			// otherwise leave admin flag unchanged
+			if (!ArrayUtils.isEmpty(admins)) {
+				user.canAdmin = false;
+				for (String admin : admins) {
+					if (admin.startsWith("@")) { // Team
+						if (user.getTeam(admin.substring(1)) != null)
+							user.canAdmin = true;
+					} else
+						if (user.getName().equalsIgnoreCase(admin))
+							user.canAdmin = true;
+				}
+			}
+		}
+	}
+	
+	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, "");		
+		if (!StringUtils.isEmpty(displayName)) {
+			// Replace embedded ${} with attributes
+			if (displayName.contains("${")) {
+				for (Attribute userAttribute : userEntry.getAttributes())
+					displayName = StringUtils.replace(displayName, "${" + userAttribute.getName() + "}", userAttribute.getValue());
+
+				user.displayName = displayName;
+			} else {
+				Attribute attribute = userEntry.getAttribute(displayName);
+				if (attribute != null && attribute.hasValue()) {
+					user.displayName = attribute.getValue();
+				}
+			}
+		}
+		
+		// Get email address Attribute
+		String email = settings.getString(Keys.realm.ldap.email, "");
+		if (!StringUtils.isEmpty(email)) {
+			if (email.contains("${")) {
+				for (Attribute userAttribute : userEntry.getAttributes())
+					email = StringUtils.replace(email, "${" + userAttribute.getName() + "}", userAttribute.getValue());
+
+				user.emailAddress = email;
+			} else {
+				Attribute attribute = userEntry.getAttribute(email);
+				if (attribute != null && attribute.hasValue()) {
+					user.emailAddress = attribute.getValue();
+				}
+			}
+		}
+	}
+
+	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;		
+	}
+
+	private SearchResult doSearch(LDAPConnection ldapConnection, String base, String filter) {
+		try {
+			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
+			ldapConnection.bind(userDn, password);
+			return true;
+		} catch (LDAPException e) {
+			logger.error("Error authenticating user", e);
+			return false;
+		}
+	}
+
+    @Override
+    public List<String> getAllUsernames() {
+        synchronizeLdapUsers();
+        return super.getAllUsernames();
+    }
+
+    @Override
+    public List<UserModel> getAllUsers() {
+        synchronizeLdapUsers();
+        return super.getAllUsers();
+    }
+	
+	/**
+	 * Returns a simple username without any domain prefixes.
+	 * 
+	 * @param username
+	 * @return a simple username
+	 */
+	protected String getSimpleUsername(String username) {
+		int lastSlash = username.lastIndexOf('\\');
+		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();
+		for (int i = 0; i < filter.length(); i++) {
+			char curChar = filter.charAt(i);
+			switch (curChar) {
+			case '\\':
+				sb.append("\\5c");
+				break;
+			case '*':
+				sb.append("\\2a");
+				break;
+			case '(':
+				sb.append("\\28");
+				break;
+			case ')':
+				sb.append("\\29");
+				break;
+			case '\u0000': 
+				sb.append("\\00"); 
+				break;
+			default:
+				sb.append(curChar);
+			}
+		}
+		return sb.toString();
+	}
+}
diff --git a/src/main/java/com/gitblit/LogoServlet.java b/src/main/java/com/gitblit/LogoServlet.java
new file mode 100644
index 0000000..823e464
--- /dev/null
+++ b/src/main/java/com/gitblit/LogoServlet.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2013 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * 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");
+		if (file.exists()) {
+			return Math.max(lastModified, file.lastModified());
+		} else {
+			return lastModified;
+		}
+	}
+	
+	@Override
+	protected void doGet(HttpServletRequest request, HttpServletResponse response)
+			throws ServletException, IOException {
+		InputStream is = null;
+		try {
+			String contentType = null;
+			File file = GitBlit.getFileOrFolder(Keys.web.headerLogo, "${baseFolder}/logo.png");
+			if (file.exists()) {
+				// custom logo
+				ServletContext context = request.getSession().getServletContext();
+				contentType = context.getMimeType(file.getName());
+				response.setContentLength((int) file.length());
+				response.setDateHeader("Last-Modified", Math.max(lastModified, file.lastModified()));
+				is = new FileInputStream(file);
+			} else {
+				// default logo
+				response.setDateHeader("Last-Modified", lastModified);
+				is = getClass().getResourceAsStream("/logo.png");
+			}			
+			if (contentType == null) {
+				contentType = "image/png";
+			}
+			response.setContentType(contentType);
+			response.setHeader("Cache-Control", "public, max-age=3600, must-revalidate");
+			OutputStream os = response.getOutputStream();
+			byte[] buf = new byte[4096];
+			int bytesRead = is.read(buf);
+			while (bytesRead != -1) {
+				os.write(buf, 0, bytesRead);
+				bytesRead = is.read(buf);
+			}
+			os.flush();
+		} catch (Exception e) {
+			e.printStackTrace();
+		} finally {
+			if(is != null) {
+				is.close();
+			}
+		}
+	}
+}
diff --git a/src/com/gitblit/LuceneExecutor.java b/src/main/java/com/gitblit/LuceneExecutor.java
similarity index 100%
rename from src/com/gitblit/LuceneExecutor.java
rename to src/main/java/com/gitblit/LuceneExecutor.java
diff --git a/src/main/java/com/gitblit/MailExecutor.java b/src/main/java/com/gitblit/MailExecutor.java
new file mode 100644
index 0000000..c4e776a
--- /dev/null
+++ b/src/main/java/com/gitblit/MailExecutor.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright 2011 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Properties;
+import java.util.Queue;
+import java.util.Set;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.regex.Pattern;
+
+import javax.mail.Authenticator;
+import javax.mail.Message;
+import javax.mail.PasswordAuthentication;
+import javax.mail.Session;
+import javax.mail.Transport;
+import javax.mail.internet.InternetAddress;
+import javax.mail.internet.MimeMessage;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.gitblit.utils.StringUtils;
+
+/**
+ * The mail executor handles sending email messages asynchronously from queue.
+ * 
+ * @author James Moger
+ * 
+ */
+public class MailExecutor implements Runnable {
+
+	private final Logger logger = LoggerFactory.getLogger(MailExecutor.class);
+
+	private final Queue<Message> queue = new ConcurrentLinkedQueue<Message>();
+
+	private final Session session;
+
+	private final IStoredSettings settings;
+
+	public MailExecutor(IStoredSettings settings) {
+		this.settings = settings;
+
+		final String mailUser = settings.getString(Keys.mail.username, null);
+		final String mailPassword = settings.getString(Keys.mail.password, null);
+		final boolean smtps = settings.getBoolean(Keys.mail.smtps, false);
+		boolean authenticate = !StringUtils.isEmpty(mailUser) && !StringUtils.isEmpty(mailPassword);
+		String server = settings.getString(Keys.mail.server, "");
+		if (StringUtils.isEmpty(server)) {
+			session = null;
+			return;
+		}
+		int port = settings.getInteger(Keys.mail.port, 25);
+		boolean isGMail = false;
+		if (server.equals("smtp.gmail.com")) {
+			port = 465;
+			isGMail = true;
+		}
+
+		Properties props = new Properties();
+		props.setProperty("mail.smtp.host", server);
+		props.setProperty("mail.smtp.port", String.valueOf(port));
+		props.setProperty("mail.smtp.auth", String.valueOf(authenticate));
+		props.setProperty("mail.smtp.auths", String.valueOf(authenticate));
+
+		if (isGMail || smtps) {
+			props.setProperty("mail.smtp.starttls.enable", "true");
+			props.put("mail.smtp.socketFactory.port", String.valueOf(port));
+			props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
+			props.put("mail.smtp.socketFactory.fallback", "false");
+		}
+
+		if (!StringUtils.isEmpty(mailUser) && !StringUtils.isEmpty(mailPassword)) {
+			// SMTP requires authentication
+			session = Session.getInstance(props, new Authenticator() {
+				protected PasswordAuthentication getPasswordAuthentication() {
+					PasswordAuthentication passwordAuthentication = new PasswordAuthentication(
+							mailUser, mailPassword);
+					return passwordAuthentication;
+				}
+			});
+		} else {
+			// SMTP does not require authentication
+			session = Session.getInstance(props);
+		}
+	}
+
+	/**
+	 * Indicates if the mail executor can send emails.
+	 * 
+	 * @return true if the mail executor is ready to send emails
+	 */
+	public boolean isReady() {
+		return session != null;
+	}
+
+
+	/**
+	 * Create a message.
+	 * 
+	 * @param toAddresses
+	 * @return a message
+	 */
+	public Message createMessage(String... toAddresses) {
+		return createMessage(Arrays.asList(toAddresses));
+	}
+
+	/**
+	 * Create a message.
+	 * 
+	 * @param toAddresses
+	 * @return a message
+	 */
+	public Message createMessage(List<String> toAddresses) {
+		MimeMessage message = new MimeMessage(session);
+		try {
+			String fromAddress = settings.getString(Keys.mail.fromAddress, null);
+			if (StringUtils.isEmpty(fromAddress)) {
+				fromAddress = "gitblit@gitblit.com";
+			}
+			InternetAddress from = new InternetAddress(fromAddress, "Gitblit");
+			message.setFrom(from);
+
+			// determine unique set of addresses
+			Set<String> uniques = new HashSet<String>();
+			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>();
+			for (String address : uniques) {
+				if (StringUtils.isEmpty(address)) {
+					continue;
+				}
+				if (validEmail.matcher(address).find()) {
+					try {
+						tos.add(new InternetAddress(address));
+					} catch (Throwable t) {
+					}
+				}
+			}			
+			message.setRecipients(Message.RecipientType.BCC,
+					tos.toArray(new InternetAddress[tos.size()]));
+			message.setSentDate(new Date());
+		} catch (Exception e) {
+			logger.error("Failed to properly create message", e);
+		}
+		return message;
+	}
+
+	/**
+	 * Returns the status of the mail queue.
+	 * 
+	 * @return true, if the queue is empty
+	 */
+	public boolean hasEmptyQueue() {
+		return queue.isEmpty();
+	}
+
+	/**
+	 * Queue's an email message to be sent.
+	 * 
+	 * @param message
+	 * @return true if the message was queued
+	 */
+	public boolean queue(Message message) {
+		if (!isReady()) {
+			return false;
+		}
+		try {
+			message.saveChanges();
+		} catch (Throwable t) {
+			logger.error("Failed to save changes to message!", t);
+		}
+		queue.add(message);
+		return true;
+	}
+
+	@Override
+	public void run() {
+		if (!queue.isEmpty()) {
+			if (session != null) {
+				// send message via mail server
+				List<Message> failures = new ArrayList<Message>();
+				Message message = null;
+				while ((message = queue.poll()) != null) {
+					try {
+						if (settings.getBoolean(Keys.mail.debug, false)) {
+							logger.info("send: " + StringUtils.trimString(message.getSubject(), 60));
+						}
+						Transport.send(message);
+					} catch (Throwable e) {
+						logger.error("Failed to send message", e);
+						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);
+	}
+}
diff --git a/src/main/java/com/gitblit/PagesFilter.java b/src/main/java/com/gitblit/PagesFilter.java
new file mode 100644
index 0000000..68ae31e
--- /dev/null
+++ b/src/main/java/com/gitblit/PagesFilter.java
@@ -0,0 +1,126 @@
+/*
+ * 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 org.eclipse.jgit.lib.Repository;
+
+import com.gitblit.Constants.AccessRestrictionType;
+import com.gitblit.models.RepositoryModel;
+import com.gitblit.models.UserModel;
+
+/**
+ * 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) {		
+		// get the repository name from the url by finding a known url suffix
+		String repository = "";		
+		Repository r = null;
+		int offset = 0;
+		while (r == null) {
+			int slash = url.indexOf('/', offset);
+			if (slash == -1) {
+				repository = url;
+			} else {
+				repository = url.substring(0, slash);
+			}
+			r = GitBlit.self().getRepository(repository, false);
+			if (r == null) {
+				// try again
+				offset = slash + 1;	
+			} else {
+				// close the repo
+				r.close();
+			}			
+			if (repository.equals(url)) {
+				// either only repository in url or no repository found
+				break;
+			}
+		}
+		return repository;
+	}
+
+	/**
+	 * Analyze the url and returns the action of the request.
+	 * 
+	 * @param cloneUrl
+	 * @return action of the request
+	 */
+	@Override
+	protected String getUrlRequestAction(String suffix) {
+		return "VIEW";
+	}
+
+	/**
+	 * Determine if a non-existing repository can be created using this filter.
+	 *  
+	 * @return true if the filter allows repository creation
+	 */
+	@Override
+	protected boolean isCreationAllowed() {
+		return false;
+	}
+
+	/**
+	 * Determine if the action may be executed on the repository.
+	 * 
+	 * @param repository
+	 * @param action
+	 * @return true if the action may be performed
+	 */
+	@Override
+	protected boolean isActionAllowed(RepositoryModel repository, String action) {
+		return true;
+	}
+	
+	/**
+	 * Determine if the repository requires authentication.
+	 * 
+	 * @param repository
+	 * @param action
+	 * @return true if authentication required
+	 */
+	@Override
+	protected boolean requiresAuthentication(RepositoryModel repository, String action) {
+		return repository.accessRestriction.atLeast(AccessRestrictionType.VIEW);
+	}
+
+	/**
+	 * 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) {		
+		return user.canView(repository);
+	}
+}
diff --git a/src/main/java/com/gitblit/PagesServlet.java b/src/main/java/com/gitblit/PagesServlet.java
new file mode 100644
index 0000000..fc71bc5
--- /dev/null
+++ b/src/main/java/com/gitblit/PagesServlet.java
@@ -0,0 +1,261 @@
+/*
+ * 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.IOException;
+import java.text.MessageFormat;
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevTree;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.gitblit.models.RefModel;
+import com.gitblit.utils.ArrayUtils;
+import com.gitblit.utils.JGitUtils;
+import com.gitblit.utils.MarkdownUtils;
+import com.gitblit.utils.StringUtils;
+
+/**
+ * Serves the content of a gh-pages branch.
+ * 
+ * @author James Moger
+ * 
+ */
+public class PagesServlet extends HttpServlet {
+
+	private static final long serialVersionUID = 1L;
+
+	private transient Logger logger = LoggerFactory.getLogger(PagesServlet.class);
+
+	public PagesServlet() {
+		super();
+	}
+
+	/**
+	 * Returns an url to this servlet for the specified parameters.
+	 * 
+	 * @param baseURL
+	 * @param repository
+	 * @param path
+	 * @return an url
+	 */
+	public static String asLink(String baseURL, String repository, String path) {
+		if (baseURL.length() > 0 && baseURL.charAt(baseURL.length() - 1) == '/') {
+			baseURL = baseURL.substring(0, baseURL.length() - 1);
+		}
+		return baseURL + Constants.PAGES + repository + "/" + (path == null ? "" : ("/" + path));
+	}
+
+	/**
+	 * Retrieves the specified resource from the gh-pages branch of the
+	 * repository.
+	 * 
+	 * @param request
+	 * @param response
+	 * @throws javax.servlet.ServletException
+	 * @throws java.io.IOException
+	 */
+	private void processRequest(HttpServletRequest request, HttpServletResponse response)
+			throws ServletException, IOException {
+		String path = request.getPathInfo();
+		if (path.toLowerCase().endsWith(".git")) {
+			// forward to url with trailing /
+			// this is important for relative pages links
+			response.sendRedirect(request.getServletPath() + path + "/");
+			return;
+		}
+		if (path.charAt(0) == '/') {
+			// strip leading /
+			path = path.substring(1);
+		}
+
+		// determine repository and resource from url
+		String repository = "";
+		String resource = "";
+		Repository r = null;
+		int offset = 0;
+		while (r == null) {
+			int slash = path.indexOf('/', offset);
+			if (slash == -1) {
+				repository = path;
+			} else {
+				repository = path.substring(0, slash);
+			}
+			r = GitBlit.self().getRepository(repository, false);
+			offset = slash + 1;
+			if (offset > 0) {
+				resource = path.substring(offset);
+			}
+			if (repository.equals(path)) {
+				// either only repository in url or no repository found
+				break;
+			}
+		}
+
+		ServletContext context = request.getSession().getServletContext();
+
+		try {
+			if (r == null) {
+				// repository not found!
+				String mkd = MessageFormat.format(
+						"# Error\nSorry, no valid **repository** specified in this url: {0}!",
+						repository);
+				error(response, mkd);
+				return;
+			}
+
+			// retrieve the content from the repository
+			RefModel pages = JGitUtils.getPagesBranch(r);
+			RevCommit commit = JGitUtils.getCommit(r, pages.getObjectId().getName());
+
+			if (commit == null) {
+				// branch not found!
+				String mkd = MessageFormat.format(
+						"# Error\nSorry, the repository {0} does not have a **gh-pages** branch!",
+						repository);
+				error(response, mkd);
+				r.close();
+				return;
+			}
+
+			String [] encodings = GitBlit.getEncodings();
+
+			RevTree tree = commit.getTree();
+			byte[] content = null;
+			if (StringUtils.isEmpty(resource)) {
+				// find resource
+				List<String> markdownExtensions = GitBlit.getStrings(Keys.web.markdownExtensions);
+				List<String> extensions = new ArrayList<String>(markdownExtensions.size() + 2);
+				extensions.add("html");
+				extensions.add("htm");
+				extensions.addAll(markdownExtensions);
+				for (String ext : extensions){
+					String file = "index." + ext;
+					String stringContent = JGitUtils.getStringContent(r, tree, file, encodings);
+					if(stringContent == null){
+						continue;
+					}
+					content = stringContent.getBytes(Constants.ENCODING);
+					if (content != null) {
+						resource = file;
+						// assume text/html unless the servlet container
+						// overrides
+						response.setContentType("text/html; charset=" + Constants.ENCODING);
+						break;
+					}
+				}
+			} else {
+				// specific resource
+				try {
+					String contentType = context.getMimeType(resource);
+					if (contentType == null) {
+						contentType = "text/plain";
+					}
+					if (contentType.startsWith("text")) {
+						content = JGitUtils.getStringContent(r, tree, resource, encodings).getBytes(
+								Constants.ENCODING);
+					} else {
+						content = JGitUtils.getByteContent(r, tree, resource, false);
+					}
+					response.setContentType(contentType);
+				} catch (Exception e) {
+				}
+			}
+
+			// no content, try custom 404 page
+			if (ArrayUtils.isEmpty(content)) {
+				String custom404 = JGitUtils.getStringContent(r, tree, "404.html", encodings);
+				if (!StringUtils.isEmpty(custom404)) {
+					content = custom404.getBytes(Constants.ENCODING);
+				}
+
+				// still no content
+				if (ArrayUtils.isEmpty(content)) {
+					String str = MessageFormat.format(
+							"# Error\nSorry, the requested resource **{0}** was not found.",
+							resource);
+					content = MarkdownUtils.transformMarkdown(str).getBytes(Constants.ENCODING);
+				}
+
+				try {
+					// output the content
+					logger.warn("Pages 404: " + resource);
+					response.setStatus(HttpServletResponse.SC_NOT_FOUND);
+					response.getOutputStream().write(content);
+					response.flushBuffer();
+				} catch (Throwable t) {
+					logger.error("Failed to write page to client", t);
+				}
+				return;
+			}
+
+			// check to see if we should transform markdown files
+			for (String ext : GitBlit.getStrings(Keys.web.markdownExtensions)) {
+				if (resource.endsWith(ext)) {
+					String mkd = new String(content, Constants.ENCODING);
+					content = MarkdownUtils.transformMarkdown(mkd).getBytes(Constants.ENCODING);
+					response.setContentType("text/html; charset=" + Constants.ENCODING);
+					break;
+				}
+			}
+
+			try {
+				// output the content
+				response.setHeader("Cache-Control", "public, max-age=3600, must-revalidate");
+				response.setDateHeader("Last-Modified", JGitUtils.getCommitDate(commit).getTime());
+				response.getOutputStream().write(content);
+				response.flushBuffer();
+			} catch (Throwable t) {
+				logger.error("Failed to write page to client", t);
+			}
+
+			// close the repository
+			r.close();
+		} catch (Throwable t) {
+			logger.error("Failed to write page to client", t);
+		}
+	}
+
+	private void error(HttpServletResponse response, String mkd) throws ServletException,
+			IOException, ParseException {
+		String content = MarkdownUtils.transformMarkdown(mkd);
+		response.setContentType("text/html; charset=" + Constants.ENCODING);
+		response.getWriter().write(content);
+	}
+
+	@Override
+	protected void doPost(HttpServletRequest request, HttpServletResponse response)
+			throws ServletException, IOException {
+		processRequest(request, response);
+	}
+
+	@Override
+	protected void doGet(HttpServletRequest request, HttpServletResponse response)
+			throws ServletException, IOException {
+		processRequest(request, response);
+	}
+}
diff --git a/src/main/java/com/gitblit/RedmineUserService.java b/src/main/java/com/gitblit/RedmineUserService.java
new file mode 100644
index 0000000..d677e3e
--- /dev/null
+++ b/src/main/java/com/gitblit/RedmineUserService.java
@@ -0,0 +1,187 @@
+package com.gitblit;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.HttpURLConnection;
+
+import org.apache.wicket.util.io.IOUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.gitblit.Constants.AccountType;
+import com.gitblit.models.UserModel;
+import com.gitblit.utils.ArrayUtils;
+import com.gitblit.utils.ConnectionUtils;
+import com.gitblit.utils.StringUtils;
+import com.google.gson.Gson;
+
+/**
+ * Implementation of an Redmine user service.<br>
+ * you can login to gitblit with Redmine user id and api key.
+ */
+public class RedmineUserService extends GitblitUserService {
+
+    private final Logger logger = LoggerFactory.getLogger(RedmineUserService.class);
+
+    private IStoredSettings settings;
+
+    private String testingJson;
+
+    private class RedmineCurrent {
+        private class RedmineUser {
+            public String login;
+            public String firstname;
+            public String lastname;
+            public String mail;
+        }
+
+        public RedmineUser user;
+    }
+
+    public RedmineUserService() {
+        super();
+    }
+
+    @Override
+    public void setup(IStoredSettings settings) {
+        this.settings = settings;
+
+        String file = settings.getString(Keys.realm.redmine.backingUserService, "${baseFolder}/users.conf");
+        File realmFile = GitBlit.getFileOrFolder(file);
+
+        serviceImpl = createUserService(realmFile);
+        logger.info("Redmine User Service backed by " + serviceImpl.toString());
+    }
+
+    @Override
+    public boolean supportsCredentialChanges() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsDisplayNameChanges() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsEmailAddressChanges() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsTeamMembershipChanges() {
+        return false;
+    }
+    
+	 @Override
+	protected AccountType getAccountType() {
+		return AccountType.REDMINE;
+	}
+
+    @Override
+    public UserModel authenticate(String username, char[] password) {
+		if (isLocalAccount(username)) {
+			// local account, bypass Redmine authentication
+			return super.authenticate(username, password);
+		}
+
+        String jsonString = null;
+        try {
+        	// first attempt by username/password
+        	jsonString = getCurrentUserAsJson(username, password);
+        } catch (Exception e1) {
+        	logger.warn("Failed to authenticate via username/password against Redmine");
+        	try {
+        		// second attempt is by apikey
+        		jsonString = getCurrentUserAsJson(null, password);
+        		username = null;
+        	} catch (Exception e2) {
+        		logger.error("Failed to authenticate via apikey against Redmine", e2);
+        		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);
+        } catch (Exception e) {
+        	logger.error("Failed to deserialize Redmine json response: " + jsonString, e);
+        	return null;
+        }
+
+        if (StringUtils.isEmpty(username)) {
+        	// if the username has been reset because of apikey authentication
+        	// then use the email address of the user. this is the original
+        	// behavior as contributed by github/mallowlabs
+        	username = current.user.mail;
+        }
+
+        UserModel user = getUserModel(username);
+        if (user == null)	// create user object for new authenticated user
+        	user = new UserModel(username.toLowerCase());
+
+        // create a user cookie
+        if (StringUtils.isEmpty(user.cookie) && !ArrayUtils.isEmpty(password)) {
+        	user.cookie = StringUtils.getSHA1(user.username + new String(password));
+        }
+
+        // update user attributes from Redmine
+        user.accountType = getAccountType();
+        user.displayName = current.user.firstname + " " + current.user.lastname;
+        user.emailAddress = current.user.mail;
+        user.password = Constants.EXTERNAL_ACCOUNT;
+        if (!StringUtils.isEmpty(current.user.login)) {
+        	// only admin users can get login name
+        	// evidently this is an undocumented behavior of Redmine
+        	user.canAdmin = true;
+        }
+
+        // TODO consider Redmine group mapping for team membership
+        // http://www.redmine.org/projects/redmine/wiki/Rest_Users
+
+        // push the changes to the backing user service
+        super.updateUserModel(user);
+
+        return user;
+    }
+
+    private String getCurrentUserAsJson(String username, char [] password) throws IOException {
+        if (testingJson != null) { // for testing
+            return testingJson;
+        }
+
+        String url = this.settings.getString(Keys.realm.redmine.url, "");
+        if (!url.endsWith("/")) {
+        	url = url.concat("/");
+        }
+        HttpURLConnection http;
+        if (username == null) {
+        	// apikey authentication
+        	String apiKey = String.valueOf(password);
+        	String apiUrl = url + "users/current.json?key=" + apiKey;
+        	http = (HttpURLConnection) ConnectionUtils.openConnection(apiUrl, null, null);
+        } else {
+        	// username/password BASIC authentication
+        	String apiUrl = url + "users/current.json";
+        	http = (HttpURLConnection) ConnectionUtils.openConnection(apiUrl, username, password);
+        }
+        http.setRequestMethod("GET");
+        http.connect();
+        InputStreamReader reader = new InputStreamReader(http.getInputStream());
+        return IOUtils.toString(reader);
+    }
+    
+    /**
+     * set json response. do NOT invoke from production code.
+     * @param json json
+     */
+    public void setTestingCurrentUserAsJson(String json) {
+        this.testingJson = json;
+    }
+}
diff --git a/src/com/gitblit/RobotsTxtServlet.java b/src/main/java/com/gitblit/RobotsTxtServlet.java
similarity index 100%
rename from src/com/gitblit/RobotsTxtServlet.java
rename to src/main/java/com/gitblit/RobotsTxtServlet.java
diff --git a/src/com/gitblit/RpcFilter.java b/src/main/java/com/gitblit/RpcFilter.java
similarity index 100%
rename from src/com/gitblit/RpcFilter.java
rename to src/main/java/com/gitblit/RpcFilter.java
diff --git a/src/main/java/com/gitblit/RpcServlet.java b/src/main/java/com/gitblit/RpcServlet.java
new file mode 100644
index 0000000..f31bf86
--- /dev/null
+++ b/src/main/java/com/gitblit/RpcServlet.java
@@ -0,0 +1,348 @@
+/*
+ * Copyright 2011 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit;
+
+import java.io.IOException;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jgit.lib.Repository;
+
+import com.gitblit.Constants.RpcRequest;
+import com.gitblit.models.RefModel;
+import com.gitblit.models.RegistrantAccessPermission;
+import com.gitblit.models.RepositoryModel;
+import com.gitblit.models.ServerSettings;
+import com.gitblit.models.TeamModel;
+import com.gitblit.models.UserModel;
+import com.gitblit.utils.HttpUtils;
+import com.gitblit.utils.JGitUtils;
+import com.gitblit.utils.RpcUtils;
+
+/**
+ * Handles remote procedure calls.
+ * 
+ * @author James Moger
+ * 
+ */
+public class RpcServlet extends JsonServlet {
+
+	private static final long serialVersionUID = 1L;
+
+	public static final int PROTOCOL_VERSION = 5;
+
+	public RpcServlet() {
+		super();
+	}
+
+	/**
+	 * Processes an rpc request.
+	 * 
+	 * @param request
+	 * @param response
+	 * @throws javax.servlet.ServletException
+	 * @throws java.io.IOException
+	 */
+	@Override
+	protected void processRequest(HttpServletRequest request, HttpServletResponse response)
+			throws ServletException, IOException {
+		RpcRequest reqType = RpcRequest.fromName(request.getParameter("req"));
+		String objectName = request.getParameter("name");
+		logger.info(MessageFormat.format("Rpc {0} request from {1}", reqType,
+				request.getRemoteAddr()));
+
+		UserModel user = (UserModel) request.getUserPrincipal();
+
+		boolean allowManagement = user != null && user.canAdmin()
+				&& GitBlit.getBoolean(Keys.web.enableRpcManagement, false);
+
+		boolean allowAdmin = user != null && user.canAdmin()
+				&& GitBlit.getBoolean(Keys.web.enableRpcAdministration, false);
+
+		Object result = null;
+		if (RpcRequest.GET_PROTOCOL.equals(reqType)) {
+			// Return the protocol version
+			result = PROTOCOL_VERSION;
+		} else if (RpcRequest.LIST_REPOSITORIES.equals(reqType)) {
+			// Determine the Gitblit clone url
+			String gitblitUrl = HttpUtils.getGitblitURL(request);
+			StringBuilder sb = new StringBuilder();
+			sb.append(gitblitUrl);
+			sb.append(Constants.GIT_PATH);
+			sb.append("{0}");
+			String cloneUrl = sb.toString();
+
+			// list repositories
+			List<RepositoryModel> list = GitBlit.self().getRepositoryModels(user);
+			Map<String, RepositoryModel> repositories = new HashMap<String, RepositoryModel>();
+			for (RepositoryModel model : list) {
+				String url = MessageFormat.format(cloneUrl, model.name);
+				repositories.put(url, model);
+			}
+			result = repositories;
+		} else if (RpcRequest.LIST_BRANCHES.equals(reqType)) {
+			// list all local branches in all repositories accessible to user
+			Map<String, List<String>> localBranches = new HashMap<String, List<String>>();
+			List<RepositoryModel> models = GitBlit.self().getRepositoryModels(user);
+			for (RepositoryModel model : models) {
+				if (!model.hasCommits) {
+					// skip empty repository
+					continue;
+				}
+				if (model.isCollectingGarbage) {
+					// skip garbage collecting repository
+					logger.warn(MessageFormat.format("Temporarily excluding {0} from RPC, busy collecting garbage", model.name));
+					continue;
+				}
+				// get local branches
+				Repository repository = GitBlit.self().getRepository(model.name);
+				List<RefModel> refs = JGitUtils.getLocalBranches(repository, false, -1);
+				if (model.showRemoteBranches) {
+					// add remote branches if repository displays them
+					refs.addAll(JGitUtils.getRemoteBranches(repository, false, -1));
+				}
+				if (refs.size() > 0) {
+					List<String> branches = new ArrayList<String>();
+					for (RefModel ref : refs) {
+						branches.add(ref.getName());
+					}
+					localBranches.put(model.name, branches);
+				}
+				repository.close();
+			}
+			result = localBranches;
+		} else if (RpcRequest.LIST_USERS.equals(reqType)) {
+			// list users
+			List<String> names = GitBlit.self().getAllUsernames();
+			List<UserModel> users = new ArrayList<UserModel>();
+			for (String name : names) {
+				users.add(GitBlit.self().getUserModel(name));
+			}
+			result = users;
+		} else if (RpcRequest.LIST_TEAMS.equals(reqType)) {
+			// list teams
+			List<String> names = GitBlit.self().getAllTeamnames();
+			List<TeamModel> teams = new ArrayList<TeamModel>();
+			for (String name : names) {
+				teams.add(GitBlit.self().getTeamModel(name));
+			}
+			result = teams;
+		} else if (RpcRequest.CREATE_REPOSITORY.equals(reqType)) {
+			// create repository
+			RepositoryModel model = deserialize(request, response, RepositoryModel.class);
+			try {
+				GitBlit.self().updateRepositoryModel(model.name, model, true);
+			} catch (GitBlitException e) {
+				response.setStatus(failureCode);
+			}
+		} else if (RpcRequest.EDIT_REPOSITORY.equals(reqType)) {
+			// edit repository
+			RepositoryModel model = deserialize(request, response, RepositoryModel.class);
+			// name specifies original repository name in event of rename
+			String repoName = objectName;
+			if (repoName == null) {
+				repoName = model.name;
+			}
+			try {
+				GitBlit.self().updateRepositoryModel(repoName, model, false);
+			} catch (GitBlitException e) {
+				response.setStatus(failureCode);
+			}
+		} else if (RpcRequest.DELETE_REPOSITORY.equals(reqType)) {
+			// delete repository
+			RepositoryModel model = deserialize(request, response, RepositoryModel.class);
+			GitBlit.self().deleteRepositoryModel(model);
+		} else if (RpcRequest.CREATE_USER.equals(reqType)) {
+			// create user
+			UserModel model = deserialize(request, response, UserModel.class);
+			try {
+				GitBlit.self().updateUserModel(model.username, model, true);
+			} catch (GitBlitException e) {
+				response.setStatus(failureCode);
+			}
+		} else if (RpcRequest.EDIT_USER.equals(reqType)) {
+			// edit user
+			UserModel model = deserialize(request, response, UserModel.class);
+			// name parameter specifies original user name in event of rename
+			String username = objectName;
+			if (username == null) {
+				username = model.username;
+			}
+			try {
+				GitBlit.self().updateUserModel(username, model, false);
+			} catch (GitBlitException e) {
+				response.setStatus(failureCode);
+			}
+		} else if (RpcRequest.DELETE_USER.equals(reqType)) {
+			// delete user
+			UserModel model = deserialize(request, response, UserModel.class);
+			if (!GitBlit.self().deleteUser(model.username)) {
+				response.setStatus(failureCode);
+			}
+		} else if (RpcRequest.CREATE_TEAM.equals(reqType)) {
+			// create team
+			TeamModel model = deserialize(request, response, TeamModel.class);
+			try {
+				GitBlit.self().updateTeamModel(model.name, model, true);
+			} catch (GitBlitException e) {
+				response.setStatus(failureCode);
+			}
+		} else if (RpcRequest.EDIT_TEAM.equals(reqType)) {
+			// edit team
+			TeamModel model = deserialize(request, response, TeamModel.class);
+			// name parameter specifies original team name in event of rename
+			String teamname = objectName;
+			if (teamname == null) {
+				teamname = model.name;
+			}
+			try {
+				GitBlit.self().updateTeamModel(teamname, model, false);
+			} catch (GitBlitException e) {
+				response.setStatus(failureCode);
+			}
+		} else if (RpcRequest.DELETE_TEAM.equals(reqType)) {
+			// delete team
+			TeamModel model = deserialize(request, response, TeamModel.class);
+			if (!GitBlit.self().deleteTeam(model.name)) {
+				response.setStatus(failureCode);
+			}
+		} else if (RpcRequest.LIST_REPOSITORY_MEMBERS.equals(reqType)) {
+			// get repository members
+			RepositoryModel model = GitBlit.self().getRepositoryModel(objectName);
+			result = GitBlit.self().getRepositoryUsers(model);
+		} else if (RpcRequest.SET_REPOSITORY_MEMBERS.equals(reqType)) {
+			// rejected since 1.2.0
+			response.setStatus(failureCode);
+		} else if (RpcRequest.LIST_REPOSITORY_MEMBER_PERMISSIONS.equals(reqType)) {
+			// get repository member permissions
+			RepositoryModel model = GitBlit.self().getRepositoryModel(objectName);
+			result = GitBlit.self().getUserAccessPermissions(model);
+		} else if (RpcRequest.SET_REPOSITORY_MEMBER_PERMISSIONS.equals(reqType)) {
+			// set the repository permissions for the specified users
+			RepositoryModel model = GitBlit.self().getRepositoryModel(objectName);
+			Collection<RegistrantAccessPermission> permissions = deserialize(request, response, RpcUtils.REGISTRANT_PERMISSIONS_TYPE);
+			result = GitBlit.self().setUserAccessPermissions(model, permissions);
+		} else if (RpcRequest.LIST_REPOSITORY_TEAMS.equals(reqType)) {
+			// get repository teams
+			RepositoryModel model = GitBlit.self().getRepositoryModel(objectName);
+			result = GitBlit.self().getRepositoryTeams(model);
+		} else if (RpcRequest.SET_REPOSITORY_TEAMS.equals(reqType)) {
+			// rejected since 1.2.0
+			response.setStatus(failureCode);
+		} else if (RpcRequest.LIST_REPOSITORY_TEAM_PERMISSIONS.equals(reqType)) {
+			// get repository team permissions
+			RepositoryModel model = GitBlit.self().getRepositoryModel(objectName);
+			result = GitBlit.self().getTeamAccessPermissions(model);
+		} else if (RpcRequest.SET_REPOSITORY_TEAM_PERMISSIONS.equals(reqType)) {
+			// set the repository permissions for the specified teams
+			RepositoryModel model = GitBlit.self().getRepositoryModel(objectName);
+			Collection<RegistrantAccessPermission> permissions = deserialize(request, response, RpcUtils.REGISTRANT_PERMISSIONS_TYPE);
+			result = GitBlit.self().setTeamAccessPermissions(model, permissions);
+		} else if (RpcRequest.LIST_FEDERATION_REGISTRATIONS.equals(reqType)) {
+			// return the list of federation registrations
+			if (allowAdmin) {
+				result = GitBlit.self().getFederationRegistrations();
+			} else {
+				response.sendError(notAllowedCode);
+			}
+		} else if (RpcRequest.LIST_FEDERATION_RESULTS.equals(reqType)) {
+			// return the list of federation result registrations
+			if (allowAdmin && GitBlit.canFederate()) {
+				result = GitBlit.self().getFederationResultRegistrations();
+			} else {
+				response.sendError(notAllowedCode);
+			}
+		} else if (RpcRequest.LIST_FEDERATION_PROPOSALS.equals(reqType)) {
+			// return the list of federation proposals
+			if (allowAdmin && GitBlit.canFederate()) {
+				result = GitBlit.self().getPendingFederationProposals();
+			} else {
+				response.sendError(notAllowedCode);
+			}
+		} else if (RpcRequest.LIST_FEDERATION_SETS.equals(reqType)) {
+			// return the list of federation sets
+			if (allowAdmin && GitBlit.canFederate()) {
+				String gitblitUrl = HttpUtils.getGitblitURL(request);
+				result = GitBlit.self().getFederationSets(gitblitUrl);
+			} else {
+				response.sendError(notAllowedCode);
+			}
+		} else if (RpcRequest.LIST_SETTINGS.equals(reqType)) {
+			// return the server's settings
+			ServerSettings settings = GitBlit.self().getSettingsModel();
+			if (allowAdmin) {
+				// return all settings
+				result = settings;
+			} else {
+				// anonymous users get a few settings to allow browser launching
+				List<String> keys = new ArrayList<String>();
+				keys.add(Keys.web.siteName);
+				keys.add(Keys.web.mountParameters);
+				keys.add(Keys.web.syndicationEntries);
+
+				if (allowManagement) {
+					// keys necessary for repository and/or user management
+					keys.add(Keys.realm.minPasswordLength);
+					keys.add(Keys.realm.passwordStorage);
+					keys.add(Keys.federation.sets);
+				}
+				// build the settings
+				ServerSettings managementSettings = new ServerSettings();
+				for (String key : keys) {
+					managementSettings.add(settings.get(key));
+				}
+				if (allowManagement) {
+					managementSettings.pushScripts = settings.pushScripts;
+				}
+				result = managementSettings;
+			}
+		} else if (RpcRequest.EDIT_SETTINGS.equals(reqType)) {
+			// update settings on the server
+			if (allowAdmin) {
+				Map<String, String> settings = deserialize(request, response,
+						RpcUtils.SETTINGS_TYPE);
+				GitBlit.self().updateSettings(settings);
+			} else {
+				response.sendError(notAllowedCode);
+			}
+		} else if (RpcRequest.LIST_STATUS.equals(reqType)) {
+			// return the server's status information
+			if (allowAdmin) {
+				result = GitBlit.self().getStatus();
+			} else {
+				response.sendError(notAllowedCode);
+			}
+		} else if (RpcRequest.CLEAR_REPOSITORY_CACHE.equals(reqType)) {
+			// clear the repository list cache
+			if (allowManagement) {
+				GitBlit.self().resetRepositoryListCache();
+			} else {
+				response.sendError(notAllowedCode);
+			}
+		}
+
+		// send the result of the request
+		serialize(response, result);
+	}
+}
diff --git a/src/main/java/com/gitblit/SalesforceUserService.java b/src/main/java/com/gitblit/SalesforceUserService.java
new file mode 100644
index 0000000..aa0795a
--- /dev/null
+++ b/src/main/java/com/gitblit/SalesforceUserService.java
@@ -0,0 +1,137 @@
+package com.gitblit;
+
+import java.io.File;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.gitblit.Constants.AccountType;
+import com.gitblit.models.UserModel;
+import com.gitblit.utils.ArrayUtils;
+import com.gitblit.utils.StringUtils;
+import com.sforce.soap.partner.Connector;
+import com.sforce.soap.partner.GetUserInfoResult;
+import com.sforce.soap.partner.PartnerConnection;
+import com.sforce.ws.ConnectionException;
+import com.sforce.ws.ConnectorConfig;
+
+public class SalesforceUserService extends GitblitUserService {
+	public static final Logger logger = LoggerFactory
+			.getLogger(SalesforceUserService.class);
+	private IStoredSettings settings;
+	
+	protected AccountType getAccountType() {
+		return AccountType.SALESFORCE;
+	}
+	
+	@Override
+	public void setup(IStoredSettings settings) {
+		this.settings = settings;
+		String file = settings.getString(
+				Keys.realm.salesforce.backingUserService,
+				"${baseFolder}/users.conf");
+		File realmFile = GitBlit.getFileOrFolder(file);
+
+		serviceImpl = createUserService(realmFile);
+
+		logger.info("Salesforce User Service backed by "
+				+ serviceImpl.toString());
+	}
+
+	@Override
+	public UserModel authenticate(String username, char[] password) {
+		if (isLocalAccount(username)) {
+			// local account, bypass Salesforce authentication
+			return super.authenticate(username, password);
+		}
+
+		ConnectorConfig config = new ConnectorConfig();
+		config.setUsername(username);
+		config.setPassword(new String(password));
+
+		try {
+			PartnerConnection connection = Connector.newConnection(config);
+
+			GetUserInfoResult info = connection.getUserInfo();
+
+			String org = settings.getString(Keys.realm.salesforce.orgId, "0")
+					.trim();
+
+			if (!org.equals("0")) {
+				if (!org.equals(info.getOrganizationId())) {
+					logger.warn("Access attempted by user of an invalid org: "
+							+ info.getUserName() + ", org: "
+							+ info.getOrganizationName() + "("
+							+ info.getOrganizationId() + ")");
+
+					return null;
+				}
+			}
+
+			logger.info("Authenticated user " + info.getUserName()
+					+ " using org " + info.getOrganizationName() + "("
+					+ info.getOrganizationId() + ")");
+
+			String simpleUsername = getSimpleUsername(info);
+
+			UserModel user = null;
+			synchronized (this) {
+				user = getUserModel(simpleUsername);
+				if (user == null)
+					user = new UserModel(simpleUsername);
+
+				if (StringUtils.isEmpty(user.cookie)
+						&& !ArrayUtils.isEmpty(password)) {
+					user.cookie = StringUtils.getSHA1(user.username
+							+ new String(password));
+				}
+
+				setUserAttributes(user, info);
+
+				super.updateUserModel(user);
+			}
+
+			return user;
+		} catch (ConnectionException e) {
+			logger.error("Failed to authenticate", e);
+		}
+
+		return null;
+	}
+
+	private void setUserAttributes(UserModel user, GetUserInfoResult info) {
+		// Don't want visibility into the real password, make up a dummy
+		user.password = Constants.EXTERNAL_ACCOUNT;
+		user.accountType = getAccountType();
+
+		// Get full name Attribute
+		user.displayName = info.getUserFullName();
+
+		// Get email address Attribute
+		user.emailAddress = info.getUserEmail();
+	}
+
+	/**
+	 * Simple user name is the first part of the email address.
+	 */
+	private String getSimpleUsername(GetUserInfoResult info) {
+		String email = info.getUserEmail();
+
+		return email.split("@")[0];
+	}
+
+	@Override
+	public boolean supportsCredentialChanges() {
+		return false;
+	}
+
+	@Override
+	public boolean supportsDisplayNameChanges() {
+		return false;
+	}
+
+	@Override
+	public boolean supportsEmailAddressChanges() {
+		return false;
+	}
+}
diff --git a/src/main/java/com/gitblit/SparkleShareInviteServlet.java b/src/main/java/com/gitblit/SparkleShareInviteServlet.java
new file mode 100644
index 0000000..14d281a
--- /dev/null
+++ b/src/main/java/com/gitblit/SparkleShareInviteServlet.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2013 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit;
+
+import java.io.IOException;
+import java.text.MessageFormat;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import com.gitblit.models.RepositoryModel;
+import com.gitblit.models.UserModel;
+import com.gitblit.utils.StringUtils;
+
+/**
+ * Handles requests for Sparkleshare Invites
+ * 
+ * @author James Moger
+ * 
+ */
+public class SparkleShareInviteServlet extends HttpServlet {
+
+	private static final long serialVersionUID = 1L;
+
+	public SparkleShareInviteServlet() {
+		super();
+	}
+	
+	@Override
+	protected void doPost(HttpServletRequest request, HttpServletResponse response)
+			throws ServletException, java.io.IOException {
+		processRequest(request, response);
+	}
+
+	@Override
+	protected void doGet(HttpServletRequest request, HttpServletResponse response)
+			throws ServletException, IOException {
+		processRequest(request, response);
+	}
+
+	protected void processRequest(javax.servlet.http.HttpServletRequest request,
+			javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException,
+			java.io.IOException {		
+		
+		// extract repo name from request
+		String repoUrl = request.getPathInfo().substring(1);
+
+		// trim trailing .xml
+		if (repoUrl.endsWith(".xml")) {
+			repoUrl = repoUrl.substring(0, repoUrl.length() - 4);
+		}
+		
+		String servletPath =  Constants.GIT_PATH;
+		
+		int schemeIndex = repoUrl.indexOf("://") + 3;
+		String host = repoUrl.substring(0, repoUrl.indexOf('/', schemeIndex));				
+		String path = repoUrl.substring(repoUrl.indexOf(servletPath) + servletPath.length());
+		String username = null;
+		int fetchIndex = repoUrl.indexOf('@');
+		if (fetchIndex > -1) {
+			username = repoUrl.substring(schemeIndex, fetchIndex);
+		}
+		UserModel user;
+		if (StringUtils.isEmpty(username)) {
+			user = GitBlit.self().authenticate(request);
+		} else {
+			user = GitBlit.self().getUserModel(username);
+		}
+		if (user == null) {
+			user = UserModel.ANONYMOUS;
+			username = "";
+		}
+		
+		// ensure that the requested repository exists
+		RepositoryModel model = GitBlit.self().getRepositoryModel(path);
+		if (model == null) {
+			response.setStatus(HttpServletResponse.SC_NOT_FOUND);
+			response.getWriter().append(MessageFormat.format("Repository \"{0}\" not found!", path));
+			return;
+		}
+		
+		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));
+		sb.append(MessageFormat.format("<remote_path>{0}{1}</remote_path>\n", servletPath, model.name));
+		if (GitBlit.getInteger(Keys.fanout.port, 0) > 0) {
+			// Gitblit is running it's own fanout service for pubsub notifications
+			sb.append(MessageFormat.format("<announcements_url>tcp://{0}:{1}</announcements_url>\n", request.getServerName(), GitBlit.getString(Keys.fanout.port, "")));
+		}
+		sb.append("</invite></sparkleshare>\n");
+
+		// write invite to client
+		response.setContentType("application/xml");
+		response.setContentLength(sb.length());
+		response.getWriter().append(sb.toString());
+	}
+}
diff --git a/src/com/gitblit/SyndicationFilter.java b/src/main/java/com/gitblit/SyndicationFilter.java
similarity index 100%
rename from src/com/gitblit/SyndicationFilter.java
rename to src/main/java/com/gitblit/SyndicationFilter.java
diff --git a/src/com/gitblit/SyndicationServlet.java b/src/main/java/com/gitblit/SyndicationServlet.java
similarity index 100%
rename from src/com/gitblit/SyndicationServlet.java
rename to src/main/java/com/gitblit/SyndicationServlet.java
diff --git a/src/com/gitblit/WebXmlSettings.java b/src/main/java/com/gitblit/WebXmlSettings.java
similarity index 100%
rename from src/com/gitblit/WebXmlSettings.java
rename to src/main/java/com/gitblit/WebXmlSettings.java
diff --git a/src/main/java/com/gitblit/WindowsUserService.java b/src/main/java/com/gitblit/WindowsUserService.java
new file mode 100644
index 0000000..4830297
--- /dev/null
+++ b/src/main/java/com/gitblit/WindowsUserService.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright 2013 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit;
+
+import java.io.File;
+import java.util.Set;
+import java.util.TreeSet;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import waffle.windows.auth.IWindowsAccount;
+import waffle.windows.auth.IWindowsAuthProvider;
+import waffle.windows.auth.IWindowsComputer;
+import waffle.windows.auth.IWindowsIdentity;
+import waffle.windows.auth.impl.WindowsAuthProviderImpl;
+
+import com.gitblit.Constants.AccountType;
+import com.gitblit.models.UserModel;
+import com.gitblit.utils.ArrayUtils;
+import com.gitblit.utils.StringUtils;
+import com.sun.jna.platform.win32.Win32Exception;
+
+/**
+ * Implementation of a Windows user service.
+ * 
+ * @author James Moger
+ */
+public class WindowsUserService extends GitblitUserService {
+
+    private final Logger logger = LoggerFactory.getLogger(WindowsUserService.class);
+
+    private IStoredSettings settings;
+    
+    private IWindowsAuthProvider waffle;
+
+    public WindowsUserService() {
+        super();
+    }
+
+    @Override
+    public void setup(IStoredSettings settings) {
+        this.settings = settings;
+
+        String file = settings.getString(Keys.realm.windows.backingUserService, "${baseFolder}/users.conf");
+        File realmFile = GitBlit.getFileOrFolder(file);
+
+        serviceImpl = createUserService(realmFile);
+        logger.info("Windows User Service backed by " + serviceImpl.toString());
+        
+        waffle = new WindowsAuthProviderImpl();
+        IWindowsComputer computer = waffle.getCurrentComputer();
+        logger.info("      name = " + computer.getComputerName());
+        logger.info("    status = " + describeJoinStatus(computer.getJoinStatus()));
+        logger.info("  memberOf = " + computer.getMemberOf());
+        //logger.info("  groups     = " + Arrays.asList(computer.getGroups()));
+    }
+    
+    protected String describeJoinStatus(String value) {
+    	if ("NetSetupUnknownStatus".equals(value)) {
+    		return "unknown";
+    	} else if ("NetSetupUnjoined".equals(value)) {
+    		return "not joined";
+    	} else if ("NetSetupWorkgroupName".equals(value)) {
+    		return "joined to a workgroup";
+    	} else if ("NetSetupDomainName".equals(value)) {
+    		return "joined to a domain";
+    	}
+    	return value;
+    }
+
+    @Override
+    public boolean supportsCredentialChanges() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsDisplayNameChanges() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsEmailAddressChanges() {
+        return true;
+    }
+
+    @Override
+    public boolean supportsTeamMembershipChanges() {
+        return true;
+    }
+    
+	 @Override
+	protected AccountType getAccountType() {
+		return AccountType.WINDOWS;
+	}
+
+    @Override
+    public UserModel authenticate(String username, char[] password) {
+		if (isLocalAccount(username)) {
+			// local account, bypass Windows authentication
+			return super.authenticate(username, password);
+		}
+
+		String defaultDomain = settings.getString(Keys.realm.windows.defaultDomain, null);
+		if (StringUtils.isEmpty(defaultDomain)) {
+			// ensure that default domain is null
+			defaultDomain = null;
+		}
+
+		if (defaultDomain != null) {
+			// sanitize username
+			if (username.startsWith(defaultDomain + "\\")) {
+				// strip default domain from domain\ username
+				username = username.substring(defaultDomain.length() + 1);
+			} else if (username.endsWith("@" + defaultDomain)) {
+				// strip default domain from username@domain
+				username = username.substring(0, username.lastIndexOf('@'));
+			}
+		}
+
+		IWindowsIdentity identity = null;
+		try {
+			if (username.indexOf('@') > -1 || username.indexOf('\\') > -1) {
+				// manually specified domain
+				identity = waffle.logonUser(username, new String(password));
+			} else {
+				// no domain specified, use default domain
+				identity = waffle.logonDomainUser(username, defaultDomain, new String(password));
+			}
+		} catch (Win32Exception e) {
+			logger.error(e.getMessage());
+			return null;
+		}
+
+		if (identity.isGuest() && !settings.getBoolean(Keys.realm.windows.allowGuests, false)) {
+			logger.warn("Guest account access is disabled");
+			identity.dispose();
+			return null;
+		}
+		
+        UserModel user = getUserModel(username);
+        if (user == null)	// create user object for new authenticated user
+        	user = new UserModel(username.toLowerCase());
+
+        // create a user cookie
+        if (StringUtils.isEmpty(user.cookie) && !ArrayUtils.isEmpty(password)) {
+        	user.cookie = StringUtils.getSHA1(user.username + new String(password));
+        }
+
+        // update user attributes from Windows identity
+        user.accountType = getAccountType();
+        String fqn = identity.getFqn();
+        if (fqn.indexOf('\\') > -1) {
+        	user.displayName = fqn.substring(fqn.lastIndexOf('\\') + 1);
+        } else {
+        	user.displayName = fqn;
+        }
+        user.password = Constants.EXTERNAL_ACCOUNT;
+
+        Set<String> groupNames = new TreeSet<String>();
+       	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
+        super.updateUserModel(user);
+
+
+        // cleanup resources
+        identity.dispose();
+        
+        return user;
+    }
+}
diff --git a/src/com/gitblit/authority/AuthorityWorker.java b/src/main/java/com/gitblit/authority/AuthorityWorker.java
similarity index 100%
rename from src/com/gitblit/authority/AuthorityWorker.java
rename to src/main/java/com/gitblit/authority/AuthorityWorker.java
diff --git a/src/com/gitblit/authority/CertificateStatus.java b/src/main/java/com/gitblit/authority/CertificateStatus.java
similarity index 100%
rename from src/com/gitblit/authority/CertificateStatus.java
rename to src/main/java/com/gitblit/authority/CertificateStatus.java
diff --git a/src/com/gitblit/authority/CertificateStatusRenderer.java b/src/main/java/com/gitblit/authority/CertificateStatusRenderer.java
similarity index 100%
rename from src/com/gitblit/authority/CertificateStatusRenderer.java
rename to src/main/java/com/gitblit/authority/CertificateStatusRenderer.java
diff --git a/src/main/java/com/gitblit/authority/CertificatesTableModel.java b/src/main/java/com/gitblit/authority/CertificatesTableModel.java
new file mode 100644
index 0000000..333836d
--- /dev/null
+++ b/src/main/java/com/gitblit/authority/CertificatesTableModel.java
@@ -0,0 +1,169 @@
+/*
+ * 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.authority;
+
+import java.math.BigInteger;
+import java.security.cert.X509Certificate;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Date;
+
+import javax.swing.table.AbstractTableModel;
+
+import com.gitblit.client.Translation;
+import com.gitblit.utils.X509Utils.RevocationReason;
+
+/**
+ * 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;
+
+		@Override
+		public String toString() {
+			return name().replace('_', ' ');
+		}
+	}
+
+	public CertificatesTableModel() {
+	}
+
+	@Override
+	public int getRowCount() {
+		return ucm == null || ucm.certs == null ? 0 : ucm.certs.size();
+	}
+
+	@Override
+	public int getColumnCount() {
+		return Columns.values().length;
+	}
+
+	@Override
+	public String getColumnName(int column) {
+		Columns col = Columns.values()[column];
+		switch (col) {
+		case SerialNumber:
+			return Translation.get("gb.serialNumber");
+		case Issued:
+			return Translation.get("gb.issued");
+		case Expires:
+			return Translation.get("gb.expires");
+		case Status:
+			return Translation.get("gb.status");
+		case Reason:
+			return Translation.get("gb.reason");
+		}
+		return "";
+	}
+
+	/**
+	 * Returns <code>Object.class</code> regardless of <code>columnIndex</code>.
+	 * 
+	 * @param columnIndex
+	 *            the column being queried
+	 * @return the Object.class
+	 */
+	public Class<?> getColumnClass(int columnIndex) {
+		Columns col = Columns.values()[columnIndex];
+		switch (col) {
+		case Status:
+			return CertificateStatus.class;
+		case Issued:
+			return Date.class;
+		case Expires:
+			return Date.class;
+		case SerialNumber:
+			return BigInteger.class;
+		default:
+			return String.class;
+		}
+	}
+
+	@Override
+	public boolean isCellEditable(int rowIndex, int columnIndex) {
+		Columns col = Columns.values()[columnIndex];
+		switch (col) {
+		default:
+			return false;
+		}
+	}
+
+	@Override
+	public Object getValueAt(int rowIndex, int columnIndex) {
+		X509Certificate cert = ucm.certs.get(rowIndex);
+		Columns col = Columns.values()[columnIndex];
+		switch (col) {
+		case Status:
+			return ucm.getStatus(cert);
+		case SerialNumber:
+			return cert.getSerialNumber();
+		case Issued:
+			return cert.getNotBefore();
+		case Expires:
+			return cert.getNotAfter();
+		case Reason:
+			if (ucm.getStatus(cert).equals(CertificateStatus.revoked)) {
+				RevocationReason r = ucm.getRevocationReason(cert.getSerialNumber());
+				return Translation.get("gb." + r.name());
+			}			
+		}
+		return null;
+	}
+
+	public X509Certificate get(int modelRow) {
+		return ucm.certs.get(modelRow);
+	}
+	
+	public void setUserCertificateModel(UserCertificateModel ucm) {
+		this.ucm = ucm;
+		if (ucm == null) {
+			return;
+		}
+		Collections.sort(ucm.certs, new Comparator<X509Certificate>() {
+			@Override
+			public int compare(X509Certificate o1, X509Certificate o2) {
+				// sort by issue date in reverse chronological order
+				int result = o2.getNotBefore().compareTo(o1.getNotBefore());
+				if (result == 0) {
+					// same issue date, show expiring first
+					boolean r1 = CertificatesTableModel.this.ucm.isRevoked(o1.getSerialNumber());
+					boolean r2 = CertificatesTableModel.this.ucm.isRevoked(o2.getSerialNumber());
+					if ((r1 && r2) || (!r1 && !r2)) {
+						// both revoked or both not revoked
+						// chronlogical order by expiration dates
+						result = o1.getNotAfter().compareTo(o2.getNotAfter());
+					} else if (r1) {
+						// r1 is revoked, r2 first
+						return 1;
+					} else {
+						// r2 is revoked, r1 first
+						return -1;
+					}
+				}
+				return result;
+			}
+		});
+	}
+}
diff --git a/src/com/gitblit/authority/DefaultOidsPanel.java b/src/main/java/com/gitblit/authority/DefaultOidsPanel.java
similarity index 100%
rename from src/com/gitblit/authority/DefaultOidsPanel.java
rename to src/main/java/com/gitblit/authority/DefaultOidsPanel.java
diff --git a/src/main/java/com/gitblit/authority/GitblitAuthority.java b/src/main/java/com/gitblit/authority/GitblitAuthority.java
new file mode 100644
index 0000000..e0b079e
--- /dev/null
+++ b/src/main/java/com/gitblit/authority/GitblitAuthority.java
@@ -0,0 +1,923 @@
+/*
+ * 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.authority;
+
+import java.awt.BorderLayout;
+import java.awt.Container;
+import java.awt.Desktop;
+import java.awt.Dimension;
+import java.awt.EventQueue;
+import java.awt.FlowLayout;
+import java.awt.GridLayout;
+import java.awt.Insets;
+import java.awt.Point;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.KeyAdapter;
+import java.awt.event.KeyEvent;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.io.BufferedInputStream;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileWriter;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.net.URI;
+import java.security.PrivateKey;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.activation.DataHandler;
+import javax.activation.FileDataSource;
+import javax.mail.Message;
+import javax.mail.Multipart;
+import javax.mail.internet.MimeBodyPart;
+import javax.mail.internet.MimeMultipart;
+import javax.swing.ImageIcon;
+import javax.swing.InputVerifier;
+import javax.swing.JButton;
+import javax.swing.JComponent;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JPasswordField;
+import javax.swing.JScrollPane;
+import javax.swing.JSplitPane;
+import javax.swing.JTable;
+import javax.swing.JTextArea;
+import javax.swing.JTextField;
+import javax.swing.JToolBar;
+import javax.swing.RowFilter;
+import javax.swing.SwingConstants;
+import javax.swing.UIManager;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+import javax.swing.table.TableRowSorter;
+
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.lib.StoredConfig;
+import org.eclipse.jgit.storage.file.FileBasedConfig;
+import org.eclipse.jgit.util.FS;
+import org.slf4j.LoggerFactory;
+
+import com.gitblit.ConfigUserService;
+import com.gitblit.Constants;
+import com.gitblit.FileSettings;
+import com.gitblit.IStoredSettings;
+import com.gitblit.IUserService;
+import com.gitblit.Keys;
+import com.gitblit.MailExecutor;
+import com.gitblit.client.HeaderPanel;
+import com.gitblit.client.Translation;
+import com.gitblit.models.UserModel;
+import com.gitblit.utils.ArrayUtils;
+import com.gitblit.utils.FileUtils;
+import com.gitblit.utils.StringUtils;
+import com.gitblit.utils.TimeUtils;
+import com.gitblit.utils.X509Utils;
+import com.gitblit.utils.X509Utils.RevocationReason;
+import com.gitblit.utils.X509Utils.X509Log;
+import com.gitblit.utils.X509Utils.X509Metadata;
+
+/**
+ * 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;
+
+	private JButton newSSLCertificate;
+
+	public static void main(String... args) {
+		// filter out the baseFolder parameter
+		String folder = "data";
+		for (int i = 0; i< args.length; i++) {
+			String arg = args[i];
+			if (arg.equals("--baseFolder")) {
+				if (i + 1 == args.length) {
+					System.out.println("Invalid --baseFolder parameter!");
+					System.exit(-1);
+				} else if (args[i + 1] != ".") {
+					folder = args[i+1];
+				}
+				break;
+			}
+		}
+		final String baseFolder = folder;
+		EventQueue.invokeLater(new Runnable() {
+			public void run() {
+				try {
+					UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
+				} catch (Exception e) {
+				}
+				GitblitAuthority authority = new GitblitAuthority();
+				authority.initialize(baseFolder);
+				authority.setLocationRelativeTo(null);
+				authority.setVisible(true);
+			}
+		});
+	}
+
+	public GitblitAuthority() {
+		super();
+		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() + ")");
+		setContentPane(getUI());
+		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+		addWindowListener(new WindowAdapter() {
+			@Override
+			public void windowClosing(WindowEvent event) {
+				saveSizeAndPosition();
+			}
+
+			@Override
+			public void windowOpened(WindowEvent event) {
+			}
+		});		
+
+		File folder = new File(baseFolder).getAbsoluteFile();
+		load(folder);
+		
+		setSizeAndPosition();
+	}
+	
+	private void setSizeAndPosition() {
+		String sz = null;
+		String pos = null;
+		try {
+			StoredConfig config = getConfig();
+			sz = config.getString("ui", null, "size");
+			pos = config.getString("ui", null, "position");
+			defaultDuration = config.getInt("new",  "duration", 365);
+		} catch (Throwable t) {
+			t.printStackTrace();
+		}
+
+		// try to restore saved window size
+		if (StringUtils.isEmpty(sz)) {
+			setSize(900, 600);
+		} else {
+			String[] chunks = sz.split("x");
+			int width = Integer.parseInt(chunks[0]);
+			int height = Integer.parseInt(chunks[1]);
+			setSize(width, height);
+		}
+
+		// try to restore saved window position
+		if (StringUtils.isEmpty(pos)) {
+			setLocationRelativeTo(null);
+		} else {
+			String[] chunks = pos.split(",");
+			int x = Integer.parseInt(chunks[0]);
+			int y = Integer.parseInt(chunks[1]);
+			setLocation(x, y);
+		}
+	}
+
+	private void saveSizeAndPosition() {
+		try {
+			// save window size and position
+			StoredConfig config = getConfig();
+			Dimension sz = GitblitAuthority.this.getSize();
+			config.setString("ui", null, "size",
+					MessageFormat.format("{0,number,0}x{1,number,0}", sz.width, sz.height));
+			Point pos = GitblitAuthority.this.getLocationOnScreen();
+			config.setString("ui", null, "position",
+					MessageFormat.format("{0,number,0},{1,number,0}", pos.x, pos.y));
+			config.save();
+		} catch (Throwable t) {
+			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()) {
+			return null;
+		}
+		gitblitSettings = new FileSettings(file.getAbsolutePath());
+		mail = new MailExecutor(gitblitSettings);
+		String us = gitblitSettings.getString(Keys.realm.userService, "${baseFolder}/users.conf");
+		String ext = us.substring(us.lastIndexOf(".") + 1).toLowerCase();
+		IUserService service = null;
+		if (!ext.equals("conf") && !ext.equals("properties")) {
+			if (us.equals("com.gitblit.LdapUserService")) {
+				us = gitblitSettings.getString(Keys.realm.ldap.backingUserService, "${baseFolder}/users.conf");		
+			} else if (us.equals("com.gitblit.LdapUserService")) {
+				us = gitblitSettings.getString(Keys.realm.redmine.backingUserService, "${baseFolder}/users.conf");
+			}
+		}
+
+		if (us.endsWith(".conf")) {
+			service = new ConfigUserService(FileUtils.resolveParameter(Constants.baseFolder$, folder, us));
+		} 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);
+		System.out.println(Constants.baseFolder$ + " set to " + folder);
+		if (userService == null) {
+			JOptionPane.showMessageDialog(this, MessageFormat.format("Sorry, {0} doesn't look like a Gitblit GO installation.", folder));
+		} else {
+			// build empty certificate model for all users
+			Map<String, UserCertificateModel> map = new HashMap<String, UserCertificateModel>();
+			for (String user : userService.getAllUsernames()) {
+				UserModel model = userService.getUserModel(user);
+				UserCertificateModel ucm = new UserCertificateModel(model);				
+				map.put(user, ucm);
+			}
+			File certificatesConfigFile = new File(folder, X509Utils.CA_CONFIG);
+			FileBasedConfig config = new FileBasedConfig(certificatesConfigFile, FS.detect());
+			if (certificatesConfigFile.exists()) {
+				try {
+					config.load();
+					// replace user certificate model with actual data
+					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);
+					}
+				} catch (IOException e) {
+					e.printStackTrace();
+				} catch (ConfigInvalidException e) {
+					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"),
+							Translation.get("gb.warning"), JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE);
+					if (res != JOptionPane.YES_OPTION) {
+						if (Desktop.isDesktopSupported()) {
+							if (Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) {
+								try {
+									Desktop.getDesktop().browse(URI.create("http://www.oracle.com/technetwork/java/javase/downloads/index.html"));
+								} catch (IOException e) {
+								}
+							}
+						}
+						System.exit(1);
+					}
+				}
+				
+				// show certificate defaults dialog 
+				certificateDefaultsButton.doClick();
+				
+				// create "localhost" ssl certificate
+				prepareX509Infrastructure();
+			}
+		}
+	}
+	
+	private boolean prepareX509Infrastructure() {
+		if (caKeystorePassword == null) {
+			JPasswordField pass = new JPasswordField(10);
+			pass.setText(caKeystorePassword);
+			pass.addAncestorListener(new RequestFocusListener());
+			JPanel panel = new JPanel(new BorderLayout());
+			panel.add(new JLabel(Translation.get("gb.enterKeystorePassword")), BorderLayout.NORTH);
+			panel.add(pass, BorderLayout.CENTER);
+			int result = JOptionPane.showConfirmDialog(GitblitAuthority.this, panel, Translation.get("gb.password"), JOptionPane.OK_CANCEL_OPTION);
+			if (result == JOptionPane.OK_OPTION) {
+				caKeystorePassword = new String(pass.getPassword());
+			} else {
+				return false;
+			}
+		}
+
+		X509Metadata metadata = new X509Metadata("localhost", caKeystorePassword);
+		setMetadataDefaults(metadata);
+		metadata.notAfter = new Date(System.currentTimeMillis() + 10*TimeUtils.ONEYEAR);
+		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);
+		if (!userFolder.exists()) {
+			return list;
+		}
+		File [] certs = userFolder.listFiles(new FilenameFilter() {
+			@Override
+			public boolean accept(File dir, String name) {
+				return name.toLowerCase().endsWith(".cer") || name.toLowerCase().endsWith(".crt");
+			}
+		});
+		try {
+			CertificateFactory factory = CertificateFactory.getInstance("X.509");
+			for (File cert : certs) {				
+				BufferedInputStream is = new BufferedInputStream(new FileInputStream(cert));
+				X509Certificate x509 = (X509Certificate) factory.generateCertificate(is);
+				is.close();
+				list.add(x509);
+			}
+		} catch (Exception e) {
+			Utils.showException(GitblitAuthority.this, e);
+		}
+		return list;
+	}
+	
+	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();
+			}
+
+			@Override
+			public Date getDefaultExpiration() {
+				Calendar c = Calendar.getInstance();
+				c.add(Calendar.DATE, defaultDuration);
+				c.set(Calendar.HOUR_OF_DAY, 0);
+				c.set(Calendar.MINUTE, 0);
+				c.set(Calendar.SECOND, 0);
+				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()) {
+					return false;
+				}
+
+				Date notAfter = metadata.notAfter;
+				setMetadataDefaults(metadata);
+				metadata.notAfter = notAfter;
+				
+				// set user's specified OID values
+				UserModel user = ucm.user;				
+				if (!StringUtils.isEmpty(user.organizationalUnit)) {
+					metadata.oids.put("OU", user.organizationalUnit);
+				}
+				if (!StringUtils.isEmpty(user.organization)) {
+					metadata.oids.put("O", user.organization);
+				}
+				if (!StringUtils.isEmpty(user.locality)) {
+					metadata.oids.put("L", user.locality);
+				}
+				if (!StringUtils.isEmpty(user.stateProvince)) {
+					metadata.oids.put("ST", user.stateProvince);
+				}
+				if (!StringUtils.isEmpty(user.countryCode)) {
+					metadata.oids.put("C", user.countryCode);
+				}
+
+				File caKeystoreFile = new File(folder, X509Utils.CA_KEY_STORE);
+				File zip = X509Utils.newClientBundle(metadata, caKeystoreFile, caKeystorePassword, GitblitAuthority.this);
+
+				// save latest expiration date
+				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()) {
+					return false;
+				}
+
+				File caRevocationList = new File(folder, X509Utils.CA_REVOCATION_LIST);
+				File caKeystoreFile = new File(folder, X509Utils.CA_KEY_STORE);
+				if (X509Utils.revoke(cert, reason, caRevocationList, caKeystoreFile, caKeystorePassword, GitblitAuthority.this)) {
+					File certificatesConfigFile = new File(folder, X509Utils.CA_CONFIG);
+					FileBasedConfig config = new FileBasedConfig(certificatesConfigFile, FS.detect());
+					if (certificatesConfigFile.exists()) {
+						try {
+							config.load();
+						} catch (Exception e) {
+							Utils.showException(GitblitAuthority.this, e);
+						}
+					}
+					// add serial to revoked list
+					ucm.revoke(cert.getSerialNumber(), reason);
+					ucm.update(config);
+					try {
+						config.save();
+					} 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());
+		table.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
+
+			@Override
+			public void valueChanged(ListSelectionEvent e) {
+				if (e.getValueIsAdjusting()) {
+					return;
+				}
+				int row = table.getSelectedRow();
+				if (row < 0) {
+					return;
+				}
+				int modelIndex = table.convertRowIndexToModel(row);
+				UserCertificateModel ucm = tableModel.get(modelIndex);
+				if (ucm.certs == null) {
+					ucm.certs = findCerts(folder, ucm.user.username);
+				}
+				userCertificatePanel.setUserCertificateModel(ucm);
+			}
+		});
+		
+		JPanel usersPanel = new JPanel(new BorderLayout()) {
+			
+			private static final long serialVersionUID = 1L;
+
+			@Override
+			public Insets getInsets() {
+				return Utils.INSETS;
+			}
+		};
+		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.addActionListener(new ActionListener() {
+			@Override
+			public void actionPerformed(ActionEvent e) {
+				X509Metadata metadata = new X509Metadata("whocares", "whocares");
+				File certificatesConfigFile = new File(folder, X509Utils.CA_CONFIG);
+				FileBasedConfig config = new FileBasedConfig(certificatesConfigFile, FS.detect());
+				NewCertificateConfig certificateConfig = null;
+				if (certificatesConfigFile.exists()) {
+					try {
+						config.load();
+					} catch (Exception x) {
+						Utils.showException(GitblitAuthority.this, x);
+					}
+					certificateConfig = NewCertificateConfig.KEY.parse(config);
+					certificateConfig.update(metadata);
+				}
+				InputVerifier verifier = new InputVerifier() {
+					public boolean verify(JComponent comp) {
+						boolean returnValue;
+						JTextField textField = (JTextField) comp;
+						try {
+							Integer.parseInt(textField.getText());
+							returnValue = true;
+						} catch (NumberFormatException e) {
+							returnValue = false;
+						}
+						return returnValue;
+					}
+				};
+
+				JTextField siteNameTF = new JTextField(20);
+				siteNameTF.setText(gitblitSettings.getString(Keys.web.siteName, "Gitblit"));
+				JPanel siteNamePanel = Utils.newFieldPanel(Translation.get("gb.siteName"),
+						siteNameTF, Translation.get("gb.siteNameDescription"));
+
+				JTextField validityTF = new JTextField(4);
+				validityTF.setInputVerifier(verifier);
+				validityTF.setVerifyInputWhenFocusTarget(true);
+				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, 
+						panel, Translation.get("gb.newCertificateDefaults"), JOptionPane.OK_CANCEL_OPTION,
+						JOptionPane.QUESTION_MESSAGE, new ImageIcon(getClass().getResource("/settings_32x32.png")));
+				if (result == JOptionPane.OK_OPTION) {
+					try {
+						oids.update(metadata);
+						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);
+					} catch (Exception e1) {
+						Utils.showException(GitblitAuthority.this, e1);
+					}
+				}
+			}
+		});
+		
+		newSSLCertificate = new JButton(new ImageIcon(getClass().getResource("/rosette_16x16.png")));
+		newSSLCertificate.setFocusable(false);
+		newSSLCertificate.setToolTipText(Translation.get("gb.newSSLCertificate"));		
+		newSSLCertificate.addActionListener(new ActionListener() {
+			@Override
+			public void actionPerformed(ActionEvent e) {
+				Date defaultExpiration = new Date(System.currentTimeMillis() + 10*TimeUtils.ONEYEAR);
+				NewSSLCertificateDialog dialog = new NewSSLCertificateDialog(GitblitAuthority.this, defaultExpiration);
+				dialog.setModal(true);
+				dialog.setVisible(true);
+				if (dialog.isCanceled()) {
+					return;
+				}
+				final Date expires = dialog.getExpiration();
+				final String hostname = dialog.getHostname();
+				final boolean serveCertificate = dialog.isServeCertificate();
+				
+				AuthorityWorker worker = new AuthorityWorker(GitblitAuthority.this) {
+
+					@Override
+					protected Boolean doRequest() throws IOException {
+						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);
+						metadata.notAfter = expires;
+						File serverKeystoreFile = new File(folder, X509Utils.SERVER_KEY_STORE);
+						X509Certificate cert = X509Utils.newSSLCertificate(metadata, caPrivateKey, caCert, serverKeystoreFile, GitblitAuthority.this);
+						boolean hasCert = cert != null;
+						if (hasCert && serveCertificate) {
+							// update Gitblit https connector alias
+							Map<String, String> updates = new HashMap<String, String>();
+							updates.put(Keys.server.certificateAlias, metadata.commonName);
+							gitblitSettings.saveSettings(updates);
+						}
+						return hasCert;
+					}
+
+					@Override
+					protected void onSuccess() {
+						if (serveCertificate) {
+							JOptionPane.showMessageDialog(GitblitAuthority.this, 
+									MessageFormat.format(Translation.get("gb.sslCertificateGeneratedRestart"), hostname),
+									Translation.get("gb.newSSLCertificate"), JOptionPane.INFORMATION_MESSAGE);
+						} else {
+							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.addActionListener(new ActionListener() {
+			@Override
+			public void actionPerformed(ActionEvent e) {
+				int row = table.getSelectedRow();
+				if (row < 0) {
+					return;
+				}
+				int modelIndex = table.convertRowIndexToModel(row);
+				final UserCertificateModel ucm = tableModel.get(modelIndex);
+				if (ArrayUtils.isEmpty(ucm.certs)) {
+					JOptionPane.showMessageDialog(GitblitAuthority.this, MessageFormat.format(Translation.get("gb.pleaseGenerateClientCertificate"), ucm.user.getDisplayName()));
+				}
+				final File zip = new File(folder, X509Utils.CERTS + File.separator + ucm.user.username + File.separator + ucm.user.username + ".zip");
+				if (!zip.exists()) {
+					return;
+				}
+				
+				AuthorityWorker worker = new AuthorityWorker(GitblitAuthority.this) {
+					@Override
+					protected Boolean doRequest() throws IOException {
+						X509Metadata metadata = new X509Metadata(ucm.user.username, "whocares");
+						metadata.serverHostname = gitblitSettings.getString(Keys.web.siteName, Constants.NAME);
+						if (StringUtils.isEmpty(metadata.serverHostname)) {
+							metadata.serverHostname = Constants.NAME;
+						}
+						metadata.userDisplayname = ucm.user.getDisplayName();
+						return sendEmail(ucm.user, metadata, zip);
+					}
+
+					@Override
+					protected void onSuccess() {
+						JOptionPane.showMessageDialog(GitblitAuthority.this, MessageFormat.format(Translation.get("gb.clientCertificateBundleSent"),
+								ucm.user.getDisplayName()));
+					}
+					
+				};
+				worker.execute();				
+			}
+		});
+		
+		JButton logButton = new JButton(new ImageIcon(getClass().getResource("/script_16x16.png")));
+		logButton.setFocusable(false);
+		logButton.setToolTipText(Translation.get("gb.log"));		
+		logButton.addActionListener(new ActionListener() {
+			@Override
+			public void actionPerformed(ActionEvent e) {
+				File log = new File(folder, X509Utils.CERTS + File.separator + "log.txt");
+				if (log.exists()) {
+					String content = FileUtils.readContent(log,  "\n");
+					JTextArea textarea = new JTextArea(content);
+					JScrollPane scrollPane = new JScrollPane(textarea);
+					scrollPane.setPreferredSize(new Dimension(700, 400));
+					JOptionPane.showMessageDialog(GitblitAuthority.this, scrollPane, log.getAbsolutePath(), JOptionPane.INFORMATION_MESSAGE);
+				}
+			}
+		});
+		
+		final JTextField filterTextfield = new JTextField(15);
+		filterTextfield.addActionListener(new ActionListener() {
+			public void actionPerformed(ActionEvent e) {
+				filterUsers(filterTextfield.getText());
+			}
+		});
+		filterTextfield.addKeyListener(new KeyAdapter() {
+			public void keyReleased(KeyEvent e) {
+				filterUsers(filterTextfield.getText());
+			}
+		});
+		
+		JToolBar buttonControls = new JToolBar(JToolBar.HORIZONTAL);
+		buttonControls.setFloatable(false);
+		buttonControls.add(certificateDefaultsButton);
+		buttonControls.add(newSSLCertificate);
+		buttonControls.add(emailBundle);
+		buttonControls.add(logButton);
+
+		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) {
+			statusLabel.setText("JCE Unlimited Strength Jurisdiction Policy");
+		} else {
+			statusLabel.setText("JCE Standard Encryption Policy");
+		}
+		
+		JPanel root = new JPanel(new BorderLayout()) {
+			private static final long serialVersionUID = 1L;
+			public Insets getInsets() {
+				return Utils.INSETS;
+			}
+		};
+		JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, leftPanel, userCertificatePanel);
+		splitPane.setDividerLocation(1d);
+		root.add(splitPane, BorderLayout.CENTER);
+		root.add(statusLabel, BorderLayout.SOUTH);
+		return root;
+	}
+	
+	private void filterUsers(final String fragment) {
+		table.clearSelection();
+		userCertificatePanel.setUserCertificateModel(null);
+		if (StringUtils.isEmpty(fragment)) {
+			table.setRowSorter(defaultSorter);
+			return;
+		}
+		RowFilter<UserCertificateTableModel, Object> containsFilter = new RowFilter<UserCertificateTableModel, Object>() {
+			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())) {
+						return true;
+					}
+				}
+				return false;
+			}
+		};
+		TableRowSorter<UserCertificateTableModel> sorter = new TableRowSorter<UserCertificateTableModel>(
+				tableModel);
+		sorter.setRowFilter(containsFilter);
+		table.setRowSorter(sorter);
+	}
+	
+	@Override
+	public void log(String message) {
+		BufferedWriter writer = null;
+		try {
+			writer = new BufferedWriter(new FileWriter(new File(folder, X509Utils.CERTS + File.separator + "log.txt"), true));
+			writer.write(MessageFormat.format("{0,date,yyyy-MM-dd HH:mm}: {1}", new Date(), message));
+			writer.newLine();
+			writer.flush();
+		} catch (Exception e) {
+			LoggerFactory.getLogger(GitblitAuthority.class).error("Failed to append log entry!", e);
+		} finally {
+			if (writer != null) {
+				try {
+					writer.close();
+				} catch (IOException e) {
+				}
+			}
+		}
+	}
+	
+	private boolean sendEmail(UserModel user, X509Metadata metadata, File zip) {
+		// send email
+		try {
+			if (mail.isReady()) {
+				Message message = mail.createMessage(user.emailAddress);
+				message.setSubject("Your Gitblit client certificate for " + metadata.serverHostname);
+
+				// body of email
+				String body = X509Utils.processTemplate(new File(folder, X509Utils.CERTS + File.separator + "mail.tmpl"), metadata);
+				if (StringUtils.isEmpty(body)) {
+					body = MessageFormat.format("Hi {0}\n\nHere is your client certificate bundle.\nInside the zip file are installation instructions.", user.getDisplayName());
+				}
+				Multipart mp = new MimeMultipart();
+				MimeBodyPart messagePart = new MimeBodyPart();
+				messagePart.setText(body);
+				mp.addBodyPart(messagePart);
+
+				// attach zip
+				MimeBodyPart filePart = new MimeBodyPart();
+				FileDataSource fds = new FileDataSource(zip);
+				filePart.setDataHandler(new DataHandler(fds));
+				filePart.setFileName(fds.getName());
+				mp.addBodyPart(filePart);
+
+				message.setContent(mp);
+
+				mail.sendNow(message);
+				return true;
+			} else {
+				JOptionPane.showMessageDialog(GitblitAuthority.this, "Sorry, the mail server settings are not configured properly.\nCan not send email.", Translation.get("gb.error"), JOptionPane.ERROR_MESSAGE);
+			}
+		} catch (Exception e) {
+			Utils.showException(GitblitAuthority.this, e);
+		}
+		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());
+		if (certificatesConfigFile.exists()) {
+			try {
+				config.load();
+			} catch (Exception e) {
+				Utils.showException(GitblitAuthority.this, e);
+			}
+			NewCertificateConfig certificateConfig = NewCertificateConfig.KEY.parse(config);
+			certificateConfig.update(metadata);
+		}
+	}
+	
+	private void updateAuthorityConfig(UserCertificateModel ucm) {
+		File certificatesConfigFile = new File(folder, X509Utils.CA_CONFIG);
+		FileBasedConfig config = new FileBasedConfig(certificatesConfigFile, FS.detect());
+		if (certificatesConfigFile.exists()) {
+			try {
+				config.load();
+			} catch (Exception e) {
+				Utils.showException(GitblitAuthority.this, e);
+			}
+		}
+		ucm.update(config);
+		try {
+			config.save();
+		} catch (Exception e) {
+			Utils.showException(GitblitAuthority.this, e);
+		}
+	}
+}
diff --git a/src/main/java/com/gitblit/authority/Launcher.java b/src/main/java/com/gitblit/authority/Launcher.java
new file mode 100644
index 0000000..1da9714
--- /dev/null
+++ b/src/main/java/com/gitblit/authority/Launcher.java
@@ -0,0 +1,165 @@
+/*
+ * 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.authority;
+
+import java.awt.Color;
+import java.awt.EventQueue;
+import java.awt.FontMetrics;
+import java.awt.Graphics2D;
+import java.awt.SplashScreen;
+import java.io.File;
+import java.io.FileFilter;
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import com.gitblit.Constants;
+import com.gitblit.client.Translation;
+
+/**
+ * Downloads dependencies and launches Gitblit Authority.
+ * 
+ * @author James Moger
+ * 
+ */
+public class Launcher {
+
+	public static final boolean DEBUG = false;
+
+	/**
+	 * Parameters of the method to add an URL to the System classes.
+	 */
+	private static final Class<?>[] PARAMETERS = new Class[] { URL.class };
+
+
+	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);
+		Collections.reverse(jars);
+		for (File jar : jars) {
+			try {
+				updateSplash(splash, Translation.get("gb.loading") + " " + jar.getName() + "...");
+				addJarFile(jar);
+			} catch (IOException e) {
+
+			}
+		}
+		
+		updateSplash(splash, Translation.get("gb.starting") + " Gitblit Authority...");
+		GitblitAuthority.main(args);
+	}
+
+	private static void updateSplash(final SplashScreen splash, final String string) {
+		if (splash == null) {
+			return;
+		}
+		try {
+			EventQueue.invokeAndWait(new Runnable() {
+				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();
+						int x = 5;
+						int y = 115;
+						int w = 320 - 2 * x;
+						g.fillRect(x, y - h, w, h);
+						g.setColor(Color.lightGray);
+						g.drawRect(x, y - h, w, h);
+						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);
+						g.drawString(ver, 320 - vw - 5, 34);
+						g.dispose();
+						splash.update();
+					}
+				}
+			});
+		} catch (Throwable t) {
+			t.printStackTrace();
+		}
+	}
+	
+	public static List<File> findJars(File folder) {
+		List<File> jars = new ArrayList<File>();
+		if (folder.exists()) {
+			File[] libs = folder.listFiles(new FileFilter() {
+				@Override
+				public boolean accept(File file) {
+					return !file.isDirectory() && file.getName().toLowerCase().endsWith(".jar");
+				}
+			});
+			if (libs != null && libs.length > 0) {
+				jars.addAll(Arrays.asList(libs));
+				if (DEBUG) {
+					for (File jar : jars) {
+						System.out.println("found " + jar);
+					}
+				}
+			}
+		}
+
+		return jars;
+	}
+
+	/**
+	 * Adds a file to the classpath
+	 * 
+	 * @param f
+	 *            the file to be added
+	 * @throws IOException
+	 */
+	public static void addJarFile(File f) throws IOException {
+		if (f.getName().indexOf("-sources") > -1 || f.getName().indexOf("-javadoc") > -1) {
+			// don't add source or javadoc jars to runtime classpath
+			return;
+		}
+		URL u = f.toURI().toURL();
+		if (DEBUG) {
+			System.out.println("load=" + u.toExternalForm());
+		}
+		URLClassLoader sysloader = (URLClassLoader) ClassLoader.getSystemClassLoader();
+		Class<?> sysclass = URLClassLoader.class;
+		try {
+			Method method = sysclass.getDeclaredMethod("addURL", PARAMETERS);
+			method.setAccessible(true);
+			method.invoke(sysloader, new Object[] { u });
+		} catch (Throwable t) {
+			throw new IOException(MessageFormat.format(
+					"Error, could not add {0} to system classloader", f.getPath()), t);
+		}
+	}
+}
diff --git a/src/com/gitblit/authority/NewCertificateConfig.java b/src/main/java/com/gitblit/authority/NewCertificateConfig.java
similarity index 100%
rename from src/com/gitblit/authority/NewCertificateConfig.java
rename to src/main/java/com/gitblit/authority/NewCertificateConfig.java
diff --git a/src/com/gitblit/authority/NewClientCertificateDialog.java b/src/main/java/com/gitblit/authority/NewClientCertificateDialog.java
similarity index 100%
rename from src/com/gitblit/authority/NewClientCertificateDialog.java
rename to src/main/java/com/gitblit/authority/NewClientCertificateDialog.java
diff --git a/src/com/gitblit/authority/NewSSLCertificateDialog.java b/src/main/java/com/gitblit/authority/NewSSLCertificateDialog.java
similarity index 100%
rename from src/com/gitblit/authority/NewSSLCertificateDialog.java
rename to src/main/java/com/gitblit/authority/NewSSLCertificateDialog.java
diff --git a/src/main/java/com/gitblit/authority/RequestFocusListener.java b/src/main/java/com/gitblit/authority/RequestFocusListener.java
new file mode 100644
index 0000000..82eba6a
--- /dev/null
+++ b/src/main/java/com/gitblit/authority/RequestFocusListener.java
@@ -0,0 +1,80 @@
+/*
+ * 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.authority;
+import javax.swing.JComponent;
+import javax.swing.event.AncestorEvent;
+import javax.swing.event.AncestorListener;
+
+/**
+ *  Convenience class to request focus on a component.
+ *
+ *  When the component is added to a realized Window then component will
+ *  request focus immediately, since the ancestorAdded event is fired
+ *  immediately.
+ *
+ *  When the component is added to a non realized Window, then the focus
+ *  request will be made once the window is realized, since the
+ *  ancestorAdded event will not be fired until then.
+ *
+ *  Using the default constructor will cause the listener to be removed
+ *  from the component once the AncestorEvent is generated. A second constructor
+ *  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
+{
+	private boolean removeListener;
+
+	/*
+	 *  Convenience constructor. The listener is only used once and then it is
+	 *  removed from the component.
+	 */
+	public RequestFocusListener()
+	{
+		this(true);
+	}
+
+	/*
+	 *  Constructor that controls whether this listen can be used once or
+	 *  multiple times.
+	 *
+	 *  @param removeListener when true this listener is only invoked once
+	 *                        otherwise it can be invoked multiple times.
+	 */
+	public RequestFocusListener(boolean removeListener)
+	{
+		this.removeListener = removeListener;
+	}
+
+	@Override
+	public void ancestorAdded(AncestorEvent e)
+	{
+		JComponent component = e.getComponent();
+		component.requestFocusInWindow();
+
+		if (removeListener)
+			component.removeAncestorListener( this );
+	}
+
+	@Override
+	public void ancestorMoved(AncestorEvent e) {}
+
+	@Override
+	public void ancestorRemoved(AncestorEvent e) {}
+}
diff --git a/src/com/gitblit/authority/UserCertificateConfig.java b/src/main/java/com/gitblit/authority/UserCertificateConfig.java
similarity index 100%
rename from src/com/gitblit/authority/UserCertificateConfig.java
rename to src/main/java/com/gitblit/authority/UserCertificateConfig.java
diff --git a/src/com/gitblit/authority/UserCertificateModel.java b/src/main/java/com/gitblit/authority/UserCertificateModel.java
similarity index 100%
rename from src/com/gitblit/authority/UserCertificateModel.java
rename to src/main/java/com/gitblit/authority/UserCertificateModel.java
diff --git a/src/com/gitblit/authority/UserCertificatePanel.java b/src/main/java/com/gitblit/authority/UserCertificatePanel.java
similarity index 100%
rename from src/com/gitblit/authority/UserCertificatePanel.java
rename to src/main/java/com/gitblit/authority/UserCertificatePanel.java
diff --git a/src/com/gitblit/authority/UserCertificateTableModel.java b/src/main/java/com/gitblit/authority/UserCertificateTableModel.java
similarity index 100%
rename from src/com/gitblit/authority/UserCertificateTableModel.java
rename to src/main/java/com/gitblit/authority/UserCertificateTableModel.java
diff --git a/src/main/java/com/gitblit/authority/UserOidsPanel.java b/src/main/java/com/gitblit/authority/UserOidsPanel.java
new file mode 100644
index 0000000..5a33b3f
--- /dev/null
+++ b/src/main/java/com/gitblit/authority/UserOidsPanel.java
@@ -0,0 +1,95 @@
+/*
+ * 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.authority;
+
+import java.awt.GridLayout;
+
+import javax.swing.JPanel;
+import javax.swing.JTextField;
+
+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;
+	private JTextField organizationalUnit;
+	private JTextField organization;
+	private JTextField locality;
+	private JTextField stateProvince;
+	private JTextField countryCode;
+
+	public UserOidsPanel() {
+		super();
+		
+		displayname = new JTextField(20);
+		username = new JTextField(20);
+		username.setEditable(false);
+		emailAddress = new JTextField(20);
+		organizationalUnit = new JTextField(20);
+		organization = new JTextField(20);
+		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));
+		add(Utils.newFieldPanel(Translation.get("gb.emailAddress") + " (E)", emailAddress));
+		add(Utils.newFieldPanel(Translation.get("gb.organizationalUnit") + " (OU)", organizationalUnit));
+		add(Utils.newFieldPanel(Translation.get("gb.organization") + " (O)", organization));
+		add(Utils.newFieldPanel(Translation.get("gb.locality") + " (L)", locality));
+		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());
+		username.setText(ucm == null ? "" : ucm.user.username);
+		emailAddress.setText(ucm == null ? "" : ucm.user.emailAddress);
+		organizationalUnit.setText(ucm == null ? "" : ucm.user.organizationalUnit);
+		organization.setText(ucm == null ? "" : ucm.user.organization);
+		locality.setText(ucm == null ? "" : ucm.user.locality);
+		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);
+		emailAddress.setEditable(editable);
+		organizationalUnit.setEditable(editable);
+		organization.setEditable(editable);
+		locality.setEditable(editable);
+		stateProvince.setEditable(editable);
+		countryCode.setEditable(editable);
+	}
+	
+	protected void updateUser(UserCertificateModel ucm) {
+		ucm.user.displayName = displayname.getText();
+		ucm.user.username = username.getText();
+		ucm.user.emailAddress = emailAddress.getText();
+		ucm.user.organizationalUnit = organizationalUnit.getText();
+		ucm.user.organization = organization.getText();
+		ucm.user.locality = locality.getText();
+		ucm.user.stateProvince = stateProvince.getText();
+		ucm.user.countryCode = countryCode.getText();
+	}
+}
diff --git a/src/com/gitblit/authority/Utils.java b/src/main/java/com/gitblit/authority/Utils.java
similarity index 100%
rename from src/com/gitblit/authority/Utils.java
rename to src/main/java/com/gitblit/authority/Utils.java
diff --git a/src/com/gitblit/authority/X509CertificateViewer.java b/src/main/java/com/gitblit/authority/X509CertificateViewer.java
similarity index 100%
rename from src/com/gitblit/authority/X509CertificateViewer.java
rename to src/main/java/com/gitblit/authority/X509CertificateViewer.java
diff --git a/src/com/gitblit/client/BooleanCellRenderer.java b/src/main/java/com/gitblit/client/BooleanCellRenderer.java
similarity index 100%
rename from src/com/gitblit/client/BooleanCellRenderer.java
rename to src/main/java/com/gitblit/client/BooleanCellRenderer.java
diff --git a/src/main/java/com/gitblit/client/BranchRenderer.java b/src/main/java/com/gitblit/client/BranchRenderer.java
new file mode 100644
index 0000000..5f12c42
--- /dev/null
+++ b/src/main/java/com/gitblit/client/BranchRenderer.java
@@ -0,0 +1,83 @@
+/*
+ * 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.client;
+
+import java.awt.Color;
+import java.awt.Component;
+
+import javax.swing.JList;
+import javax.swing.JTable;
+import javax.swing.ListCellRenderer;
+import javax.swing.table.DefaultTableCellRenderer;
+
+/**
+ * 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 {
+
+	private static final long serialVersionUID = 1L;
+
+	private static final String R_HEADS = "refs/heads/";
+
+	private static final String R_REMOTES = "refs/remotes/";
+	
+	private static final String R_CHANGES = "refs/changes/";
+
+	public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
+			boolean hasFocus, int row, int column) {
+		super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
+		setText(value == null ? "" : value.toString());
+		if (isSelected) {
+			setForeground(table.getSelectionForeground());
+		}
+		return this;
+	}
+
+	@Override
+	public Component getListCellRendererComponent(JList list, Object value, int index,
+			boolean isSelected, boolean cellHasFocus) {
+		setText(value == null ? "" : value.toString());
+		if (isSelected) {
+			setBackground(list.getSelectionBackground());
+			setForeground(list.getSelectionForeground());
+		} else {
+			setBackground(list.getBackground());
+		}
+		return this;
+	}
+
+	@Override
+	public void setText(String text) {
+		String name = text;
+		Color fg = getForeground();
+		if (name.startsWith(R_HEADS)) {
+			name = name.substring(R_HEADS.length());
+			fg = new Color(0, 0x80, 0);
+		} else if (name.startsWith(R_REMOTES)) {
+			name = name.substring(R_REMOTES.length());
+			fg = Color.decode("#6C6CBF");
+		} else if (name.startsWith(R_CHANGES)) {
+			name = name.substring(R_CHANGES.length());
+			fg = Color.decode("#B0E0F0");
+		}
+		setForeground(fg);
+		super.setText(name);
+	}
+}
\ No newline at end of file
diff --git a/src/com/gitblit/client/ClosableTabComponent.java b/src/main/java/com/gitblit/client/ClosableTabComponent.java
similarity index 100%
rename from src/com/gitblit/client/ClosableTabComponent.java
rename to src/main/java/com/gitblit/client/ClosableTabComponent.java
diff --git a/src/com/gitblit/client/DateCellRenderer.java b/src/main/java/com/gitblit/client/DateCellRenderer.java
similarity index 100%
rename from src/com/gitblit/client/DateCellRenderer.java
rename to src/main/java/com/gitblit/client/DateCellRenderer.java
diff --git a/src/com/gitblit/client/EditRegistrationDialog.java b/src/main/java/com/gitblit/client/EditRegistrationDialog.java
similarity index 100%
rename from src/com/gitblit/client/EditRegistrationDialog.java
rename to src/main/java/com/gitblit/client/EditRegistrationDialog.java
diff --git a/src/main/java/com/gitblit/client/EditRepositoryDialog.java b/src/main/java/com/gitblit/client/EditRepositoryDialog.java
new file mode 100644
index 0000000..118c5c8
--- /dev/null
+++ b/src/main/java/com/gitblit/client/EditRepositoryDialog.java
@@ -0,0 +1,811 @@
+/*
+ * 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.client;
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.Font;
+import java.awt.GridLayout;
+import java.awt.Insets;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.awt.event.KeyEvent;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.swing.BoxLayout;
+import javax.swing.ButtonGroup;
+import javax.swing.DefaultListCellRenderer;
+import javax.swing.ImageIcon;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JComboBox;
+import javax.swing.JComponent;
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JRadioButton;
+import javax.swing.JRootPane;
+import javax.swing.JScrollPane;
+import javax.swing.JTabbedPane;
+import javax.swing.JTextField;
+import javax.swing.KeyStroke;
+import javax.swing.ListCellRenderer;
+import javax.swing.ScrollPaneConstants;
+
+import com.gitblit.Constants.AccessRestrictionType;
+import com.gitblit.Constants.AuthorizationControl;
+import com.gitblit.Constants.FederationStrategy;
+import com.gitblit.Constants.RegistrantType;
+import com.gitblit.models.RegistrantAccessPermission;
+import com.gitblit.models.RepositoryModel;
+import com.gitblit.utils.ArrayUtils;
+import com.gitblit.utils.StringUtils;
+
+/**
+ * Dialog to create/edit a repository.
+ * 
+ * @author James Moger
+ */
+public class EditRepositoryDialog extends JDialog {
+
+	private static final long serialVersionUID = 1L;
+
+	private final String repositoryName;
+
+	private final RepositoryModel repository;
+
+	private boolean isCreate;
+
+	private boolean canceled = true;
+
+	private JTextField nameField;
+
+	private JTextField descriptionField;
+
+	private JCheckBox useTickets;
+
+	private JCheckBox useDocs;
+
+	private JCheckBox useIncrementalPushTags;
+	
+	private JCheckBox showRemoteBranches;
+
+	private JCheckBox showReadme;
+
+	private JCheckBox skipSizeCalculation;
+
+	private JCheckBox skipSummaryMetrics;
+
+	private JCheckBox isFrozen;
+
+	private JTextField mailingListsField;
+
+	private JComboBox accessRestriction;
+	
+	private JRadioButton allowAuthenticated;
+	
+	private JRadioButton allowNamed;
+	
+	private JCheckBox allowForks;
+
+	private JCheckBox verifyCommitter;
+
+	private JComboBox federationStrategy;
+
+	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;
+
+	private JLabel preReceiveInherited;
+
+	private JPalette<String> postReceivePalette;
+
+	private JLabel postReceiveInherited;
+
+	private Set<String> repositoryNames;
+	
+	private JPanel customFieldsPanel;
+	
+	private List<JTextField> customTextfields;
+
+	public EditRepositoryDialog(int protocolVersion) {
+		this(protocolVersion, new RepositoryModel());
+		this.isCreate = true;
+		setTitle(Translation.get("gb.newRepository"));
+	}
+
+	public EditRepositoryDialog(int protocolVersion, RepositoryModel aRepository) {
+		super();
+		this.repositoryName = aRepository.name;
+		this.repository = new RepositoryModel();
+		this.repositoryNames = new HashSet<String>();
+		this.isCreate = false;
+		initialize(protocolVersion, aRepository);
+		setModal(true);
+		setResizable(false);
+		setTitle(Translation.get("gb.edit") + ": " + aRepository.name);
+		setIconImage(new ImageIcon(getClass()
+				.getResource("/gitblt-favicon.png")).getImage());
+	}
+
+	@Override
+	protected JRootPane createRootPane() {
+		KeyStroke stroke = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0);
+		JRootPane rootPane = new JRootPane();
+		rootPane.registerKeyboardAction(new ActionListener() {
+			public void actionPerformed(ActionEvent actionEvent) {
+				setVisible(false);
+			}
+		}, stroke, JComponent.WHEN_IN_FOCUSED_WINDOW);
+		return rootPane;
+	}
+
+	private void initialize(int protocolVersion, RepositoryModel anRepository) {
+		nameField = new JTextField(anRepository.name == null ? ""
+				: anRepository.name, 35);
+		descriptionField = new JTextField(anRepository.description == null ? ""
+				: anRepository.description, 35);
+
+		JTextField originField = new JTextField(
+				anRepository.origin == null ? "" : anRepository.origin, 40);
+		originField.setEditable(false);
+
+		if (ArrayUtils.isEmpty(anRepository.availableRefs)) {
+			headRefField = new JComboBox();
+			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);
+
+		ownersPalette = new JPalette<String>(true);
+
+		useTickets = new JCheckBox(Translation.get("gb.useTicketsDescription"),
+				anRepository.useTickets);
+		useDocs = new JCheckBox(Translation.get("gb.useDocsDescription"),
+				anRepository.useDocs);
+		useIncrementalPushTags = new JCheckBox(Translation.get("gb.useIncrementalPushTagsDescription"),
+				anRepository.useIncrementalPushTags);
+		showRemoteBranches = new JCheckBox(
+				Translation.get("gb.showRemoteBranchesDescription"),
+				anRepository.showRemoteBranches);
+		showReadme = new JCheckBox(Translation.get("gb.showReadmeDescription"),
+				anRepository.showReadme);
+		skipSizeCalculation = new JCheckBox(
+				Translation.get("gb.skipSizeCalculationDescription"),
+				anRepository.skipSizeCalculation);
+		skipSummaryMetrics = new JCheckBox(
+				Translation.get("gb.skipSummaryMetricsDescription"),
+				anRepository.skipSummaryMetrics);
+		isFrozen = new JCheckBox(Translation.get("gb.isFrozenDescription"),
+				anRepository.isFrozen);
+
+		maxActivityCommits = new JComboBox(new Integer [] { -1, 0, 25, 50, 75, 100, 150, 250, 500 });
+		maxActivityCommits.setSelectedItem(anRepository.maxActivityCommits);
+
+		mailingListsField = new JTextField(
+				ArrayUtils.isEmpty(anRepository.mailingLists) ? ""
+						: StringUtils.flattenStrings(anRepository.mailingLists,
+								" "), 50);
+
+		accessRestriction = new JComboBox(AccessRestrictionType.values());
+		accessRestriction.setRenderer(new AccessRestrictionRenderer());
+		accessRestriction.setSelectedItem(anRepository.accessRestriction);
+		accessRestriction.addItemListener(new ItemListener() {
+			@Override
+			public void itemStateChanged(ItemEvent e) {
+				if (e.getStateChange() == ItemEvent.SELECTED) {
+					AccessRestrictionType art = (AccessRestrictionType) accessRestriction.getSelectedItem();
+					EditRepositoryDialog.this.setupAccessPermissions(art);
+				}
+			}
+		});
+		
+		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) {					
+					usersPalette.setEnabled(false);
+					teamsPalette.setEnabled(false);
+				}
+			}
+		});
+		
+		allowNamed = new JRadioButton(Translation.get("gb.allowNamedDescription"));
+		allowNamed.setSelected(!authenticated);
+		allowNamed.addItemListener(new ItemListener() {
+			@Override
+			public void itemStateChanged(ItemEvent e) {
+				if (e.getStateChange() == ItemEvent.SELECTED) {
+					usersPalette.setEnabled(true);
+					teamsPalette.setEnabled(true);
+				}
+			}
+		});
+		
+		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);
+
+		// federation strategies - remove ORIGIN choice if this repository has
+		// no origin.
+		List<FederationStrategy> federationStrategies = new ArrayList<FederationStrategy>(
+				Arrays.asList(FederationStrategy.values()));
+		if (StringUtils.isEmpty(anRepository.origin)) {
+			federationStrategies.remove(FederationStrategy.FEDERATE_ORIGIN);
+		}
+		federationStrategy = new JComboBox(federationStrategies.toArray());
+		federationStrategy.setRenderer(new FederationStrategyRenderer());
+		federationStrategy.setSelectedItem(anRepository.federationStrategy);
+
+		JPanel fieldsPanel = new JPanel(new GridLayout(0, 1));
+		fieldsPanel.add(newFieldPanel(Translation.get("gb.name"), nameField));
+		fieldsPanel.add(newFieldPanel(Translation.get("gb.description"),
+				descriptionField));
+		fieldsPanel
+				.add(newFieldPanel(Translation.get("gb.origin"), originField));
+		fieldsPanel.add(newFieldPanel(Translation.get("gb.headRef"), headRefField));
+		fieldsPanel.add(newFieldPanel(Translation.get("gb.gcPeriod"), gcPeriod));
+		fieldsPanel.add(newFieldPanel(Translation.get("gb.gcThreshold"), gcThreshold));
+
+		fieldsPanel.add(newFieldPanel(Translation.get("gb.enableTickets"),
+				useTickets));
+		fieldsPanel
+				.add(newFieldPanel(Translation.get("gb.enableDocs"), useDocs));
+		fieldsPanel
+		.add(newFieldPanel(Translation.get("gb.enableIncrementalPushTags"), useIncrementalPushTags));
+		fieldsPanel.add(newFieldPanel(Translation.get("gb.showRemoteBranches"),
+				showRemoteBranches));
+		fieldsPanel.add(newFieldPanel(Translation.get("gb.showReadme"),
+				showReadme));
+		fieldsPanel
+				.add(newFieldPanel(Translation.get("gb.skipSizeCalculation"),
+						skipSizeCalculation));
+		fieldsPanel.add(newFieldPanel(Translation.get("gb.skipSummaryMetrics"),
+				skipSummaryMetrics));
+		fieldsPanel.add(newFieldPanel(Translation.get("gb.maxActivityCommits"),
+				maxActivityCommits));
+		fieldsPanel.add(newFieldPanel(Translation.get("gb.mailingLists"),
+				mailingListsField));
+
+		JPanel clonePushPanel = new JPanel(new GridLayout(0, 1));
+		clonePushPanel
+		.add(newFieldPanel(Translation.get("gb.isFrozen"), isFrozen));
+		clonePushPanel
+		.add(newFieldPanel(Translation.get("gb.allowForks"), allowForks));
+		clonePushPanel
+		.add(newFieldPanel(Translation.get("gb.verifyCommitter"), verifyCommitter));
+
+		usersPalette = new RegistrantPermissionsPanel(RegistrantType.USER);
+
+		JPanel northFieldsPanel = new JPanel(new BorderLayout(0, 5));
+		northFieldsPanel.add(newFieldPanel(Translation.get("gb.owners"), ownersPalette), BorderLayout.NORTH);
+		northFieldsPanel.add(newFieldPanel(Translation.get("gb.accessRestriction"),
+				accessRestriction), BorderLayout.CENTER);
+
+		JPanel northAccessPanel = new JPanel(new BorderLayout(5, 5));
+		northAccessPanel.add(northFieldsPanel, BorderLayout.NORTH);
+		northAccessPanel.add(newFieldPanel(Translation.get("gb.authorizationControl"),
+				authorizationPanel), BorderLayout.CENTER);
+		northAccessPanel.add(clonePushPanel, BorderLayout.SOUTH);
+
+		JPanel accessPanel = new JPanel(new BorderLayout(5, 5));
+		accessPanel.add(northAccessPanel, BorderLayout.NORTH);
+		accessPanel.add(newFieldPanel(Translation.get("gb.userPermissions"),
+						usersPalette), BorderLayout.CENTER);
+
+		teamsPalette = new RegistrantPermissionsPanel(RegistrantType.TEAM);
+		JPanel teamsPanel = new JPanel(new BorderLayout(5, 5));
+		teamsPanel.add(
+				newFieldPanel(Translation.get("gb.teamPermissions"),
+						teamsPalette), BorderLayout.CENTER);
+
+		setsPalette = new JPalette<String>();
+		JPanel federationPanel = new JPanel(new BorderLayout(5, 5));
+		federationPanel.add(
+				newFieldPanel(Translation.get("gb.federationStrategy"),
+						federationStrategy), BorderLayout.NORTH);
+		federationPanel
+				.add(newFieldPanel(Translation.get("gb.federationSets"),
+						setsPalette), BorderLayout.CENTER);
+
+		indexedBranchesPalette = new JPalette<String>();
+		JPanel indexedBranchesPanel = new JPanel(new BorderLayout(5, 5));
+		indexedBranchesPanel
+				.add(newFieldPanel(Translation.get("gb.indexedBranches"),
+						indexedBranchesPalette), BorderLayout.CENTER);
+
+		preReceivePalette = new JPalette<String>(true);
+		preReceiveInherited = new JLabel();
+		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));
+		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);
+		customFieldsScrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
+		customFieldsScrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
+
+		JTabbedPane panel = new JTabbedPane(JTabbedPane.TOP);
+		panel.addTab(Translation.get("gb.general"), fieldsPanel);
+		panel.addTab(Translation.get("gb.accessRestriction"), accessPanel);
+		if (protocolVersion >= 2) {
+			panel.addTab(Translation.get("gb.teams"), teamsPanel);
+		}
+		panel.addTab(Translation.get("gb.federation"), federationPanel);
+		if (protocolVersion >= 3) {
+			panel.addTab(Translation.get("gb.indexedBranches"), indexedBranchesPanel);
+		}
+		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() {
+			public void actionPerformed(ActionEvent event) {
+				if (validateFields()) {
+					canceled = false;
+					setVisible(false);
+				}
+			}
+		});
+
+		JButton cancelButton = new JButton(Translation.get("gb.cancel"));
+		cancelButton.addActionListener(new ActionListener() {
+			public void actionPerformed(ActionEvent event) {
+				canceled = true;
+				setVisible(false);
+			}
+		});
+
+		JPanel controls = new JPanel();
+		controls.add(cancelButton);
+		controls.add(createButton);
+
+		final Insets _insets = new Insets(5, 5, 5, 5);
+		JPanel centerPanel = new JPanel(new BorderLayout(5, 5)) {
+
+			private static final long serialVersionUID = 1L;
+
+			@Override
+			public Insets getInsets() {
+				return _insets;
+			}
+		};
+		centerPanel.add(panel, BorderLayout.CENTER);
+		centerPanel.add(controls, BorderLayout.SOUTH);
+
+		getContentPane().setLayout(new BorderLayout(5, 5));
+		getContentPane().add(centerPanel, BorderLayout.CENTER);
+		pack();
+		nameField.requestFocus();
+	}
+	
+	private JPanel newFieldPanel(String label, JComponent comp) {
+		return newFieldPanel(label, 150, comp);
+	}
+
+	private JPanel newFieldPanel(String label, int labelSize, JComponent comp) {
+		JLabel fieldLabel = new JLabel(label);
+		fieldLabel.setFont(fieldLabel.getFont().deriveFont(Font.BOLD));
+		fieldLabel.setPreferredSize(new Dimension(labelSize, 20));
+		JPanel panel = new JPanel(new FlowLayout(FlowLayout.LEFT, 10, 0));
+		panel.add(fieldLabel);
+		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);
+		} else {
+			allowAuthenticated.setEnabled(true);
+			allowNamed.setEnabled(true);
+			verifyCommitter.setEnabled(true);
+			
+			if (allowNamed.isSelected()) {
+				usersPalette.setEnabled(true);
+				teamsPalette.setEnabled(true);
+			}
+		}
+
+	}
+
+	private boolean validateFields() {
+		String rname = nameField.getText();
+		if (StringUtils.isEmpty(rname)) {
+			error("Please enter a repository name!");
+			return false;
+		}
+
+		// automatically convert backslashes to forward slashes
+		rname = rname.replace('\\', '/');
+		// Automatically replace // with /
+		rname = rname.replace("//", "/");
+
+		// prohibit folder paths
+		if (rname.startsWith("/")) {
+			error("Leading root folder references (/) are prohibited.");
+			return false;
+		}
+		if (rname.startsWith("../")) {
+			error("Relative folder references (../) are prohibited.");
+			return false;
+		}
+		if (rname.contains("/../")) {
+			error("Relative folder references (../) are prohibited.");
+			return false;
+		}
+		if (rname.endsWith("/")) {
+			rname = rname.substring(0, rname.length() - 1);
+		}
+
+		// confirm valid characters in repository name
+		Character c = StringUtils.findInvalidCharacter(rname);
+		if (c != null) {
+			error(MessageFormat.format(
+					"Illegal character ''{0}'' in repository name!", c));
+			return false;
+		}
+
+		// verify repository name uniqueness on create
+		if (isCreate) {
+			// force repo names to lowercase
+			// this means that repository name checking for rpc creation
+			// is case-insensitive, regardless of the Gitblit server's
+			// filesystem
+			if (repositoryNames.contains(rname.toLowerCase())) {
+				error(MessageFormat
+						.format("Can not create repository ''{0}'' because it already exists.",
+								rname));
+				return false;
+			}
+		} else {
+			// check rename collision
+			if (!repositoryName.equalsIgnoreCase(rname)) {
+				if (repositoryNames.contains(rname.toLowerCase())) {
+					error(MessageFormat
+							.format("Failed to rename ''{0}'' because ''{1}'' already exists.",
+									repositoryName, rname));
+					return false;
+				}
+			}
+		}
+
+		if (accessRestriction.getSelectedItem() == null) {
+			error("Please select access restriction!");
+			return false;
+		}
+
+		if (federationStrategy.getSelectedItem() == null) {
+			error("Please select federation strategy!");
+			return false;
+		}
+
+		repository.name = rname;
+		repository.description = descriptionField.getText();
+		repository.owners.clear();
+		repository.owners.addAll(ownersPalette.getSelections());
+		repository.HEAD = headRefField.getSelectedItem() == null ? null
+				: headRefField.getSelectedItem().toString();
+		repository.gcPeriod = (Integer) gcPeriod.getSelectedItem();
+		repository.gcThreshold = gcThreshold.getText();
+		repository.useTickets = useTickets.isSelected();
+		repository.useDocs = useDocs.isSelected();
+		repository.useIncrementalPushTags = useIncrementalPushTags.isSelected();
+		repository.showRemoteBranches = showRemoteBranches.isSelected();
+		repository.showReadme = showReadme.isSelected();
+		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();
+
+		String ml = mailingListsField.getText();
+		if (!StringUtils.isEmpty(ml)) {
+			Set<String> list = new HashSet<String>();
+			for (String address : ml.split("(,|\\s)")) {
+				if (StringUtils.isEmpty(address)) {
+					continue;
+				}
+				list.add(address.toLowerCase());
+			}
+			repository.mailingLists = new ArrayList<String>(list);
+		}
+
+		repository.accessRestriction = (AccessRestrictionType) accessRestriction
+				.getSelectedItem();
+		repository.authorizationControl = allowAuthenticated.isSelected() ? 
+				AuthorizationControl.AUTHENTICATED : AuthorizationControl.NAMED;
+		repository.federationStrategy = (FederationStrategy) federationStrategy
+				.getSelectedItem();
+
+		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) {
+			for (JTextField field : customTextfields) {
+				String key = field.getName();
+				String value = field.getText();
+				repository.customFields.put(key, value);
+			}
+		}
+		return true;
+	}
+
+	private void error(String message) {
+		JOptionPane.showMessageDialog(EditRepositoryDialog.this, message,
+				Translation.get("gb.error"), JOptionPane.ERROR_MESSAGE);
+	}
+	
+	public void setAccessRestriction(AccessRestrictionType restriction) {
+		this.accessRestriction.setSelectedItem(restriction);
+		setupAccessPermissions(restriction);
+	}
+
+	public void setAuthorizationControl(AuthorizationControl authorization) {
+		boolean authenticated = authorization != null && AuthorizationControl.AUTHENTICATED.equals(authorization);
+		this.allowAuthenticated.setSelected(authenticated);
+		this.allowNamed.setSelected(!authenticated);
+	}
+
+	public void setUsers(List<String> owners, List<String> all, List<RegistrantAccessPermission> permissions) {
+		ownersPalette.setObjects(all, owners);
+		usersPalette.setObjects(all, permissions);
+	}
+
+	public void setTeams(List<String> all, List<RegistrantAccessPermission> permissions) {
+		teamsPalette.setObjects(all, permissions);
+	}
+
+	public void setRepositories(List<RepositoryModel> repositories) {
+		repositoryNames.clear();
+		for (RepositoryModel repository : repositories) {
+			// force repo names to lowercase
+			// this means that repository name checking for rpc creation
+			// is case-insensitive, regardless of the Gitblit server's
+			// filesystem
+			repositoryNames.add(repository.name.toLowerCase());
+		}
+	}
+
+	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);
+	}
+
+	public void setPreReceiveScripts(List<String> all, List<String> inherited,
+			List<String> selected) {
+		preReceivePalette.setObjects(all, selected);
+		showInherited(inherited, preReceiveInherited);
+	}
+
+	public void setPostReceiveScripts(List<String> all, List<String> inherited,
+			List<String> selected) {
+		postReceivePalette.setObjects(all, selected);
+		showInherited(inherited, postReceiveInherited);
+	}
+
+	private void showInherited(List<String> list, JLabel label) {
+		StringBuilder sb = new StringBuilder();
+		if (list != null && list.size() > 0) {
+			sb.append("<html><body><b>INHERITED</b><ul style=\"margin-left:5px;list-style-type: none;\">");
+			for (String script : list) {
+				sb.append("<li>").append(script).append("</li>");
+			}
+			sb.append("</ul></body></html>");
+		}
+		label.setText(sb.toString());
+	}
+
+	public RepositoryModel getRepository() {
+		if (canceled) {
+			return null;
+		}
+		return repository;
+	}
+
+	public List<RegistrantAccessPermission> getUserAccessPermissions() {
+		return usersPalette.getPermissions();
+	}
+
+	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)) {
+
+			private static final long serialVersionUID = 1L;
+
+			@Override
+			public Insets getInsets() {
+				return insets;
+			}
+		};		
+		
+		for (Map.Entry<String, String> entry : customFields.entrySet()) {
+			String field = entry.getKey();
+			String value = "";
+			if (repository.customFields != null && repository.customFields.containsKey(field)) {
+				value = repository.customFields.get(field);
+			}
+			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);		
+		jsp.getVerticalScrollBar().setBlockIncrement(100);
+		jsp.getVerticalScrollBar().setUnitIncrement(100);
+		jsp.setViewportBorder(null);
+		customFieldsPanel.setLayout(new FlowLayout(FlowLayout.LEFT));
+		customFieldsPanel.add(jsp);
+	}
+
+	/**
+	 * ListCellRenderer to display descriptive text about the access
+	 * restriction.
+	 * 
+	 */
+	private class AccessRestrictionRenderer extends DefaultListCellRenderer {
+
+		private static final long serialVersionUID = 1L;
+
+		@Override
+		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) {
+				case NONE:
+					setText(Translation.get("gb.notRestricted"));
+					break;
+				case PUSH:
+					setText(Translation.get("gb.pushRestricted"));
+					break;
+				case CLONE:
+					setText(Translation.get("gb.cloneRestricted"));
+					break;
+				case VIEW:
+					setText(Translation.get("gb.viewRestricted"));
+					break;
+				}
+			} else {
+				setText(value.toString());
+			}
+			return this;
+		}
+	}
+
+	/**
+	 * ListCellRenderer to display descriptive text about the federation
+	 * strategy.
+	 */
+	private class FederationStrategyRenderer extends JLabel implements
+			ListCellRenderer {
+
+		private static final long serialVersionUID = 1L;
+
+		@Override
+		public Component getListCellRendererComponent(JList list, Object value,
+				int index, boolean isSelected, boolean cellHasFocus) {
+			if (value instanceof FederationStrategy) {
+				FederationStrategy strategy = (FederationStrategy) value;
+				switch (strategy) {
+				case EXCLUDE:
+					setText(Translation.get("gb.excludeFromFederation"));
+					break;
+				case FEDERATE_THIS:
+					setText(Translation.get("gb.federateThis"));
+					break;
+				case FEDERATE_ORIGIN:
+					setText(Translation.get("gb.federateOrigin"));
+					break;
+				}
+			} else {
+				setText(value.toString());
+			}
+			return this;
+		}
+	}
+}
diff --git a/src/com/gitblit/client/EditTeamDialog.java b/src/main/java/com/gitblit/client/EditTeamDialog.java
similarity index 100%
rename from src/com/gitblit/client/EditTeamDialog.java
rename to src/main/java/com/gitblit/client/EditTeamDialog.java
diff --git a/src/com/gitblit/client/EditUserDialog.java b/src/main/java/com/gitblit/client/EditUserDialog.java
similarity index 100%
rename from src/com/gitblit/client/EditUserDialog.java
rename to src/main/java/com/gitblit/client/EditUserDialog.java
diff --git a/src/com/gitblit/client/FeedEntryTableModel.java b/src/main/java/com/gitblit/client/FeedEntryTableModel.java
similarity index 100%
rename from src/com/gitblit/client/FeedEntryTableModel.java
rename to src/main/java/com/gitblit/client/FeedEntryTableModel.java
diff --git a/src/com/gitblit/client/FeedsPanel.java b/src/main/java/com/gitblit/client/FeedsPanel.java
similarity index 100%
rename from src/com/gitblit/client/FeedsPanel.java
rename to src/main/java/com/gitblit/client/FeedsPanel.java
diff --git a/src/com/gitblit/client/FeedsTableModel.java b/src/main/java/com/gitblit/client/FeedsTableModel.java
similarity index 100%
rename from src/com/gitblit/client/FeedsTableModel.java
rename to src/main/java/com/gitblit/client/FeedsTableModel.java
diff --git a/src/com/gitblit/client/GitblitClient.java b/src/main/java/com/gitblit/client/GitblitClient.java
similarity index 100%
rename from src/com/gitblit/client/GitblitClient.java
rename to src/main/java/com/gitblit/client/GitblitClient.java
diff --git a/src/main/java/com/gitblit/client/GitblitManager.java b/src/main/java/com/gitblit/client/GitblitManager.java
new file mode 100644
index 0000000..d2fd7f7
--- /dev/null
+++ b/src/main/java/com/gitblit/client/GitblitManager.java
@@ -0,0 +1,458 @@
+/*
+ * 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.client;
+
+import java.awt.BorderLayout;
+import java.awt.Cursor;
+import java.awt.Dimension;
+import java.awt.EventQueue;
+import java.awt.Point;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.KeyEvent;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.net.ConnectException;
+import java.text.MessageFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.TimeZone;
+
+import javax.swing.ImageIcon;
+import javax.swing.JFrame;
+import javax.swing.JMenu;
+import javax.swing.JMenuBar;
+import javax.swing.JMenuItem;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JTabbedPane;
+import javax.swing.KeyStroke;
+import javax.swing.SwingWorker;
+import javax.swing.UIManager;
+
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.lib.StoredConfig;
+import org.eclipse.jgit.storage.file.FileBasedConfig;
+import org.eclipse.jgit.util.FS;
+
+import com.gitblit.Constants;
+import com.gitblit.GitBlitException.ForbiddenException;
+import com.gitblit.models.FeedModel;
+import com.gitblit.utils.Base64;
+import com.gitblit.utils.StringUtils;
+
+/**
+ * Gitblit Manager issues JSON RPC requests to a Gitblit server.
+ * 
+ * @author James Moger
+ * 
+ */
+public class GitblitManager extends JFrame implements RegistrationsDialog.RegistrationListener {
+
+	private static final long serialVersionUID = 1L;
+	private static final String SERVER = "server";
+	private static final String FEED = "feed";
+	private final SimpleDateFormat dateFormat;
+	private JTabbedPane serverTabs;
+	private File configFile = new File(System.getProperty("user.home"), ".gitblit/config");
+
+	private Map<String, GitblitRegistration> registrations = new LinkedHashMap<String, GitblitRegistration>();
+	private JMenu recentMenu;
+	private int maxRecentCount = 5;
+
+	private GitblitManager() {
+		super();
+		dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US);
+		dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
+	}
+
+	private void initialize() {
+		setContentPane(getCenterPanel());
+		setIconImage(new ImageIcon(getClass().getResource("/gitblt-favicon.png")).getImage());
+		setTitle("Gitblit Manager v" + Constants.getVersion() + " (" + Constants.getBuildDate() + ")");
+		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+		addWindowListener(new WindowAdapter() {
+			@Override
+			public void windowClosing(WindowEvent event) {
+				saveSizeAndPosition();
+			}
+
+			@Override
+			public void windowOpened(WindowEvent event) {
+				manageRegistrations();
+			}
+		});
+
+		setSizeAndPosition();
+		loadRegistrations();
+		rebuildRecentMenu();
+	}
+
+	private void setSizeAndPosition() {
+		String sz = null;
+		String pos = null;
+		try {
+			StoredConfig config = getConfig();
+			sz = config.getString("ui", null, "size");
+			pos = config.getString("ui", null, "position");
+		} catch (Throwable t) {
+			t.printStackTrace();
+		}
+
+		// try to restore saved window size
+		if (StringUtils.isEmpty(sz)) {
+			setSize(850, 500);
+		} else {
+			String[] chunks = sz.split("x");
+			int width = Integer.parseInt(chunks[0]);
+			int height = Integer.parseInt(chunks[1]);
+			setSize(width, height);
+		}
+
+		// try to restore saved window position
+		if (StringUtils.isEmpty(pos)) {
+			setLocationRelativeTo(null);
+		} else {
+			String[] chunks = pos.split(",");
+			int x = Integer.parseInt(chunks[0]);
+			int y = Integer.parseInt(chunks[1]);
+			setLocation(x, y);
+		}
+	}
+
+	private void saveSizeAndPosition() {
+		try {
+			// save window size and position
+			StoredConfig config = getConfig();
+			Dimension sz = GitblitManager.this.getSize();
+			config.setString("ui", null, "size",
+					MessageFormat.format("{0,number,0}x{1,number,0}", sz.width, sz.height));
+			Point pos = GitblitManager.this.getLocationOnScreen();
+			config.setString("ui", null, "position",
+					MessageFormat.format("{0,number,0},{1,number,0}", pos.x, pos.y));
+			config.save();
+		} catch (Throwable t) {
+			Utils.showException(GitblitManager.this, t);
+		}
+	}
+
+	private JMenuBar setupMenu() {
+		JMenuBar menuBar = new JMenuBar();
+		JMenu serversMenu = new JMenu(Translation.get("gb.servers"));
+		menuBar.add(serversMenu);
+		recentMenu = new JMenu(Translation.get("gb.recent"));
+		serversMenu.add(recentMenu);
+
+		JMenuItem manage = new JMenuItem(Translation.get("gb.manage") + "...");
+		manage.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_M, KeyEvent.CTRL_DOWN_MASK, false));
+		manage.addActionListener(new ActionListener() {
+			public void actionPerformed(ActionEvent event) {
+				manageRegistrations();
+			}
+		});
+		serversMenu.add(manage);
+
+		return menuBar;
+	}
+
+	private JPanel getCenterPanel() {
+		serverTabs = new JTabbedPane(JTabbedPane.TOP);
+		JMenuBar menubar = setupMenu();
+		JPanel panel = new JPanel(new BorderLayout());
+		panel.add(menubar, BorderLayout.NORTH);
+		panel.add(serverTabs, BorderLayout.CENTER);
+		return panel;
+	}
+
+	private void manageRegistrations() {
+		RegistrationsDialog dialog = new RegistrationsDialog(new ArrayList<GitblitRegistration>(
+				registrations.values()), this);
+		dialog.setLocationRelativeTo(GitblitManager.this);
+		dialog.setVisible(true);
+	}
+
+	@Override
+	public void login(GitblitRegistration reg) {
+		if (!reg.savePassword && (reg.password == null || reg.password.length == 0)) {
+			// prompt for password
+			EditRegistrationDialog dialog = new EditRegistrationDialog(this, reg, true);
+			dialog.setLocationRelativeTo(GitblitManager.this);
+			dialog.setVisible(true);
+			GitblitRegistration newReg = dialog.getRegistration();
+			if (newReg == null) {
+				// user canceled
+				return;
+			}
+			// preserve feeds
+			newReg.feeds.addAll(reg.feeds);
+
+			// use new reg
+			reg = newReg;
+		}
+
+		// login
+		setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
+		final GitblitRegistration registration = reg;
+		final GitblitPanel panel = new GitblitPanel(registration, this);
+		SwingWorker<Boolean, Void> worker = new SwingWorker<Boolean, Void>() {
+
+			@Override
+			protected Boolean doInBackground() throws IOException {
+				panel.login();
+				return true;
+			}
+
+			@Override
+			protected void done() {
+				try {
+					boolean success = get();
+					serverTabs.addTab(registration.name, panel);
+					int idx = serverTabs.getTabCount() - 1;
+					serverTabs.setSelectedIndex(idx);
+					serverTabs.setTabComponentAt(idx, new ClosableTabComponent(registration.name,
+							null, serverTabs, panel));
+					registration.lastLogin = new Date();
+					saveRegistration(registration.name, registration);
+					registrations.put(registration.name, registration);
+					rebuildRecentMenu();
+					if (!registration.savePassword) {
+						// clear password
+						registration.password = null;
+					}
+				} catch (Throwable t) {
+					Throwable cause = t.getCause();
+					if (cause instanceof ConnectException) {
+						JOptionPane.showMessageDialog(GitblitManager.this, cause.getMessage(),
+								Translation.get("gb.error"), JOptionPane.ERROR_MESSAGE);
+					} else if (cause instanceof ForbiddenException) {
+						JOptionPane
+								.showMessageDialog(
+										GitblitManager.this,
+										"This Gitblit server does not allow RPC Management or Administration",
+										Translation.get("gb.error"), JOptionPane.ERROR_MESSAGE);
+					} else {
+						Utils.showException(GitblitManager.this, t);
+					}
+				} finally {
+					setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+				}
+			}
+		};
+		worker.execute();
+	}
+
+	private void rebuildRecentMenu() {
+		recentMenu.removeAll();
+		ImageIcon icon = new ImageIcon(getClass().getResource("/gitblt-favicon.png"));
+		List<GitblitRegistration> list = new ArrayList<GitblitRegistration>(registrations.values());
+		Collections.sort(list, new Comparator<GitblitRegistration>() {
+			@Override
+			public int compare(GitblitRegistration o1, GitblitRegistration o2) {
+				return o2.lastLogin.compareTo(o1.lastLogin);
+			}
+		});
+		if (list.size() > maxRecentCount) {
+			list = list.subList(0, maxRecentCount);
+		}
+		for (int i = 0; i < list.size(); i++) {
+			final GitblitRegistration reg = list.get(i);
+			JMenuItem item = new JMenuItem(reg.name, icon);
+			item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_1 + i, KeyEvent.CTRL_DOWN_MASK,
+					false));
+			item.addActionListener(new ActionListener() {
+				public void actionPerformed(ActionEvent e) {
+					login(reg);
+				}
+			});
+			recentMenu.add(item);
+		}
+	}
+
+	private void loadRegistrations() {
+		try {
+			StoredConfig config = getConfig();
+			Set<String> servers = config.getSubsections(SERVER);
+			for (String server : servers) {
+				Date lastLogin = new Date(0);
+				String date = config.getString(SERVER, server, "lastLogin");
+				if (!StringUtils.isEmpty(date)) {
+					lastLogin = dateFormat.parse(date);
+				}
+				String url = config.getString(SERVER, server, "url");
+				String account = config.getString(SERVER, server, "account");
+				char[] password;
+				String pw = config.getString(SERVER, server, "password");
+				if (StringUtils.isEmpty(pw)) {
+					password = new char[0];
+				} else {
+					password = new String(Base64.decode(pw)).toCharArray();
+				}
+				GitblitRegistration reg = new GitblitRegistration(server, url, account, password) {
+					private static final long serialVersionUID = 1L;
+
+					protected void cacheFeeds() {
+						writeFeedCache(this);
+					}
+				};
+				String[] feeds = config.getStringList(SERVER, server, FEED);
+				if (feeds != null) {
+					// deserialize the field definitions
+					for (String definition : feeds) {
+						FeedModel feed = new FeedModel(definition);
+						reg.feeds.add(feed);
+					}
+				}
+				reg.lastLogin = lastLogin;
+				loadFeedCache(reg);
+				registrations.put(reg.name, reg);
+			}
+		} catch (Throwable t) {
+			Utils.showException(GitblitManager.this, t);
+		}
+	}
+
+	@Override
+	public boolean saveRegistration(String name, GitblitRegistration reg) {
+		try {
+			StoredConfig config = getConfig();
+			if (!StringUtils.isEmpty(name) && !name.equals(reg.name)) {
+				// delete old registration
+				registrations.remove(name);
+				config.unsetSection(SERVER, name);
+			}
+
+			// update registration
+			config.setString(SERVER, reg.name, "url", reg.url);
+			config.setString(SERVER, reg.name, "account", reg.account);
+			if (reg.savePassword) {
+				config.setString(SERVER, reg.name, "password",
+						Base64.encodeBytes(new String(reg.password).getBytes("UTF-8")));
+			} else {
+				config.setString(SERVER, reg.name, "password", "");
+			}
+			if (reg.lastLogin != null) {
+				config.setString(SERVER, reg.name, "lastLogin", dateFormat.format(reg.lastLogin));
+			}
+			// serialize the feed definitions
+			List<String> definitions = new ArrayList<String>();
+			for (FeedModel feed : reg.feeds) {
+				definitions.add(feed.toString());
+			}
+			if (definitions.size() > 0) {
+				config.setStringList(SERVER, reg.name, FEED, definitions);
+			}
+			config.save();
+			return true;
+		} catch (Throwable t) {
+			Utils.showException(GitblitManager.this, t);
+		}
+		return false;
+	}
+
+	@Override
+	public boolean deleteRegistrations(List<GitblitRegistration> list) {
+		boolean success = false;
+		try {
+			StoredConfig config = getConfig();
+			for (GitblitRegistration reg : list) {
+				config.unsetSection(SERVER, reg.name);
+				registrations.remove(reg.name);
+			}
+			config.save();
+			success = true;
+		} catch (Throwable t) {
+			Utils.showException(GitblitManager.this, t);
+		}
+		return success;
+	}
+
+	private StoredConfig getConfig() throws IOException, ConfigInvalidException {
+		FileBasedConfig config = new FileBasedConfig(configFile, FS.detect());
+		config.load();
+		return config;
+	}
+
+	private void loadFeedCache(GitblitRegistration reg) {
+		File feedCache = new File(configFile.getParentFile(), StringUtils.getSHA1(reg.toString())
+				+ ".cache");
+		if (!feedCache.exists()) {
+			// no cache for this registration
+			return;
+		}
+		try {
+			BufferedReader reader = new BufferedReader(new FileReader(feedCache));
+			Map<String, Date> cache = new HashMap<String, Date>();
+			SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
+			String line = null;
+			while ((line = reader.readLine()) != null) {
+				String[] kvp = line.split("=");
+				cache.put(kvp[0], df.parse(kvp[1]));
+			}
+			reader.close();
+			for (FeedModel feed : reg.feeds) {
+				String name = feed.toString();
+				if (cache.containsKey(name)) {
+					feed.currentRefreshDate = cache.get(name);
+				}
+			}
+		} catch (Exception e) {
+			Utils.showException(GitblitManager.this, e);
+		}
+	}
+
+	private void writeFeedCache(GitblitRegistration reg) {
+		try {
+			File feedCache = new File(configFile.getParentFile(), StringUtils.getSHA1(reg
+					.toString()) + ".cache");
+			FileWriter writer = new FileWriter(feedCache);
+			for (FeedModel feed : reg.feeds) {
+				writer.append(MessageFormat.format("{0}={1,date,yyyy-MM-dd'T'HH:mm:ss}\n",
+						feed.toString(), feed.currentRefreshDate));
+			}
+			writer.close();
+		} catch (Exception e) {
+			Utils.showException(GitblitManager.this, e);
+		}
+	}
+
+	public static void main(String[] args) {
+		EventQueue.invokeLater(new Runnable() {
+			public void run() {
+				try {
+					UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
+				} catch (Exception e) {
+				}
+				GitblitManager frame = new GitblitManager();
+				frame.initialize();
+				frame.setVisible(true);
+			}
+		});
+	}
+}
diff --git a/src/main/java/com/gitblit/client/GitblitManagerLauncher.java b/src/main/java/com/gitblit/client/GitblitManagerLauncher.java
new file mode 100644
index 0000000..d0cc839
--- /dev/null
+++ b/src/main/java/com/gitblit/client/GitblitManagerLauncher.java
@@ -0,0 +1,164 @@
+/*
+ * 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.client;
+
+import java.awt.Color;
+import java.awt.EventQueue;
+import java.awt.FontMetrics;
+import java.awt.Graphics2D;
+import java.awt.SplashScreen;
+import java.io.File;
+import java.io.FileFilter;
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import com.gitblit.Constants;
+
+/**
+ * Downloads dependencies and launches Gitblit Manager.
+ * 
+ * @author James Moger
+ * 
+ */
+public class GitblitManagerLauncher {
+
+	public static final boolean DEBUG = false;
+
+	/**
+	 * Parameters of the method to add an URL to the System classes.
+	 */
+	private static final Class<?>[] PARAMETERS = new Class[] { URL.class };
+
+	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);
+		Collections.reverse(jars);
+		for (File jar : jars) {
+			try {
+				updateSplash(splash, Translation.get("gb.loading") + " " + jar.getName() + "...");
+				addJarFile(jar);
+			} catch (IOException e) {
+
+			}
+		}
+		
+		updateSplash(splash, Translation.get("gb.starting") + " Gitblit Manager...");
+		GitblitManager.main(args);
+	}
+
+	private static void updateSplash(final SplashScreen splash, final String string) {
+		if (splash == null) {
+			return;
+		}
+		try {
+			EventQueue.invokeAndWait(new Runnable() {
+				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();
+						int x = 5;
+						int y = 115;
+						int w = 320 - 2 * x;
+						g.fillRect(x, y - h, w, h);
+						g.setColor(Color.lightGray);
+						g.drawRect(x, y - h, w, h);
+						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);
+						g.drawString(ver, 320 - vw - 5, 34);
+						g.dispose();
+						splash.update();
+					}
+				}
+			});
+		} catch (Throwable t) {
+			t.printStackTrace();
+		}
+	}
+	
+	public static List<File> findJars(File folder) {
+		List<File> jars = new ArrayList<File>();
+		if (folder.exists()) {
+			File[] libs = folder.listFiles(new FileFilter() {
+				@Override
+				public boolean accept(File file) {
+					return !file.isDirectory() && file.getName().toLowerCase().endsWith(".jar");
+				}
+			});
+			if (libs != null && libs.length > 0) {
+				jars.addAll(Arrays.asList(libs));
+				if (DEBUG) {
+					for (File jar : jars) {
+						System.out.println("found " + jar);
+					}
+				}
+			}
+		}
+
+		return jars;
+	}
+
+	/**
+	 * Adds a file to the classpath
+	 * 
+	 * @param f
+	 *            the file to be added
+	 * @throws IOException
+	 */
+	public static void addJarFile(File f) throws IOException {
+		if (f.getName().indexOf("-sources") > -1 || f.getName().indexOf("-javadoc") > -1) {
+			// don't add source or javadoc jars to runtime classpath
+			return;
+		}
+		URL u = f.toURI().toURL();
+		if (DEBUG) {
+			System.out.println("load=" + u.toExternalForm());
+		}
+		URLClassLoader sysloader = (URLClassLoader) ClassLoader.getSystemClassLoader();
+		Class<?> sysclass = URLClassLoader.class;
+		try {
+			Method method = sysclass.getDeclaredMethod("addURL", PARAMETERS);
+			method.setAccessible(true);
+			method.invoke(sysloader, new Object[] { u });
+		} catch (Throwable t) {
+			throw new IOException(MessageFormat.format(
+					"Error, could not add {0} to system classloader", f.getPath()), t);
+		}
+	}
+
+}
diff --git a/src/com/gitblit/client/GitblitPanel.java b/src/main/java/com/gitblit/client/GitblitPanel.java
similarity index 100%
rename from src/com/gitblit/client/GitblitPanel.java
rename to src/main/java/com/gitblit/client/GitblitPanel.java
diff --git a/src/com/gitblit/client/GitblitRegistration.java b/src/main/java/com/gitblit/client/GitblitRegistration.java
similarity index 100%
rename from src/com/gitblit/client/GitblitRegistration.java
rename to src/main/java/com/gitblit/client/GitblitRegistration.java
diff --git a/src/com/gitblit/client/GitblitWorker.java b/src/main/java/com/gitblit/client/GitblitWorker.java
similarity index 100%
rename from src/com/gitblit/client/GitblitWorker.java
rename to src/main/java/com/gitblit/client/GitblitWorker.java
diff --git a/src/com/gitblit/client/HeaderPanel.java b/src/main/java/com/gitblit/client/HeaderPanel.java
similarity index 100%
rename from src/com/gitblit/client/HeaderPanel.java
rename to src/main/java/com/gitblit/client/HeaderPanel.java
diff --git a/src/com/gitblit/client/IndicatorsRenderer.java b/src/main/java/com/gitblit/client/IndicatorsRenderer.java
similarity index 100%
rename from src/com/gitblit/client/IndicatorsRenderer.java
rename to src/main/java/com/gitblit/client/IndicatorsRenderer.java
diff --git a/src/com/gitblit/client/JPalette.java b/src/main/java/com/gitblit/client/JPalette.java
similarity index 100%
rename from src/com/gitblit/client/JPalette.java
rename to src/main/java/com/gitblit/client/JPalette.java
diff --git a/src/com/gitblit/client/MessageRenderer.java b/src/main/java/com/gitblit/client/MessageRenderer.java
similarity index 100%
rename from src/com/gitblit/client/MessageRenderer.java
rename to src/main/java/com/gitblit/client/MessageRenderer.java
diff --git a/src/com/gitblit/client/NameRenderer.java b/src/main/java/com/gitblit/client/NameRenderer.java
similarity index 100%
rename from src/com/gitblit/client/NameRenderer.java
rename to src/main/java/com/gitblit/client/NameRenderer.java
diff --git a/src/com/gitblit/client/PropertiesTableModel.java b/src/main/java/com/gitblit/client/PropertiesTableModel.java
similarity index 100%
rename from src/com/gitblit/client/PropertiesTableModel.java
rename to src/main/java/com/gitblit/client/PropertiesTableModel.java
diff --git a/src/com/gitblit/client/RegistrantPermissionsPanel.java b/src/main/java/com/gitblit/client/RegistrantPermissionsPanel.java
similarity index 100%
rename from src/com/gitblit/client/RegistrantPermissionsPanel.java
rename to src/main/java/com/gitblit/client/RegistrantPermissionsPanel.java
diff --git a/src/com/gitblit/client/RegistrantPermissionsTableModel.java b/src/main/java/com/gitblit/client/RegistrantPermissionsTableModel.java
similarity index 100%
rename from src/com/gitblit/client/RegistrantPermissionsTableModel.java
rename to src/main/java/com/gitblit/client/RegistrantPermissionsTableModel.java
diff --git a/src/com/gitblit/client/RegistrationsDialog.java b/src/main/java/com/gitblit/client/RegistrationsDialog.java
similarity index 100%
rename from src/com/gitblit/client/RegistrationsDialog.java
rename to src/main/java/com/gitblit/client/RegistrationsDialog.java
diff --git a/src/com/gitblit/client/RegistrationsTableModel.java b/src/main/java/com/gitblit/client/RegistrationsTableModel.java
similarity index 100%
rename from src/com/gitblit/client/RegistrationsTableModel.java
rename to src/main/java/com/gitblit/client/RegistrationsTableModel.java
diff --git a/src/com/gitblit/client/RepositoriesPanel.java b/src/main/java/com/gitblit/client/RepositoriesPanel.java
similarity index 100%
rename from src/com/gitblit/client/RepositoriesPanel.java
rename to src/main/java/com/gitblit/client/RepositoriesPanel.java
diff --git a/src/com/gitblit/client/RepositoriesTableModel.java b/src/main/java/com/gitblit/client/RepositoriesTableModel.java
similarity index 100%
rename from src/com/gitblit/client/RepositoriesTableModel.java
rename to src/main/java/com/gitblit/client/RepositoriesTableModel.java
diff --git a/src/com/gitblit/client/SearchDialog.java b/src/main/java/com/gitblit/client/SearchDialog.java
similarity index 100%
rename from src/com/gitblit/client/SearchDialog.java
rename to src/main/java/com/gitblit/client/SearchDialog.java
diff --git a/src/com/gitblit/client/SettingCellRenderer.java b/src/main/java/com/gitblit/client/SettingCellRenderer.java
similarity index 100%
rename from src/com/gitblit/client/SettingCellRenderer.java
rename to src/main/java/com/gitblit/client/SettingCellRenderer.java
diff --git a/src/com/gitblit/client/SettingPanel.java b/src/main/java/com/gitblit/client/SettingPanel.java
similarity index 100%
rename from src/com/gitblit/client/SettingPanel.java
rename to src/main/java/com/gitblit/client/SettingPanel.java
diff --git a/src/com/gitblit/client/SettingsPanel.java b/src/main/java/com/gitblit/client/SettingsPanel.java
similarity index 100%
rename from src/com/gitblit/client/SettingsPanel.java
rename to src/main/java/com/gitblit/client/SettingsPanel.java
diff --git a/src/com/gitblit/client/SettingsTableModel.java b/src/main/java/com/gitblit/client/SettingsTableModel.java
similarity index 100%
rename from src/com/gitblit/client/SettingsTableModel.java
rename to src/main/java/com/gitblit/client/SettingsTableModel.java
diff --git a/src/com/gitblit/client/StatusPanel.java b/src/main/java/com/gitblit/client/StatusPanel.java
similarity index 100%
rename from src/com/gitblit/client/StatusPanel.java
rename to src/main/java/com/gitblit/client/StatusPanel.java
diff --git a/src/com/gitblit/client/SubscribedRepositoryRenderer.java b/src/main/java/com/gitblit/client/SubscribedRepositoryRenderer.java
similarity index 100%
rename from src/com/gitblit/client/SubscribedRepositoryRenderer.java
rename to src/main/java/com/gitblit/client/SubscribedRepositoryRenderer.java
diff --git a/src/com/gitblit/client/SubscriptionsDialog.java b/src/main/java/com/gitblit/client/SubscriptionsDialog.java
similarity index 100%
rename from src/com/gitblit/client/SubscriptionsDialog.java
rename to src/main/java/com/gitblit/client/SubscriptionsDialog.java
diff --git a/src/com/gitblit/client/TeamsPanel.java b/src/main/java/com/gitblit/client/TeamsPanel.java
similarity index 100%
rename from src/com/gitblit/client/TeamsPanel.java
rename to src/main/java/com/gitblit/client/TeamsPanel.java
diff --git a/src/com/gitblit/client/TeamsTableModel.java b/src/main/java/com/gitblit/client/TeamsTableModel.java
similarity index 100%
rename from src/com/gitblit/client/TeamsTableModel.java
rename to src/main/java/com/gitblit/client/TeamsTableModel.java
diff --git a/src/main/java/com/gitblit/client/Translation.java b/src/main/java/com/gitblit/client/Translation.java
new file mode 100644
index 0000000..9f643db
--- /dev/null
+++ b/src/main/java/com/gitblit/client/Translation.java
@@ -0,0 +1,59 @@
+/*
+ * 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.client;
+
+import java.util.MissingResourceException;
+import java.util.ResourceBundle;
+
+import com.gitblit.utils.TimeUtils;
+
+/**
+ * Loads the Gitblit language resource file.
+ * 
+ * @author James Moger
+ * 
+ */
+public class Translation {
+
+	private final static ResourceBundle translation;
+	
+	private final static TimeUtils timeUtils;
+
+	static {
+		ResourceBundle bundle;
+		try {
+			// development location
+			bundle = ResourceBundle.getBundle("com/gitblit/wicket/GitBlitWebApp");
+		} catch (MissingResourceException e) {
+			// runtime location
+			bundle = ResourceBundle.getBundle("GitBlitWebApp");
+		}
+		translation = bundle;
+		
+		timeUtils = new TimeUtils(translation, null);
+	}
+
+	public static String get(String key) {
+		if (translation.containsKey(key)) {
+			return translation.getString(key).trim();
+		}
+		return key;
+	}
+	
+	public static TimeUtils getTimeUtils() {
+		return timeUtils;
+	}
+}
diff --git a/src/com/gitblit/client/UsersPanel.java b/src/main/java/com/gitblit/client/UsersPanel.java
similarity index 100%
rename from src/com/gitblit/client/UsersPanel.java
rename to src/main/java/com/gitblit/client/UsersPanel.java
diff --git a/src/com/gitblit/client/UsersTableModel.java b/src/main/java/com/gitblit/client/UsersTableModel.java
similarity index 100%
rename from src/com/gitblit/client/UsersTableModel.java
rename to src/main/java/com/gitblit/client/UsersTableModel.java
diff --git a/src/com/gitblit/client/Utils.java b/src/main/java/com/gitblit/client/Utils.java
similarity index 100%
rename from src/com/gitblit/client/Utils.java
rename to src/main/java/com/gitblit/client/Utils.java
diff --git a/src/com/gitblit/client/splash.png b/src/main/java/com/gitblit/client/splash.png
similarity index 100%
rename from src/com/gitblit/client/splash.png
rename to src/main/java/com/gitblit/client/splash.png
Binary files differ
diff --git a/src/com/gitblit/fanout/FanoutClient.java b/src/main/java/com/gitblit/fanout/FanoutClient.java
similarity index 100%
rename from src/com/gitblit/fanout/FanoutClient.java
rename to src/main/java/com/gitblit/fanout/FanoutClient.java
diff --git a/src/com/gitblit/fanout/FanoutConstants.java b/src/main/java/com/gitblit/fanout/FanoutConstants.java
similarity index 100%
rename from src/com/gitblit/fanout/FanoutConstants.java
rename to src/main/java/com/gitblit/fanout/FanoutConstants.java
diff --git a/src/com/gitblit/fanout/FanoutNioService.java b/src/main/java/com/gitblit/fanout/FanoutNioService.java
similarity index 100%
rename from src/com/gitblit/fanout/FanoutNioService.java
rename to src/main/java/com/gitblit/fanout/FanoutNioService.java
diff --git a/src/com/gitblit/fanout/FanoutService.java b/src/main/java/com/gitblit/fanout/FanoutService.java
similarity index 100%
rename from src/com/gitblit/fanout/FanoutService.java
rename to src/main/java/com/gitblit/fanout/FanoutService.java
diff --git a/src/com/gitblit/fanout/FanoutServiceConnection.java b/src/main/java/com/gitblit/fanout/FanoutServiceConnection.java
similarity index 100%
rename from src/com/gitblit/fanout/FanoutServiceConnection.java
rename to src/main/java/com/gitblit/fanout/FanoutServiceConnection.java
diff --git a/src/com/gitblit/fanout/FanoutSocketService.java b/src/main/java/com/gitblit/fanout/FanoutSocketService.java
similarity index 100%
rename from src/com/gitblit/fanout/FanoutSocketService.java
rename to src/main/java/com/gitblit/fanout/FanoutSocketService.java
diff --git a/src/com/gitblit/fanout/FanoutStats.java b/src/main/java/com/gitblit/fanout/FanoutStats.java
similarity index 100%
rename from src/com/gitblit/fanout/FanoutStats.java
rename to src/main/java/com/gitblit/fanout/FanoutStats.java
diff --git a/src/main/java/com/gitblit/git/GitDaemon.java b/src/main/java/com/gitblit/git/GitDaemon.java
new file mode 100644
index 0000000..b760fbc
--- /dev/null
+++ b/src/main/java/com/gitblit/git/GitDaemon.java
@@ -0,0 +1,350 @@
+/*
+ * Copyright (C) 2013 gitblit.com
+ * Copyright (C) 2008-2009, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.gitblit.git;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InterruptedIOException;
+import java.io.OutputStream;
+import java.net.InetSocketAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.SocketAddress;
+import java.text.MessageFormat;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.eclipse.jgit.errors.RepositoryNotFoundException;
+import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.transport.ReceivePack;
+import org.eclipse.jgit.transport.ServiceMayNotContinueException;
+import org.eclipse.jgit.transport.UploadPack;
+import org.eclipse.jgit.transport.resolver.ReceivePackFactory;
+import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException;
+import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException;
+import org.eclipse.jgit.transport.resolver.UploadPackFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.gitblit.utils.StringUtils;
+
+/**
+ * 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 {
+
+	private final Logger logger = LoggerFactory.getLogger(GitDaemon.class);
+
+	/** 9418: IANA assigned port number for Git. */
+	public static final int DEFAULT_PORT = 9418;
+
+	private static final int BACKLOG = 5;
+
+	private InetSocketAddress myAddress;
+
+	private final GitDaemonService[] services;
+
+	private final ThreadGroup processors;
+
+	private AtomicBoolean run;
+
+	private ServerSocket acceptSocket;
+
+	private Thread acceptThread;
+
+	private int timeout;
+
+	private RepositoryResolver<GitDaemonClient> repositoryResolver;
+
+	private UploadPackFactory<GitDaemonClient> uploadPackFactory;
+
+	private ReceivePackFactory<GitDaemonClient> receivePackFactory;
+
+	/** Configure a daemon to listen on any available network port. */
+	public GitDaemon() {
+		this(null);
+	}
+
+	/**
+	 * Construct the Gitblit Git daemon.
+	 * 
+	 * @param bindInterface
+	 *            the ip address of the interface to bind
+	 * @param port
+	 *            the port to serve on
+	 * @param folder
+	 *            the folder to serve from
+	 */
+	public GitDaemon(String bindInterface, int port, File folder) {
+		this(StringUtils.isEmpty(bindInterface) ? new InetSocketAddress(port)
+				: new InetSocketAddress(bindInterface, port));
+
+		// 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.
+	 */
+	public GitDaemon(final InetSocketAddress addr) {
+		myAddress = addr;
+		processors = new ThreadGroup("Git-Daemon");
+
+		run = new AtomicBoolean(false);
+		repositoryResolver = null;
+		uploadPackFactory = new GitblitUploadPackFactory<GitDaemonClient>();
+		receivePackFactory = new GitblitReceivePackFactory<GitDaemonClient>();
+
+		services = new GitDaemonService[] { new GitDaemonService("upload-pack", "uploadpack") {
+					{
+						setEnabled(true);
+						setOverridable(false);
+					}
+
+					@Override
+					protected void execute(final GitDaemonClient dc, final Repository db)
+							throws IOException, ServiceNotEnabledException,
+							ServiceNotAuthorizedException {
+						UploadPack up = uploadPackFactory.create(dc, db);
+						InputStream in = dc.getInputStream();
+						OutputStream out = dc.getOutputStream();
+						up.upload(in, out, null);
+					}
+				}, new GitDaemonService("receive-pack", "receivepack") {
+					{
+						setEnabled(true);
+						setOverridable(false);
+					}
+
+					@Override
+					protected void execute(final GitDaemonClient dc, final Repository db)
+							throws IOException, ServiceNotEnabledException,
+							ServiceNotAuthorizedException {
+						ReceivePack rp = receivePackFactory.create(dc, db);
+						InputStream in = dc.getInputStream();
+						OutputStream out = dc.getOutputStream();
+						rp.receive(in, out, null);
+					}
+				} };
+	}
+	
+	public int getPort() {
+		return myAddress.getPort();
+	}
+	
+	public String formatUrl(String servername, String repository) {
+		if (getPort() == 9418) {
+			// standard port
+			return MessageFormat.format("git://{0}/{1}", servername, repository);
+		} else {
+			// non-standard port
+			return MessageFormat.format("git://{0}:{1,number,0}/{2}", servername, getPort(), repository);
+		}
+	}
+
+	/** @return timeout (in seconds) before aborting an IO operation. */
+	public int getTimeout() {
+		return timeout;
+	}
+
+	/**
+	 * 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
+	 *            connected client.
+	 */
+	public void setTimeout(final int seconds) {
+		timeout = seconds;
+	}
+
+	/**
+	 * Start this daemon on a background thread.
+	 * 
+	 * @throws IOException
+	 *             the server socket could not be opened.
+	 * @throws IllegalStateException
+	 *             the daemon is already running.
+	 */
+	public synchronized void start() throws IOException {
+		if (acceptThread != null)
+			throw new IllegalStateException(JGitText.get().daemonAlreadyRunning);
+
+		final ServerSocket listenSock = new ServerSocket(myAddress != null ? myAddress.getPort()
+				: 0, BACKLOG, myAddress != null ? myAddress.getAddress() : null);
+		myAddress = (InetSocketAddress) listenSock.getLocalSocketAddress();
+
+		run.set(true);
+		acceptSocket = listenSock;
+		acceptThread = new Thread(processors, "Git-Daemon-Accept") {
+			public void run() {
+				while (isRunning()) {
+					try {
+						startClient(listenSock.accept());
+					} catch (InterruptedIOException e) {
+						// Test again to see if we should keep accepting.
+					} catch (IOException e) {
+						break;
+					}
+				}
+
+				try {
+					listenSock.close();
+				} catch (IOException err) {
+					//
+				} finally {
+					acceptSocket = null;
+				}
+
+			}
+		};
+		acceptThread.start();
+		
+		logger.info(MessageFormat.format("Git Daemon is listening on {0}:{1,number,0}", myAddress.getAddress().getHostAddress(), myAddress.getPort()));
+	}
+
+	/** @return true if this daemon is receiving connections. */
+	public boolean isRunning() {
+		return run.get();
+	}
+
+	/** Stop this daemon. */
+	public synchronized void stop() {
+		if (isRunning() && acceptThread != null) {
+			run.set(false);
+			logger.info("Git Daemon stopping...");
+			try {
+				// close the accept socket
+				// this throws a SocketException in the accept thread
+				acceptSocket.close();
+			} catch (IOException e1) {
+			}
+			try {
+				// join the accept thread
+				acceptThread.join();
+				logger.info("Git Daemon stopped.");
+			} catch (InterruptedException e) {
+				logger.error("Accept thread join interrupted", e);
+			} finally {
+				acceptThread = null;
+			}
+		}
+	}
+
+	private void startClient(final Socket s) {
+		final GitDaemonClient dc = new GitDaemonClient(this);
+
+		final SocketAddress peer = s.getRemoteSocketAddress();
+		if (peer instanceof InetSocketAddress)
+			dc.setRemoteAddress(((InetSocketAddress) peer).getAddress());
+
+		new Thread(processors, "Git-Daemon-Client " + peer.toString()) {
+			public void run() {
+				try {
+					dc.execute(s);
+				} catch (ServiceNotEnabledException e) {
+					// Ignored. Client cannot use this repository.
+				} catch (ServiceNotAuthorizedException e) {
+					// Ignored. Client cannot use this repository.
+				} catch (IOException e) {
+					// Ignore unexpected IO exceptions from clients
+				} finally {
+					try {
+						s.getInputStream().close();
+					} catch (IOException e) {
+						// Ignore close exceptions
+					}
+					try {
+						s.getOutputStream().close();
+					} catch (IOException e) {
+						// Ignore close exceptions
+					}
+				}
+			}
+		}.start();
+	}
+
+	synchronized GitDaemonService matchService(final String cmd) {
+		for (final GitDaemonService d : services) {
+			if (d.handles(cmd))
+				return d;
+		}
+		return null;
+	}
+
+	Repository openRepository(GitDaemonClient client, String name)
+			throws ServiceMayNotContinueException {
+		// Assume any attempt to use \ was by a Windows client
+		// and correct to the more typical / used in Git URIs.
+		//
+		name = name.replace('\\', '/');
+
+		// git://thishost/path should always be name="/path" here
+		//
+		if (!name.startsWith("/")) //$NON-NLS-1$
+			return null;
+
+		try {
+			return repositoryResolver.open(client, name.substring(1));
+		} catch (RepositoryNotFoundException e) {
+			// null signals it "wasn't found", which is all that is suitable
+			// for the remote client to know.
+			return null;
+		} catch (ServiceNotEnabledException e) {
+			// null signals it "wasn't found", which is all that is suitable
+			// for the remote client to know.
+			return null;
+		}
+	}
+}
diff --git a/src/main/java/com/gitblit/git/GitDaemonClient.java b/src/main/java/com/gitblit/git/GitDaemonClient.java
new file mode 100644
index 0000000..e7455e0
--- /dev/null
+++ b/src/main/java/com/gitblit/git/GitDaemonClient.java
@@ -0,0 +1,131 @@
+package com.gitblit.git;
+
+/*
+ * Copyright (C) 2008-2009, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.net.Socket;
+
+import org.eclipse.jgit.transport.Daemon;
+import org.eclipse.jgit.transport.PacketLineIn;
+import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException;
+import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException;
+import org.eclipse.jgit.util.io.SafeBufferedOutputStream;
+
+/** Active network client of {@link Daemon}. */
+public class GitDaemonClient {
+	private final GitDaemon daemon;
+
+	private InetAddress peer;
+
+	private InputStream rawIn;
+
+	private OutputStream rawOut;
+	
+	private String repositoryName;
+
+	GitDaemonClient(final GitDaemon d) {
+		daemon = d;
+	}
+
+	void setRemoteAddress(final InetAddress ia) {
+		peer = ia;
+	}
+
+	/** @return the daemon which spawned this client. */
+	public GitDaemon getDaemon() {
+		return daemon;
+	}
+
+	/** @return Internet address of the remote client. */
+	public InetAddress getRemoteAddress() {
+		return peer;
+	}
+
+	/** @return input stream to read from the connected client. */
+	public InputStream getInputStream() {
+		return rawIn;
+	}
+
+	/** @return output stream to send data to the connected client. */
+	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;
+	}
+
+	void execute(final Socket sock) throws IOException,
+			ServiceNotEnabledException, ServiceNotAuthorizedException {
+		rawIn = new BufferedInputStream(sock.getInputStream());
+		rawOut = new SafeBufferedOutputStream(sock.getOutputStream());
+
+		if (0 < daemon.getTimeout())
+			sock.setSoTimeout(daemon.getTimeout() * 1000);
+		String cmd = new PacketLineIn(rawIn).readStringRaw();
+		final int nul = cmd.indexOf('\0');
+		if (nul >= 0) {
+			// Newer clients hide a "host" header behind this byte.
+			// Currently we don't use it for anything, so we ignore
+			// this portion of the command.
+			//
+			cmd = cmd.substring(0, nul);
+		}
+
+		final GitDaemonService srv = getDaemon().matchService(cmd);
+		if (srv == null)
+			return;
+		sock.setSoTimeout(0);
+		srv.execute(this, cmd);
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/git/GitDaemonService.java b/src/main/java/com/gitblit/git/GitDaemonService.java
new file mode 100644
index 0000000..03c4e1c
--- /dev/null
+++ b/src/main/java/com/gitblit/git/GitDaemonService.java
@@ -0,0 +1,165 @@
+package com.gitblit.git;
+
+/*
+ * Copyright (C) 2008-2009, Google Inc.
+ * Copyright (C) 2009, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+import java.io.IOException;
+
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.Config.SectionParser;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.transport.Daemon;
+import org.eclipse.jgit.transport.PacketLineOut;
+import org.eclipse.jgit.transport.ServiceMayNotContinueException;
+import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException;
+import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException;
+
+/** A service exposed by {@link Daemon} over anonymous <code>git://</code>. */
+public abstract class GitDaemonService {
+	private final String command;
+
+	private final SectionParser<ServiceConfig> configKey;
+
+	private boolean enabled;
+
+	private boolean overridable;
+
+	GitDaemonService(final String cmdName, final String cfgName) {
+		command = cmdName.startsWith("git-") ? cmdName : "git-" + cmdName; //$NON-NLS-1$ //$NON-NLS-2$
+		configKey = new SectionParser<ServiceConfig>() {
+			public ServiceConfig parse(final Config cfg) {
+				return new ServiceConfig(GitDaemonService.this, cfg, cfgName);
+			}
+		};
+		overridable = true;
+	}
+
+	private static class ServiceConfig {
+		final boolean enabled;
+
+		ServiceConfig(final GitDaemonService service, final Config cfg,
+				final String name) {
+			enabled = cfg.getBoolean("daemon", name, service.isEnabled()); //$NON-NLS-1$
+		}
+	}
+
+	/** @return is this service enabled for invocation? */
+	public boolean isEnabled() {
+		return enabled;
+	}
+
+	/**
+	 * @param on
+	 *            true to allow this service to be used; false to deny it.
+	 */
+	public void setEnabled(final boolean on) {
+		enabled = on;
+	}
+
+	/** @return can this service be configured in the repository config file? */
+	public boolean isOverridable() {
+		return overridable;
+	}
+
+	/**
+	 * @param on
+	 *            true to permit repositories to override this service's enabled
+	 *            state with the <code>daemon.servicename</code> config setting.
+	 */
+	public void setOverridable(final boolean on) {
+		overridable = on;
+	}
+
+	/** @return name of the command requested by clients. */
+	public String getCommandName() {
+		return command;
+	}
+
+	/**
+	 * Determine if this service can handle the requested command.
+	 *
+	 * @param commandLine
+	 *            input line from the client.
+	 * @return true if this command can accept the given command line.
+	 */
+	public boolean handles(final String commandLine) {
+		return command.length() + 1 < commandLine.length()
+				&& commandLine.charAt(command.length()) == ' '
+				&& commandLine.startsWith(command);
+	}
+
+	void execute(final GitDaemonClient client, final String commandLine)
+			throws IOException, ServiceNotEnabledException,
+			ServiceNotAuthorizedException {
+		final String name = commandLine.substring(command.length() + 1);
+		Repository db;
+		try {
+			db = client.getDaemon().openRepository(client, name);
+		} catch (ServiceMayNotContinueException e) {
+			// An error when opening the repo means the client is expecting a ref
+			// advertisement, so use that style of error.
+			PacketLineOut pktOut = new PacketLineOut(client.getOutputStream());
+			pktOut.writeString("ERR " + e.getMessage() + "\n"); //$NON-NLS-1$ //$NON-NLS-2$
+			db = null;
+		}
+		if (db == null)
+			return;
+		try {
+			if (isEnabledFor(db))
+				execute(client, db);
+		} finally {
+			db.close();
+		}
+	}
+
+	private boolean isEnabledFor(final Repository db) {
+		if (isOverridable())
+			return db.getConfig().get(configKey).enabled;
+		return isEnabled();
+	}
+
+	abstract void execute(GitDaemonClient client, Repository db)
+			throws IOException, ServiceNotEnabledException,
+			ServiceNotAuthorizedException;
+}
diff --git a/src/main/java/com/gitblit/git/GitServlet.java b/src/main/java/com/gitblit/git/GitServlet.java
new file mode 100644
index 0000000..5a40106
--- /dev/null
+++ b/src/main/java/com/gitblit/git/GitServlet.java
@@ -0,0 +1,42 @@
+/*
+ * 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.git;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+
+import com.gitblit.GitBlit;
+
+/**
+ * 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 {
+
+	private static final long serialVersionUID = 1L;
+
+	@Override
+	public void init(ServletConfig config) throws ServletException {
+		setRepositoryResolver(new RepositoryResolver<HttpServletRequest>(GitBlit.getRepositoriesFolder()));
+		setUploadPackFactory(new GitblitUploadPackFactory<HttpServletRequest>());
+		setReceivePackFactory(new GitblitReceivePackFactory<HttpServletRequest>());
+		super.init(config);
+	}
+}
diff --git a/src/main/java/com/gitblit/git/GitblitReceivePackFactory.java b/src/main/java/com/gitblit/git/GitblitReceivePackFactory.java
new file mode 100644
index 0000000..77a3df6
--- /dev/null
+++ b/src/main/java/com/gitblit/git/GitblitReceivePackFactory.java
@@ -0,0 +1,103 @@
+/*
+ * 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.git;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.transport.ReceivePack;
+import org.eclipse.jgit.transport.resolver.ReceivePackFactory;
+import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException;
+import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.gitblit.GitBlit;
+import com.gitblit.models.RepositoryModel;
+import com.gitblit.models.UserModel;
+import com.gitblit.utils.HttpUtils;
+
+/**
+ * The receive pack factory creates a receive pack which accepts pushes from
+ * clients.
+ * 
+ * @author James Moger
+ *
+ * @param <X> the connection type
+ */
+public class GitblitReceivePackFactory<X> implements ReceivePackFactory<X> {
+
+	protected final Logger logger = LoggerFactory.getLogger(GitblitReceivePackFactory.class);
+	
+	@Override
+	public ReceivePack create(X req, Repository db)
+			throws ServiceNotEnabledException, ServiceNotAuthorizedException {
+
+		final ReceivePack rp = new ReceivePack(db);
+		UserModel user = UserModel.ANONYMOUS;
+		String repositoryName = "";
+		String origin = "";
+		String gitblitUrl = "";
+		int timeout = 0;
+		
+		if (req instanceof HttpServletRequest) {
+			// http/https request may or may not be authenticated 
+			HttpServletRequest request = (HttpServletRequest) req;
+			repositoryName = request.getAttribute("gitblitRepositoryName").toString();
+			origin = request.getRemoteHost();
+			gitblitUrl = HttpUtils.getGitblitURL(request);
+
+			// determine pushing user
+			String username = request.getRemoteUser();
+			if (username != null && !"".equals(username)) {
+				user = GitBlit.self().getUserModel(username);
+				if (user == null) {
+					// anonymous push, create a temporary usermodel
+					user = new UserModel(username);
+				}
+			}
+		} else if (req instanceof GitDaemonClient) {
+			// git daemon request is alway anonymous
+			GitDaemonClient client = (GitDaemonClient) req;
+			repositoryName = client.getRepositoryName();
+			origin = client.getRemoteAddress().getHostAddress();
+			// set timeout from Git daemon
+			timeout = client.getDaemon().getTimeout();
+		}
+
+		// set pushing user identity for reflog
+		rp.setRefLogIdent(new PersonIdent(user.username, user.username + "@" + origin));
+		rp.setTimeout(timeout);
+		
+		// set advanced ref permissions
+		RepositoryModel repository = GitBlit.self().getRepositoryModel(repositoryName);
+		rp.setAllowCreates(user.canCreateRef(repository));
+		rp.setAllowDeletes(user.canDeleteRef(repository));
+		rp.setAllowNonFastForwards(user.canRewindRef(repository));
+
+		// setup the receive hook
+		ReceiveHook hook = new ReceiveHook();
+		hook.user = user;
+		hook.repository = repository;
+		hook.gitblitUrl = gitblitUrl;
+
+		rp.setPreReceiveHook(hook);
+		rp.setPostReceiveHook(hook);
+
+		return rp;
+	}
+}
diff --git a/src/main/java/com/gitblit/git/GitblitUploadPackFactory.java b/src/main/java/com/gitblit/git/GitblitUploadPackFactory.java
new file mode 100644
index 0000000..1756ac5
--- /dev/null
+++ b/src/main/java/com/gitblit/git/GitblitUploadPackFactory.java
@@ -0,0 +1,107 @@
+/*
+ * 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.git;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.transport.RefFilter;
+import org.eclipse.jgit.transport.UploadPack;
+import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException;
+import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException;
+import org.eclipse.jgit.transport.resolver.UploadPackFactory;
+
+import com.gitblit.GitBlit;
+import com.gitblit.models.UserModel;
+
+/**
+ * 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> {
+
+	@Override
+	public UploadPack create(X req, Repository db)
+			throws ServiceNotEnabledException, ServiceNotAuthorizedException {
+
+		UserModel user = UserModel.ANONYMOUS;
+		int timeout = 0;
+
+		if (req instanceof HttpServletRequest) {
+			// http/https request may or may not be authenticated 
+			user = GitBlit.self().authenticate((HttpServletRequest) req);
+			if (user == null) {
+				user = UserModel.ANONYMOUS;
+			}
+		} else if (req instanceof GitDaemonClient) {
+			// git daemon request is always anonymous
+			GitDaemonClient client = (GitDaemonClient) req;
+			// set timeout from Git daemon
+			timeout = client.getDaemon().getTimeout();
+		}
+
+		RefFilter refFilter = new UserRefFilter(user);
+		UploadPack up = new UploadPack(db);
+		up.setRefFilter(refFilter);
+		up.setTimeout(timeout);
+		
+		return up;
+	}
+
+	/**
+	 * Restricts advertisement of certain refs based on the permission of the
+	 * 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()) {
+				// admins can see all refs
+				return refs;
+			}
+
+			// normal users can not clone any gitblit refs
+			// JGit's RefMap is custom and does not support iterator removal :(
+			List<String> toRemove = new ArrayList<String>();
+			for (String ref : refs.keySet()) {
+				if (ref.startsWith("refs/gitblit/")) {
+					toRemove.add(ref);
+				}
+			}
+			for (String ref : toRemove) {
+				refs.remove(ref);
+			}
+			return refs;
+		}
+	}
+}
diff --git a/src/main/java/com/gitblit/git/ReceiveHook.java b/src/main/java/com/gitblit/git/ReceiveHook.java
new file mode 100644
index 0000000..d75a238
--- /dev/null
+++ b/src/main/java/com/gitblit/git/ReceiveHook.java
@@ -0,0 +1,361 @@
+/*
+ * 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.git;
+
+import groovy.lang.Binding;
+import groovy.util.GroovyScriptEngine;
+
+import java.io.File;
+import java.io.IOException;
+import java.text.MessageFormat;
+import java.util.Collection;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.transport.PostReceiveHook;
+import org.eclipse.jgit.transport.PreReceiveHook;
+import org.eclipse.jgit.transport.ReceiveCommand;
+import org.eclipse.jgit.transport.ReceiveCommand.Result;
+import org.eclipse.jgit.transport.ReceivePack;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.gitblit.Constants;
+import com.gitblit.Constants.AccessRestrictionType;
+import com.gitblit.GitBlit;
+import com.gitblit.Keys;
+import com.gitblit.client.Translation;
+import com.gitblit.models.RepositoryModel;
+import com.gitblit.models.UserModel;
+import com.gitblit.utils.ArrayUtils;
+import com.gitblit.utils.ClientLogger;
+import com.gitblit.utils.CommitCache;
+import com.gitblit.utils.JGitUtils;
+import com.gitblit.utils.RefLogUtils;
+import com.gitblit.utils.StringUtils;
+
+/**
+ * The Gitblit receive hook allows for special processing on push events.
+ * That might include rejecting writes to specific branches or executing a
+ * script.
+ * 
+ * @author James Moger
+ * 
+ */
+public class ReceiveHook implements PreReceiveHook, PostReceiveHook {
+
+	protected final Logger logger = LoggerFactory.getLogger(ReceiveHook.class);
+
+	protected UserModel user;
+	
+	protected RepositoryModel repository;
+
+	protected String gitblitUrl;
+
+	private GroovyScriptEngine gse;
+
+	private File groovyDir;
+
+	public ReceiveHook() {
+		groovyDir = GitBlit.getGroovyScriptsFolder();
+		try {
+			// set Grape root
+			File grapeRoot = GitBlit.getFileOrFolder(Keys.groovy.grapeFolder, "${baseFolder}/groovy/grape").getAbsoluteFile();
+			grapeRoot.mkdirs();
+			System.setProperty("grape.root", grapeRoot.getAbsolutePath());
+
+			gse = new GroovyScriptEngine(groovyDir.getAbsolutePath());			
+		} catch (IOException e) {
+			//throw new ServletException("Failed to instantiate Groovy Script Engine!", e);
+		}
+	}
+
+	/**
+	 * Instrumentation point where the incoming push event has been parsed,
+	 * validated, objects created BUT refs have not been updated. You might
+	 * use this to enforce a branch-write permissions model.
+	 */
+	@Override
+	public void onPreReceive(ReceivePack rp, Collection<ReceiveCommand> commands) {
+		if (repository.isFrozen) {
+			// repository is frozen/readonly
+			String reason = MessageFormat.format("Gitblit does not allow pushes to \"{0}\" because it is frozen!", repository.name);
+			logger.warn(reason);
+			for (ReceiveCommand cmd : commands) {
+				cmd.setResult(Result.REJECTED_OTHER_REASON, reason);
+			}
+			return;
+		}
+		
+		if (!repository.isBare) {
+			// repository has a working copy
+			String reason = MessageFormat.format("Gitblit does not allow pushes to \"{0}\" because it has a working copy!", repository.name);
+			logger.warn(reason);
+			for (ReceiveCommand cmd : commands) {
+				cmd.setResult(Result.REJECTED_OTHER_REASON, reason);
+			}
+			return;
+		}
+
+		if (!user.canPush(repository)) {
+			// user does not have push permissions
+			String reason = MessageFormat.format("User \"{0}\" does not have push permissions for \"{1}\"!", user.username, repository.name);
+			logger.warn(reason);
+			for (ReceiveCommand cmd : commands) {
+				cmd.setResult(Result.REJECTED_OTHER_REASON, reason);
+			}
+			return;
+		}
+
+		if (repository.accessRestriction.atLeast(AccessRestrictionType.PUSH) && repository.verifyCommitter) {
+			// enforce committer verification
+			if (StringUtils.isEmpty(user.emailAddress)) {
+				// emit warning if user does not have an email address 
+				logger.warn(MessageFormat.format("Consider setting an email address for {0} ({1}) to improve committer verification.", user.getDisplayName(), user.username));
+			}
+
+			// Optionally enforce that the committer of the left parent chain
+			// match the account being used to push the commits.
+			// 
+			// This requires all merge commits are executed with the "--no-ff"
+			// option to force a merge commit even if fast-forward is possible.
+			// This ensures that the chain of left parents has the commit
+			// identity of the merging user.
+			boolean allRejected = false;
+			for (ReceiveCommand cmd : commands) {
+				String linearParent = null;
+				try {
+					List<RevCommit> commits = JGitUtils.getRevLog(rp.getRepository(), cmd.getOldId().name(), cmd.getNewId().name());
+					for (RevCommit commit : commits) {
+						
+						if (linearParent != null) {
+		            		if (!commit.getName().equals(linearParent)) {
+		            			// ignore: commit is right-descendant of a merge
+		            			continue;
+		            		}
+		            	}
+						
+						// update expected next commit id
+						if (commit.getParentCount() == 0) {
+		                	linearParent = null;
+						} else {
+							linearParent = commit.getParents()[0].getId().getName();
+						}
+						
+						PersonIdent committer = commit.getCommitterIdent();
+						if (!user.is(committer.getName(), committer.getEmailAddress())) {
+							String reason;
+							if (StringUtils.isEmpty(user.emailAddress)) {
+								// account does not have an email address
+								reason = MessageFormat.format("{0} by {1} <{2}> was not committed by {3} ({4})", 
+										commit.getId().name(), committer.getName(), StringUtils.isEmpty(committer.getEmailAddress()) ? "?":committer.getEmailAddress(), user.getDisplayName(), user.username);
+							} else {
+								// account has an email address
+								reason = MessageFormat.format("{0} by {1} <{2}> was not committed by {3} ({4}) <{5}>", 
+										commit.getId().name(), committer.getName(), StringUtils.isEmpty(committer.getEmailAddress()) ? "?":committer.getEmailAddress(), user.getDisplayName(), user.username, user.emailAddress);
+							}
+							logger.warn(reason);
+							cmd.setResult(Result.REJECTED_OTHER_REASON, reason);
+							allRejected &= true;
+							break;
+						} else {
+							allRejected = false;
+						}
+					}
+				} catch (Exception e) {
+					logger.error("Failed to verify commits were made by pushing user", e);
+				}
+			}
+
+			if (allRejected) {
+				// all ref updates rejected, abort
+				return;
+			}
+		}
+		
+		// reset branch commit cache on REWIND and DELETE
+		for (ReceiveCommand cmd : commands) {
+			String ref = cmd.getRefName();
+			if (ref.startsWith(Constants.R_HEADS)) {
+				switch (cmd.getType()) {
+				case UPDATE_NONFASTFORWARD:
+				case DELETE:
+					CommitCache.instance().clear(repository.name, ref);
+					break;
+				default:
+					break;
+				}
+			}
+		}
+
+		Set<String> scripts = new LinkedHashSet<String>();
+		scripts.addAll(GitBlit.self().getPreReceiveScriptsInherited(repository));
+		if (!ArrayUtils.isEmpty(repository.preReceiveScripts)) {
+			scripts.addAll(repository.preReceiveScripts);
+		}
+		runGroovy(repository, user, commands, rp, scripts);
+		for (ReceiveCommand cmd : commands) {
+			if (!Result.NOT_ATTEMPTED.equals(cmd.getResult())) {
+				logger.warn(MessageFormat.format("{0} {1} because \"{2}\"", cmd.getNewId()
+						.getName(), cmd.getResult(), cmd.getMessage()));
+			}
+		}
+	}
+
+	/**
+	 * Instrumentation point where the incoming push has been applied to the
+	 * repository. This is the point where we would trigger a Jenkins build
+	 * or send an email.
+	 */
+	@Override
+	public void onPostReceive(ReceivePack rp, Collection<ReceiveCommand> commands) {
+		if (commands.size() == 0) {
+			logger.debug("skipping post-receive hooks, no refs created, updated, or removed");
+			return;
+		}
+
+		// log ref changes
+		for (ReceiveCommand cmd : commands) {
+			if (Result.OK.equals(cmd.getResult())) {
+				// add some logging for important ref changes
+				switch (cmd.getType()) {
+				case DELETE:
+					logger.info(MessageFormat.format("{0} DELETED {1} in {2} ({3})", user.username, cmd.getRefName(), repository.name, cmd.getOldId().name()));
+					break;
+				case CREATE:
+					logger.info(MessageFormat.format("{0} CREATED {1} in {2}", user.username, cmd.getRefName(), repository.name));
+					break;
+				case UPDATE:
+					logger.info(MessageFormat.format("{0} UPDATED {1} in {2} (from {3} to {4})", user.username, cmd.getRefName(), repository.name, cmd.getOldId().name(), cmd.getNewId().name()));
+					break;
+				case UPDATE_NONFASTFORWARD:
+					logger.info(MessageFormat.format("{0} UPDATED NON-FAST-FORWARD {1} in {2} (from {3} to {4})", user.username, cmd.getRefName(), repository.name, cmd.getOldId().name(), cmd.getNewId().name()));
+					break;
+				default:
+					break;
+				}
+			}
+		}
+
+		if (repository.useIncrementalPushTags) {
+			// tag each pushed branch tip
+			String emailAddress = user.emailAddress == null ? rp.getRefLogIdent().getEmailAddress() : user.emailAddress;
+			PersonIdent userIdent = new PersonIdent(user.getDisplayName(), emailAddress);
+
+			for (ReceiveCommand cmd : commands) {
+				if (!cmd.getRefName().startsWith("refs/heads/")) {
+					// only tag branch ref changes
+					continue;
+				}
+
+				if (!ReceiveCommand.Type.DELETE.equals(cmd.getType())
+						&& ReceiveCommand.Result.OK.equals(cmd.getResult())) {
+					String objectId = cmd.getNewId().getName();
+					String branch = cmd.getRefName().substring("refs/heads/".length());
+					// get translation based on the server's locale setting
+					String template = Translation.get("gb.incrementalPushTagMessage");
+					String msg = MessageFormat.format(template, branch);
+					String prefix;
+					if (StringUtils.isEmpty(repository.incrementalPushTagPrefix)) {
+						prefix = GitBlit.getString(Keys.git.defaultIncrementalPushTagPrefix, "r");
+					} else {
+						prefix = repository.incrementalPushTagPrefix;
+					}
+
+					JGitUtils.createIncrementalRevisionTag(
+							rp.getRepository(),
+							objectId,
+							userIdent,
+							prefix,
+							"0",
+							msg);
+				}
+			}				
+		}
+
+		// update push log
+		try {
+			RefLogUtils.updateRefLog(user, rp.getRepository(), commands);
+			logger.debug(MessageFormat.format("{0} push log updated", repository.name));
+		} catch (Exception e) {
+			logger.error(MessageFormat.format("Failed to update {0} pushlog", repository.name), e);
+		}
+
+		// run Groovy hook scripts 
+		Set<String> scripts = new LinkedHashSet<String>();
+		scripts.addAll(GitBlit.self().getPostReceiveScriptsInherited(repository));
+		if (!ArrayUtils.isEmpty(repository.postReceiveScripts)) {
+			scripts.addAll(repository.postReceiveScripts);
+		}
+		runGroovy(repository, user, commands, rp, scripts);
+	}
+
+	/**
+	 * Runs the specified Groovy hook scripts.
+	 * 
+	 * @param repository
+	 * @param user
+	 * @param commands
+	 * @param scripts
+	 */
+	protected void runGroovy(RepositoryModel repository, UserModel user,
+			Collection<ReceiveCommand> commands, ReceivePack rp, Set<String> scripts) {
+		if (scripts == null || scripts.size() == 0) {
+			// no Groovy scripts to execute
+			return;
+		}
+
+		Binding binding = new Binding();
+		binding.setVariable("gitblit", GitBlit.self());
+		binding.setVariable("repository", repository);
+		binding.setVariable("receivePack", rp);
+		binding.setVariable("user", user);
+		binding.setVariable("commands", commands);
+		binding.setVariable("url", gitblitUrl);
+		binding.setVariable("logger", logger);
+		binding.setVariable("clientLogger", new ClientLogger(rp));
+		for (String script : scripts) {
+			if (StringUtils.isEmpty(script)) {
+				continue;
+			}
+			// allow script to be specified without .groovy extension
+			// this is easier to read in the settings
+			File file = new File(groovyDir, script);
+			if (!file.exists() && !script.toLowerCase().endsWith(".groovy")) {
+				file = new File(groovyDir, script + ".groovy");
+				if (file.exists()) {
+					script = file.getName();
+				}
+			}
+			try {
+				Object result = gse.run(script, binding);
+				if (result instanceof Boolean) {
+					if (!((Boolean) result)) {
+						logger.error(MessageFormat.format(
+								"Groovy script {0} has failed!  Hook scripts aborted.", script));
+						break;
+					}
+				}
+			} catch (Exception e) {
+				logger.error(
+						MessageFormat.format("Failed to execute Groovy script {0}", script), e);
+			}
+		}
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/git/RepositoryResolver.java b/src/main/java/com/gitblit/git/RepositoryResolver.java
new file mode 100644
index 0000000..21a8376
--- /dev/null
+++ b/src/main/java/com/gitblit/git/RepositoryResolver.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2013 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit.git;
+
+import java.io.File;
+import java.io.IOException;
+import java.text.MessageFormat;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.eclipse.jgit.errors.RepositoryNotFoundException;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.transport.resolver.FileResolver;
+import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.gitblit.GitBlit;
+import com.gitblit.models.RepositoryModel;
+import com.gitblit.models.UserModel;
+
+/**
+ * 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);
+	}
+
+	/**
+	 * Open the repository and inject the repository name into the settings.
+	 */
+	@Override
+	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.
+		if (req instanceof HttpServletRequest) {
+			// http/https request
+			HttpServletRequest client = (HttpServletRequest) req;
+			client.setAttribute("gitblitRepositoryName", name);
+		} else if (req instanceof GitDaemonClient) {
+			// git request
+			GitDaemonClient client = (GitDaemonClient) req;
+			client.setRepositoryName(name);
+		}
+		return repo;
+	}
+	
+	/**
+	 * Check if this repository can be served by the requested client connection.
+	 */
+	@Override
+	protected boolean isExportOk(X req, String repositoryName, Repository db) throws IOException {
+		RepositoryModel model = GitBlit.self().getRepositoryModel(repositoryName);
+
+		String scheme = null;
+		UserModel user = null;
+		String origin = null;
+		
+		if (req instanceof GitDaemonClient) {
+			// git daemon request
+			// this is an anonymous/unauthenticated protocol
+			GitDaemonClient client = (GitDaemonClient) req;
+			scheme = "git";
+			origin = client.getRemoteAddress().toString();
+			user = UserModel.ANONYMOUS;
+		} else if (req instanceof HttpServletRequest) {
+			// http/https request
+			HttpServletRequest httpRequest = (HttpServletRequest) req;
+			scheme = httpRequest.getScheme(); 
+			origin = httpRequest.getRemoteAddr();
+			user = GitBlit.self().authenticate(httpRequest);
+			if (user == null) {
+				user = UserModel.ANONYMOUS;
+			}
+		}
+
+		if (user.canClone(model)) {
+			// user can access this git repo
+			logger.debug(MessageFormat.format("{0}:// access of {1} by {2} from {3} PERMITTED",
+					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));
+		return false;
+	}
+}
diff --git a/src/main/java/com/gitblit/models/Activity.java b/src/main/java/com/gitblit/models/Activity.java
new file mode 100644
index 0000000..8af86d6
--- /dev/null
+++ b/src/main/java/com/gitblit/models/Activity.java
@@ -0,0 +1,163 @@
+/*
+ * 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.models;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+
+import org.eclipse.jgit.revwalk.RevCommit;
+
+import com.gitblit.utils.StringUtils;
+import com.gitblit.utils.TimeUtils;
+
+/**
+ * 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> {
+
+	private static final long serialVersionUID = 1L;
+
+	public final Date startDate;
+
+	public final Date endDate;
+	
+	private final Set<RepositoryCommit> commits;
+
+	private final Map<String, Metric> authorMetrics;
+
+	private final Map<String, Metric> repositoryMetrics;
+
+	private final Set<String> authorExclusions;
+
+	/**
+	 * Constructor for one day of activity.
+	 * 
+	 * @param date
+	 */
+	public Activity(Date date) {
+		this(date, TimeUtils.ONEDAY - 1);
+	}
+
+	/**
+	 * Constructor for specified duration of activity from start date.
+	 * 
+	 * @param date
+	 *            the start date of the activity
+	 * @param duration
+	 *            the duration of the period in milliseconds
+	 */
+	public Activity(Date date, long duration) {
+		startDate = date;
+		endDate = new Date(date.getTime() + duration);
+		commits = new LinkedHashSet<RepositoryCommit>();
+		authorMetrics = new HashMap<String, Metric>();
+		repositoryMetrics = new HashMap<String, Metric>();
+		authorExclusions = new TreeSet<String>();
+	}
+	
+	/**
+	 * Exclude the specified authors from the metrics.
+	 * 
+	 * @param authors
+	 */
+	public void excludeAuthors(Collection<String> authors) {
+		for (String author : authors) {
+			authorExclusions.add(author.toLowerCase());
+		}
+	}
+
+	/**
+	 * Adds a commit to the activity object as long as the commit is not a
+	 * duplicate.
+	 * 
+	 * @param repository
+	 * @param branch
+	 * @param commit
+	 * @return a RepositoryCommit, if one was added. Null if this is duplicate
+	 *         commit
+	 */
+	public RepositoryCommit addCommit(String repository, String branch, RevCommit commit) {
+		RepositoryCommit commitModel = new RepositoryCommit(repository, branch, commit);
+		return addCommit(commitModel);
+	}
+
+	/**
+	 * Adds a commit to the activity object as long as the commit is not a
+	 * duplicate.
+	 * 
+	 * @param repository
+	 * @param branch
+	 * @param commit
+	 * @return a RepositoryCommit, if one was added. Null if this is duplicate
+	 *         commit
+	 */
+	public RepositoryCommit addCommit(RepositoryCommit commitModel) {
+		if (commits.add(commitModel)) {
+			String author = StringUtils.removeNewlines(commitModel.getAuthorIdent().getName());
+			String authorName = author.toLowerCase();
+			String authorEmail = StringUtils.removeNewlines(commitModel.getAuthorIdent().getEmailAddress()).toLowerCase();
+			if (!repositoryMetrics.containsKey(commitModel.repository)) {
+				repositoryMetrics.put(commitModel.repository, new Metric(commitModel.repository));
+			}
+			repositoryMetrics.get(commitModel.repository).count++;
+
+			if (!authorExclusions.contains(authorName) && !authorExclusions.contains(authorEmail)) {
+				if (!authorMetrics.containsKey(author)) {
+					authorMetrics.put(author, new Metric(author));
+				}
+				authorMetrics.get(author).count++;
+			}
+			return commitModel;
+		}
+		return null;
+	}
+
+	public int getCommitCount() {
+		return commits.size();
+	}
+	
+	public List<RepositoryCommit> getCommits() {
+		List<RepositoryCommit> list = new ArrayList<RepositoryCommit>(commits);
+		Collections.sort(list);
+		return list;
+	}
+
+	public Map<String, Metric> getAuthorMetrics() {
+		return authorMetrics;
+	}
+
+	public Map<String, Metric> getRepositoryMetrics() {
+		return repositoryMetrics;
+	}
+
+	@Override
+	public int compareTo(Activity o) {
+		// reverse chronological order
+		return o.startDate.compareTo(startDate);
+	}
+}
diff --git a/src/main/java/com/gitblit/models/AnnotatedLine.java b/src/main/java/com/gitblit/models/AnnotatedLine.java
new file mode 100644
index 0000000..439a322
--- /dev/null
+++ b/src/main/java/com/gitblit/models/AnnotatedLine.java
@@ -0,0 +1,54 @@
+/*
+ * 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.models;
+
+import java.io.Serializable;
+import java.util.Date;
+
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.revwalk.RevCommit;
+
+/**
+ * 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 {
+
+	private static final long serialVersionUID = 1L;
+
+	public final String commitId;
+	public final String author;
+	public final Date when;
+	public final int lineNumber;
+	public final String data;
+
+	public AnnotatedLine(RevCommit commit, int lineNumber, String data) {
+		if (commit == null) {
+			this.commitId = ObjectId.zeroId().getName();
+			this.author = "?";
+			this.when = new Date(0);
+		} else {
+			this.commitId = commit.getName();
+			this.author = commit.getAuthorIdent().getName();
+			this.when = commit.getAuthorIdent().getWhen();
+		}
+		this.lineNumber = lineNumber;
+		this.data = data;
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/models/DailyLogEntry.java b/src/main/java/com/gitblit/models/DailyLogEntry.java
new file mode 100644
index 0000000..41f1381
--- /dev/null
+++ b/src/main/java/com/gitblit/models/DailyLogEntry.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2013 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit.models;
+
+import java.io.Serializable;
+import java.util.Date;
+
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.transport.ReceiveCommand;
+
+/**
+ * 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 {
+
+	private static final long serialVersionUID = 1L;
+
+	public DailyLogEntry(String repository, Date date) {
+		super(repository, date, new UserModel("digest"));
+	}
+
+	public DailyLogEntry(String repository, Date date, UserModel user) {
+		super(repository, date, user);
+	}
+
+	@Override
+	public PersonIdent getCommitterIdent() {
+		if (getAuthorCount() == 1) {
+			return getCommits().get(0).getCommitterIdent();
+		}
+		
+		return super.getCommitterIdent();
+	}
+
+	@Override
+	public PersonIdent getAuthorIdent() {
+		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
+	 */
+	public void updateRef(String ref, ReceiveCommand.Type type, String oldId, String newId) {
+		// daily digests are filled from most recent to oldest 
+		String preservedNewId = getNewId(ref);
+		if (preservedNewId == null) {
+			// no preserved new id, this is newest commit
+			// for this ref
+			preservedNewId = newId;
+		}
+		refUpdates.put(ref, type);
+		refIdChanges.put(ref, oldId + "-" + preservedNewId);
+	}
+
+}
diff --git a/src/com/gitblit/models/FederationModel.java b/src/main/java/com/gitblit/models/FederationModel.java
similarity index 100%
rename from src/com/gitblit/models/FederationModel.java
rename to src/main/java/com/gitblit/models/FederationModel.java
diff --git a/src/com/gitblit/models/FederationProposal.java b/src/main/java/com/gitblit/models/FederationProposal.java
similarity index 100%
rename from src/com/gitblit/models/FederationProposal.java
rename to src/main/java/com/gitblit/models/FederationProposal.java
diff --git a/src/com/gitblit/models/FederationSet.java b/src/main/java/com/gitblit/models/FederationSet.java
similarity index 100%
rename from src/com/gitblit/models/FederationSet.java
rename to src/main/java/com/gitblit/models/FederationSet.java
diff --git a/src/com/gitblit/models/FeedEntryModel.java b/src/main/java/com/gitblit/models/FeedEntryModel.java
similarity index 100%
rename from src/com/gitblit/models/FeedEntryModel.java
rename to src/main/java/com/gitblit/models/FeedEntryModel.java
diff --git a/src/com/gitblit/models/FeedModel.java b/src/main/java/com/gitblit/models/FeedModel.java
similarity index 100%
rename from src/com/gitblit/models/FeedModel.java
rename to src/main/java/com/gitblit/models/FeedModel.java
diff --git a/src/com/gitblit/models/ForkModel.java b/src/main/java/com/gitblit/models/ForkModel.java
similarity index 100%
rename from src/com/gitblit/models/ForkModel.java
rename to src/main/java/com/gitblit/models/ForkModel.java
diff --git a/src/main/java/com/gitblit/models/GitClientApplication.java b/src/main/java/com/gitblit/models/GitClientApplication.java
new file mode 100644
index 0000000..eb47eb1
--- /dev/null
+++ b/src/main/java/com/gitblit/models/GitClientApplication.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2013 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit.models;
+
+import java.io.Serializable;
+
+import com.gitblit.Constants.AccessPermission;
+import com.gitblit.utils.ArrayUtils;
+import com.gitblit.utils.StringUtils;
+
+/**
+ * Model class to represent a git client application.
+ * 
+ * @author James Moger
+ *
+ */
+public class GitClientApplication implements Serializable {
+
+	private static final long serialVersionUID = 1L;
+
+	public String name;
+	public String title;
+	public String description;
+	public String legal;
+	public String icon;
+	public String cloneUrl;
+	public String command;
+	public String productUrl;
+	public String [] transports;
+	public String[] platforms;
+	public AccessPermission minimumPermission;
+	public boolean isActive;
+
+	public boolean allowsPlatform(String p) {
+		if (ArrayUtils.isEmpty(platforms)) {
+			// all platforms
+			return true;
+		}
+		if (StringUtils.isEmpty(p)) {
+			return false;
+		}
+		String plc = p.toLowerCase();
+		for (String platform : platforms) {
+			if (plc.contains(platform)) {
+				return true;
+			}
+		}
+		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;
+			}
+		}
+		return false;
+	}
+	
+	@Override
+	public String toString() {
+		return StringUtils.isEmpty(title) ? name : title;
+	}
+}
\ No newline at end of file
diff --git a/src/com/gitblit/models/GitNote.java b/src/main/java/com/gitblit/models/GitNote.java
similarity index 100%
rename from src/com/gitblit/models/GitNote.java
rename to src/main/java/com/gitblit/models/GitNote.java
diff --git a/src/com/gitblit/models/GravatarProfile.java b/src/main/java/com/gitblit/models/GravatarProfile.java
similarity index 100%
rename from src/com/gitblit/models/GravatarProfile.java
rename to src/main/java/com/gitblit/models/GravatarProfile.java
diff --git a/src/com/gitblit/models/IssueModel.java b/src/main/java/com/gitblit/models/IssueModel.java
similarity index 100%
rename from src/com/gitblit/models/IssueModel.java
rename to src/main/java/com/gitblit/models/IssueModel.java
diff --git a/src/com/gitblit/models/Metric.java b/src/main/java/com/gitblit/models/Metric.java
similarity index 100%
rename from src/com/gitblit/models/Metric.java
rename to src/main/java/com/gitblit/models/Metric.java
diff --git a/src/main/java/com/gitblit/models/PathModel.java b/src/main/java/com/gitblit/models/PathModel.java
new file mode 100644
index 0000000..f894978
--- /dev/null
+++ b/src/main/java/com/gitblit/models/PathModel.java
@@ -0,0 +1,127 @@
+/*
+ * 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.models;
+
+import java.io.Serializable;
+
+import org.eclipse.jgit.diff.DiffEntry.ChangeType;
+import org.eclipse.jgit.lib.FileMode;
+
+/**
+ * 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> {
+
+	private static final long serialVersionUID = 1L;
+
+	public final String name;
+	public final String path;
+	public final long size;
+	public final int mode;
+	public final String objectId;
+	public final String commitId;
+	public boolean isParentPath;
+
+	public PathModel(String name, String path, long size, int mode, String objectId, String commitId) {
+		this.name = name;
+		this.path = path;
+		this.size = size;
+		this.mode = mode;
+		this.objectId = objectId;
+		this.commitId = commitId;
+	}
+
+	public boolean isSymlink() {
+		return FileMode.SYMLINK.equals(mode);
+	}
+
+	public boolean isSubmodule() {
+		return FileMode.GITLINK.equals(mode);
+	}
+	
+	public boolean isTree() {
+		return FileMode.TREE.equals(mode);
+	}
+
+	@Override
+	public int hashCode() {
+		return commitId.hashCode() + path.hashCode();
+	}
+
+	@Override
+	public boolean equals(Object o) {
+		if (o instanceof PathModel) {
+			PathModel other = (PathModel) o;
+			return this.path.equals(other.path);
+		}
+		return super.equals(o);
+	}
+
+	@Override
+	public int compareTo(PathModel o) {
+		boolean isTree = isTree();
+		boolean otherTree = o.isTree();
+		if (isTree && otherTree) {
+			return path.compareTo(o.path);
+		} else if (!isTree && !otherTree) {
+			if (isSubmodule() && o.isSubmodule()) {
+				return path.compareTo(o.path);
+			} else if (isSubmodule()) {
+				return -1;
+			} else if (o.isSubmodule()) {
+				return 1;
+			}
+			return path.compareTo(o.path);
+		} else if (isTree && !otherTree) {
+			return -1;
+		}
+		return 1;
+	}
+
+	/**
+	 * 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 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;
+		}
+
+		@Override
+		public int hashCode() {
+			return super.hashCode();
+		}
+
+		@Override
+		public boolean equals(Object o) {
+			return super.equals(o);
+		}
+	}
+}
diff --git a/src/com/gitblit/models/ProjectModel.java b/src/main/java/com/gitblit/models/ProjectModel.java
similarity index 100%
rename from src/com/gitblit/models/ProjectModel.java
rename to src/main/java/com/gitblit/models/ProjectModel.java
diff --git a/src/main/java/com/gitblit/models/RefLogEntry.java b/src/main/java/com/gitblit/models/RefLogEntry.java
new file mode 100644
index 0000000..abfc56b
--- /dev/null
+++ b/src/main/java/com/gitblit/models/RefLogEntry.java
@@ -0,0 +1,360 @@
+/*
+ * Copyright 2013 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit.models;
+
+import java.io.Serializable;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.transport.ReceiveCommand;
+
+import com.gitblit.utils.StringUtils;
+
+/**
+ * Model class to represent a push into a repository.
+ * 
+ * @author James Moger
+ */
+public class RefLogEntry implements Serializable, Comparable<RefLogEntry> {
+
+	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
+	 *            the date of the push
+	 * @param user
+	 *            the user who pushed
+	 */
+	public RefLogEntry(String repository, Date date, UserModel user) {
+		this.repository = repository;
+		this.date = date;
+		this.user = user;
+		this.commits = new LinkedHashSet<RepositoryCommit>();
+		this.refUpdates = new HashMap<String, ReceiveCommand.Type>();
+		this.refIdChanges = new HashMap<String, String>();
+		this.authorCount = -1;
+	}
+	
+	/**
+	 * Tracks the change type for the specified ref.
+	 * 
+	 * @param ref
+	 * @param type
+	 */
+	public void updateRef(String ref, ReceiveCommand.Type type) {
+		if (!refUpdates.containsKey(ref)) {
+			refUpdates.put(ref, type);
+		}
+	}
+	
+	/**
+	 * Tracks the change type for the specified ref.
+	 * 
+	 * @param ref
+	 * @param type
+	 * @param oldId
+	 * @param newId
+	 */
+	public void updateRef(String ref, ReceiveCommand.Type type, String oldId, String newId) {
+		if (!refUpdates.containsKey(ref)) {
+			refUpdates.put(ref, type);
+			refIdChanges.put(ref, oldId + "-" + newId);
+		}
+	}
+	
+	/**
+	 * Returns the old id of a ref.
+	 * 
+	 * @param ref
+	 * @return the old id
+	 */
+	public String getOldId(String ref) {
+		String change = refIdChanges.get(ref);
+		if (StringUtils.isEmpty(change)) {
+			return null;
+		}
+		return change.split("-")[0];
+	}
+
+	/**
+	 * Returns the new id of a ref
+	 * 
+	 * @param ref
+	 * @return the new id
+	 */
+	public String getNewId(String ref) {
+		String change = refIdChanges.get(ref);
+		if (StringUtils.isEmpty(change)) {
+			return null;
+		}
+		return change.split("-")[1];
+	}
+	
+	/**
+	 * Returns the change type of the ref change.
+	 * 
+	 * @param ref
+	 * @return the change type for the ref
+	 */
+	public ReceiveCommand.Type getChangeType(String ref) {
+		ReceiveCommand.Type type = refUpdates.get(ref);
+		return type;
+	}
+
+	/**
+	 * 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
+	 *         commit
+	 */
+	public RepositoryCommit addCommit(String branch, RevCommit commit) {
+		RepositoryCommit commitModel = new RepositoryCommit(repository, branch, commit);
+		if (commits.add(commitModel)) {
+			authorCount = -1;
+			return commitModel;
+		}
+		return null;
+	}
+
+	/**
+	 * 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
+	 *         commit
+	 */
+	public RepositoryCommit addCommit(RepositoryCommit commit) {
+		if (commits.add(commit)) {
+			authorCount = -1;
+			return commit;
+		}
+		return null;
+	}
+
+	/**
+	 * 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() {
+		for (Map.Entry<String, ReceiveCommand.Type> entry : refUpdates.entrySet()) {
+			if (ReceiveCommand.Type.UPDATE_NONFASTFORWARD.equals(entry.getValue())) {
+				return true;
+			}
+		}
+		return false;
+	}
+	
+	/**
+	 * Returns true if this ref has been rewound.
+	 * 
+	 * @param ref
+	 * @return true if this is a non-fastforward ref update
+	 */
+	public boolean isNonFastForward(String ref) {
+		ReceiveCommand.Type type = refUpdates.get(ref);
+		if (type == null) {
+			return false;
+		}
+		return ReceiveCommand.Type.UPDATE_NONFASTFORWARD.equals(type);
+	}
+
+	/**
+	 * Returns true if this ref has been deleted.
+	 * 
+	 * @param ref
+	 * @return true if this is a delete ref update
+	 */
+	public boolean isDelete(String ref) {
+		ReceiveCommand.Type type = refUpdates.get(ref);
+		if (type == null) {
+			return false;
+		}
+		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() {
+		return getChangedRefs(Constants.R_TAGS);
+	}
+
+	/**
+	 * Gets the changed refs in the push.
+	 * 
+	 * @param baseRef
+	 * @return the changed refs
+	 */
+	protected List<String> getChangedRefs(String baseRef) {
+		Set<String> refs = new HashSet<String>();
+		for (String ref : refUpdates.keySet()) {
+			if (baseRef == null || ref.startsWith(baseRef)) {
+				refs.add(ref);
+			}
+		}
+		List<String> list = new ArrayList<String>(refs);
+		Collections.sort(list);
+		return list;
+	}
+	
+	public int getAuthorCount() {
+		if (authorCount == -1) {
+			Set<String> authors = new HashSet<String>();
+			for (RepositoryCommit commit : commits) {
+				String name = commit.getAuthorIdent().getName();
+				authors.add(name);
+			}
+			authorCount = authors.size();
+		}
+		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() {
+		List<RepositoryCommit> list = new ArrayList<RepositoryCommit>(commits);
+		Collections.sort(list);
+		return list;
+	}
+	
+	/**
+	 * Returns all commits that belong to a particular ref
+	 * 
+	 * @param ref
+	 * @return a list of commits
+	 */
+	public List<RepositoryCommit> getCommits(String ref) {
+		List<RepositoryCommit> list = new ArrayList<RepositoryCommit>();
+		for (RepositoryCommit commit : commits) {
+			if (commit.branch.equals(ref)) {
+				list.add(commit);
+			}
+		}
+		Collections.sort(list);
+		return list;
+	}
+	
+	public PersonIdent getCommitterIdent() {
+		return new PersonIdent(user.getDisplayName(), user.emailAddress == null ? user.username : user.emailAddress);
+	}
+
+	public PersonIdent getAuthorIdent() {
+		if (getAuthorCount() == 1) {
+			return getCommits().get(0).getAuthorIdent();
+		}
+		return getCommitterIdent();
+	}
+
+	@Override
+	public int compareTo(RefLogEntry o) {
+		// reverse chronological order
+		return o.date.compareTo(date);
+	}
+	
+	@Override
+	public String toString() {
+		StringBuilder sb = new StringBuilder();
+		sb.append(MessageFormat.format("{0,date,yyyy-MM-dd HH:mm}: {1} pushed {2,number,0} commit{3} to {4} ",
+				date, user.getDisplayName(), commits.size(), commits.size() == 1 ? "":"s", repository));
+		for (Map.Entry<String, ReceiveCommand.Type> entry : refUpdates.entrySet()) {
+			String ref = entry.getKey();
+			ReceiveCommand.Type type = entry.getValue();
+			sb.append("\n  ").append(ref).append(' ').append(type.name()).append('\n');
+			for (RepositoryCommit commit : getCommits(ref)) {
+				sb.append("    ").append(commit.toString()).append('\n');
+			}
+		}
+		return sb.toString();
+	}
+}
diff --git a/src/com/gitblit/models/RefModel.java b/src/main/java/com/gitblit/models/RefModel.java
similarity index 100%
rename from src/com/gitblit/models/RefModel.java
rename to src/main/java/com/gitblit/models/RefModel.java
diff --git a/src/com/gitblit/models/RegistrantAccessPermission.java b/src/main/java/com/gitblit/models/RegistrantAccessPermission.java
similarity index 100%
rename from src/com/gitblit/models/RegistrantAccessPermission.java
rename to src/main/java/com/gitblit/models/RegistrantAccessPermission.java
diff --git a/src/main/java/com/gitblit/models/RepositoryCommit.java b/src/main/java/com/gitblit/models/RepositoryCommit.java
new file mode 100644
index 0000000..dd58b42
--- /dev/null
+++ b/src/main/java/com/gitblit/models/RepositoryCommit.java
@@ -0,0 +1,130 @@
+/*
+ * 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.models;
+
+import java.io.Serializable;
+import java.text.MessageFormat;
+import java.util.Date;
+import java.util.List;
+
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.revwalk.RevCommit;
+
+/**
+ * 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> {
+
+	private static final long serialVersionUID = 1L;
+
+	public final String repository;
+
+	public final String branch;
+
+	private final RevCommit commit;
+
+	private List<RefModel> refs;
+
+	public RepositoryCommit(String repository, String branch, RevCommit commit) {
+		this.repository = repository;
+		this.branch = branch;
+		this.commit = commit;
+	}
+
+	public void setRefs(List<RefModel> refs) {
+		this.refs = refs;
+	}
+
+	public List<RefModel> getRefs() {
+		return refs;
+	}
+
+	public ObjectId getId() {
+		return commit.getId();
+	}
+
+	public String getName() {
+		return commit.getName();
+	}
+
+	public String getShortName() {
+		return commit.getName().substring(0, 8);
+	}
+
+	public String getShortMessage() {
+		return commit.getShortMessage();
+	}
+	
+	public Date getCommitDate() {
+		return new Date(commit.getCommitTime() * 1000L);
+	}
+
+	public int getParentCount() {
+		return commit.getParentCount();
+	}
+	
+	public RevCommit [] getParents() {
+		return commit.getParents();
+	}
+
+	public PersonIdent getAuthorIdent() {
+		return commit.getAuthorIdent();
+	}
+
+	public PersonIdent getCommitterIdent() {
+		return commit.getCommitterIdent();
+	}
+	
+	@Override
+	public boolean equals(Object o) {
+		if (o instanceof RepositoryCommit) {
+			RepositoryCommit commit = (RepositoryCommit) o;
+			return repository.equals(commit.repository) && getName().equals(commit.getName());
+		}
+		return false;
+	}
+
+	@Override
+	public int hashCode() {
+		return (repository + commit).hashCode();
+	}
+
+	@Override
+	public int compareTo(RepositoryCommit o) {
+		// reverse-chronological order
+		if (commit.getCommitTime() > o.commit.getCommitTime()) {
+			return -1;
+		} else if (commit.getCommitTime() < o.commit.getCommitTime()) {
+			return 1;
+		}
+		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}", 
+				getShortName(), branch, getCommitterIdent().getWhen(), getAuthorIdent().getName(),
+				getShortMessage());
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/models/RepositoryModel.java b/src/main/java/com/gitblit/models/RepositoryModel.java
new file mode 100644
index 0000000..2996265
--- /dev/null
+++ b/src/main/java/com/gitblit/models/RepositoryModel.java
@@ -0,0 +1,252 @@
+/*
+ * 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.models;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+
+import com.gitblit.Constants.AccessRestrictionType;
+import com.gitblit.Constants.AuthorizationControl;
+import com.gitblit.Constants.FederationStrategy;
+import com.gitblit.utils.ArrayUtils;
+import com.gitblit.utils.StringUtils;
+
+/**
+ * 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> {
+
+	private static final long serialVersionUID = 1L;
+
+	// field names are reflectively mapped in EditRepository page
+	public String name;
+	public String description;
+	public List<String> owners;
+	public Date lastChange;
+	public String lastChangeAuthor;
+	public boolean hasCommits;
+	public boolean showRemoteBranches;
+	public boolean useTickets;
+	public boolean useDocs;
+	public boolean useIncrementalPushTags;
+	public String incrementalPushTagPrefix;
+	public AccessRestrictionType accessRestriction;
+	public AuthorizationControl authorizationControl;
+	public boolean allowAuthenticated;
+	public boolean isFrozen;
+	public boolean showReadme;
+	public FederationStrategy federationStrategy;
+	public List<String> federationSets;
+	public boolean isFederated;
+	public boolean skipSizeCalculation;
+	public boolean skipSummaryMetrics;
+	public String frequency;
+	public boolean isBare;
+	public String origin;
+	public String HEAD;
+	public List<String> availableRefs;
+	public List<String> indexedBranches;
+	public String size;
+	public List<String> preReceiveScripts;
+	public List<String> postReceiveScripts;
+	public List<String> mailingLists;
+	public Map<String, String> customFields;
+	public String projectPath;
+	private String displayName;
+	public boolean allowForks;
+	public Set<String> forks;
+	public String originRepository;
+	public boolean verifyCommitter;
+	public String gcThreshold;
+	public int gcPeriod;
+	public int maxActivityCommits;	
+	public List<String> metricAuthorExclusions;
+	
+	public transient boolean isCollectingGarbage;
+	public Date lastGC;
+	public String sparkleshareId;
+	
+	public RepositoryModel() {
+		this("", "", "", new Date(0));
+	}
+
+	public RepositoryModel(String name, String description, String owner, Date lastchange) {
+		this.name = name;
+		this.description = description;
+		this.lastChange = lastchange;
+		this.accessRestriction = AccessRestrictionType.NONE;
+		this.authorizationControl = AuthorizationControl.NAMED;
+		this.federationSets = new ArrayList<String>();
+		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>();
+		}
+		List<String> localBranches = new ArrayList<String>();
+		for (String ref : availableRefs) {
+			if (ref.startsWith("refs/heads")) {
+				localBranches.add(ref);
+			}
+		}
+		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) {
+			return name.equals(((RepositoryModel) o).name);
+		}
+		return false;
+	}
+
+	@Override
+	public String toString() {
+		if (displayName == null) {
+			displayName = StringUtils.stripDotGit(name);
+		}
+		return displayName;
+	}
+
+	@Override
+	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) && projectPath.charAt(0) == '~';
+	}
+	
+	public boolean isUsersPersonalRepository(String username) {
+		return !StringUtils.isEmpty(projectPath) && projectPath.equalsIgnoreCase("~" + username);
+	}
+	
+	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;
+		clone.name = cloneName;
+		clone.projectPath = StringUtils.getFirstPathElement(cloneName);
+		clone.isBare = true;
+		clone.description = description;
+		clone.accessRestriction = AccessRestrictionType.PUSH;
+		clone.authorizationControl = AuthorizationControl.NAMED;
+		clone.federationStrategy = federationStrategy;
+		clone.showReadme = showReadme;
+		clone.showRemoteBranches = false;
+		clone.allowForks = false;
+		clone.useDocs = useDocs;
+		clone.useTickets = useTickets;
+		clone.skipSizeCalculation = skipSizeCalculation;
+		clone.skipSummaryMetrics = skipSummaryMetrics;
+		clone.sparkleshareId = sparkleshareId; 
+		return clone;
+	}
+
+	public void addOwner(String username) {
+		if (!StringUtils.isEmpty(username)) {
+			String name = username.toLowerCase();
+			// a set would be more efficient, but this complicates JSON
+			// deserialization so we enforce uniqueness with an arraylist
+			if (!owners.contains(name)) {
+				owners.add(name);
+			}
+		}
+	}
+
+	public void removeOwner(String username) {
+		if (!StringUtils.isEmpty(username)) {
+			owners.remove(username.toLowerCase());
+		}
+	}
+
+	public void addOwners(Collection<String> usernames) {
+		if (!ArrayUtils.isEmpty(usernames)) {
+			for (String username : usernames) {
+				addOwner(username);
+			}
+		}
+	}
+
+	public void removeOwners(Collection<String> usernames) {
+		if (!ArrayUtils.isEmpty(owners)) {
+			for (String username : usernames) {
+				removeOwner(username);
+			}
+		}
+	}
+}
diff --git a/src/main/java/com/gitblit/models/RepositoryUrl.java b/src/main/java/com/gitblit/models/RepositoryUrl.java
new file mode 100644
index 0000000..d72959a
--- /dev/null
+++ b/src/main/java/com/gitblit/models/RepositoryUrl.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2013 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit.models;
+
+import java.io.Serializable;
+
+import com.gitblit.Constants.AccessPermission;
+
+/**
+ * Represents a git repository url and it's associated access permission for the
+ * current user.
+ *  
+ * @author James Moger
+ *
+ */
+public class RepositoryUrl implements Serializable {
+
+	private static final long serialVersionUID = 1L;
+
+	public final String url;
+	public final AccessPermission permission;
+
+	public RepositoryUrl(String url, AccessPermission permission) {
+		this.url = url;
+		this.permission = permission;
+	}
+	
+	public boolean isExternal() {
+		return permission == null;
+	}
+
+	@Override
+	public String toString() {
+		return url;
+	}
+}
\ No newline at end of file
diff --git a/src/com/gitblit/models/SearchResult.java b/src/main/java/com/gitblit/models/SearchResult.java
similarity index 100%
rename from src/com/gitblit/models/SearchResult.java
rename to src/main/java/com/gitblit/models/SearchResult.java
diff --git a/src/com/gitblit/models/ServerSettings.java b/src/main/java/com/gitblit/models/ServerSettings.java
similarity index 100%
rename from src/com/gitblit/models/ServerSettings.java
rename to src/main/java/com/gitblit/models/ServerSettings.java
diff --git a/src/main/java/com/gitblit/models/ServerStatus.java b/src/main/java/com/gitblit/models/ServerStatus.java
new file mode 100644
index 0000000..3a1e030
--- /dev/null
+++ b/src/main/java/com/gitblit/models/ServerStatus.java
@@ -0,0 +1,83 @@
+/*
+ * 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.models;
+
+import java.io.Serializable;
+import java.util.Date;
+import java.util.Map;
+import java.util.TreeMap;
+
+import com.gitblit.Constants;
+
+/**
+ * ServerStatus encapsulates runtime status information about the server
+ * including some information about the system environment.
+ * 
+ * @author James Moger
+ * 
+ */
+public class ServerStatus implements Serializable {
+
+	private static final long serialVersionUID = 1L;
+
+	public final Date bootDate;
+
+	public final String version;
+
+	public final String releaseDate;
+
+	public final boolean isGO;
+
+	public final Map<String, String> systemProperties;
+
+	public final long heapMaximum;
+
+	public volatile long heapAllocated;
+
+	public volatile long heapFree;
+
+	public String servletContainer;
+
+	public ServerStatus(boolean isGO) {
+		this.bootDate = new Date();
+		this.version = Constants.getVersion();
+		this.releaseDate = Constants.getBuildDate();
+		this.isGO = isGO;
+
+		this.heapMaximum = Runtime.getRuntime().maxMemory();
+
+		this.systemProperties = new TreeMap<String, String>();
+		put("file.encoding");
+		put("java.home");
+		put("java.awt.headless");
+		put("java.io.tmpdir");
+		put("java.runtime.name");
+		put("java.runtime.version");
+		put("java.vendor");
+		put("java.version");
+		put("java.vm.info");
+		put("java.vm.name");
+		put("java.vm.vendor");
+		put("java.vm.version");
+		put("os.arch");
+		put("os.name");
+		put("os.version");
+	}
+
+	private void put(String key) {
+		systemProperties.put(key, System.getProperty(key));
+	}
+}
diff --git a/src/com/gitblit/models/SettingModel.java b/src/main/java/com/gitblit/models/SettingModel.java
similarity index 100%
rename from src/com/gitblit/models/SettingModel.java
rename to src/main/java/com/gitblit/models/SettingModel.java
diff --git a/src/com/gitblit/models/SubmoduleModel.java b/src/main/java/com/gitblit/models/SubmoduleModel.java
similarity index 100%
rename from src/com/gitblit/models/SubmoduleModel.java
rename to src/main/java/com/gitblit/models/SubmoduleModel.java
diff --git a/src/main/java/com/gitblit/models/TeamModel.java b/src/main/java/com/gitblit/models/TeamModel.java
new file mode 100644
index 0000000..dfbd45d
--- /dev/null
+++ b/src/main/java/com/gitblit/models/TeamModel.java
@@ -0,0 +1,370 @@
+/*
+ * 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.models;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import com.gitblit.Constants.AccessPermission;
+import com.gitblit.Constants.AccessRestrictionType;
+import com.gitblit.Constants.PermissionType;
+import com.gitblit.Constants.RegistrantType;
+import com.gitblit.Constants.Unused;
+import com.gitblit.utils.StringUtils;
+
+/**
+ * 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> {
+
+	private static final long serialVersionUID = 1L;
+
+	// field names are reflectively mapped in EditTeam page
+	public String name;
+	public boolean canAdmin;
+	public boolean canFork;
+	public boolean canCreate;
+	public final Set<String> users = new HashSet<String>();
+	// retained for backwards-compatibility with RPC clients
+	@Deprecated
+	public final Set<String> repositories = new HashSet<String>();
+	public final Map<String, AccessPermission> permissions = new LinkedHashMap<String, AccessPermission>();
+	public final Set<String> mailingLists = new HashSet<String>();
+	public final List<String> preReceiveScripts = new ArrayList<String>();
+	public final List<String> postReceiveScripts = new ArrayList<String>();
+
+	public TeamModel(String name) {
+		this.name = name;
+	}
+
+	/**
+	 * @use hasRepositoryPermission
+	 * @param name
+	 * @return
+	 */
+	@Deprecated
+	@Unused
+	public boolean hasRepository(String name) {
+		return hasRepositoryPermission(name);
+	}
+
+	@Deprecated
+	@Unused
+	public void addRepository(String name) {
+		addRepositoryPermission(name);
+	}
+	
+	@Deprecated
+	@Unused
+	public void addRepositories(Collection<String> names) {
+		addRepositoryPermissions(names);
+	}
+
+	@Deprecated
+	@Unused
+	public void removeRepository(String name) {
+		removeRepositoryPermission(name);
+	}
+
+	
+	/**
+	 * Returns a list of repository permissions for this team.
+	 * 
+	 * @return the team's list of permissions
+	 */
+	public List<RegistrantAccessPermission> getRepositoryPermissions() {
+		List<RegistrantAccessPermission> list = new ArrayList<RegistrantAccessPermission>();
+		if (canAdmin) {
+			// team has REWIND access to all repositories
+			return list;
+		}
+		for (Map.Entry<String, AccessPermission> entry : permissions.entrySet()) {
+			String registrant = entry.getKey();
+			String source = null;
+			boolean editable = true;
+			PermissionType pType = PermissionType.EXPLICIT;
+			if (StringUtils.findInvalidCharacter(registrant) != null) {
+				// a regex will have at least 1 invalid character
+				pType = PermissionType.REGEX;
+				source = registrant;
+			}
+			list.add(new RegistrantAccessPermission(registrant, entry.getValue(), pType, RegistrantType.REPOSITORY, source, editable));
+		}
+		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
+	 */
+	public boolean hasRepositoryPermission(String name) {
+		String repository = AccessPermission.repositoryFromRole(name).toLowerCase();
+		if (permissions.containsKey(repository)) {
+			// exact repository permission specified
+			return true;
+		} else {
+			// search for regex permission match
+			for (String key : permissions.keySet()) {
+				if (name.matches(key)) {
+					AccessPermission p = permissions.get(key);
+					if (p != null) {
+						return true;
+					}
+				}
+			}
+		}
+		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
+	 */
+	public boolean hasExplicitRepositoryPermission(String name) {
+		String repository = AccessPermission.repositoryFromRole(name).toLowerCase();
+		return permissions.containsKey(repository);
+	}
+	
+	/**
+	 * Adds a repository permission to the team.
+	 * <p>
+	 * Role may be formatted as:
+	 * <ul>
+	 * <li> myrepo.git <i>(this is implicitly RW+)</i>
+	 * <li> RW+:myrepo.git
+	 * </ul>
+	 * @param role
+	 */
+	public void addRepositoryPermission(String role) {
+		AccessPermission permission = AccessPermission.permissionFromRole(role);
+		String repository = AccessPermission.repositoryFromRole(role).toLowerCase();
+		repositories.add(repository);
+		permissions.put(repository, permission);
+	}
+
+	public void addRepositoryPermissions(Collection<String> roles) {
+		for (String role:roles) {
+			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
+			permissions.remove(repository.toLowerCase());
+			repositories.remove(repository.toLowerCase());
+		} else {
+			// set the new permission
+			permissions.put(repository.toLowerCase(), permission);
+			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 = 
+				(repository.isFrozen || !repository.isBare) ?
+						AccessPermission.CLONE : AccessPermission.REWIND;
+
+		if (AccessRestrictionType.NONE.equals(repository.accessRestriction)) {
+			// anonymous rewind
+			ap.permissionType = PermissionType.ANONYMOUS;
+			if (AccessPermission.REWIND.atMost(maxPermission)) {
+				ap.permission = AccessPermission.REWIND;
+			} else {
+				ap.permission = maxPermission;
+			}
+			return ap;
+		}
+		
+		if (canAdmin) {
+			ap.permissionType = PermissionType.ADMINISTRATOR;
+			if (AccessPermission.REWIND.atMost(maxPermission)) {
+				ap.permission = AccessPermission.REWIND;
+			} else {
+				ap.permission = maxPermission;
+			}
+			return ap;
+		}
+		
+		if (permissions.containsKey(repository.name.toLowerCase())) {
+			// exact repository permission specified
+			AccessPermission p = permissions.get(repository.name.toLowerCase());
+			if (p != null && repository.accessRestriction.isValidPermission(p)) {
+				ap.permissionType = PermissionType.EXPLICIT;
+				if (p.atMost(maxPermission)) {
+					ap.permission = p;
+				} else {
+					ap.permission = maxPermission;
+				}
+				ap.mutable = true;
+				return ap;
+			}
+		} else {
+			// search for case-insensitive regex permission match
+			for (String key : permissions.keySet()) {
+				if (StringUtils.matchesIgnoreCase(repository.name, key)) {
+					AccessPermission p = permissions.get(key);
+					if (p != null && repository.accessRestriction.isValidPermission(p)) {
+						// take first match
+						ap.permissionType = PermissionType.REGEX;
+						if (p.atMost(maxPermission)) {
+							ap.permission = p;
+						} else {
+							ap.permission = maxPermission;
+						}
+						ap.source = key;
+						return ap;
+					}
+				}
+			}
+		}
+		
+		// still no explicit or regex, check for implicit permissions
+		if (AccessPermission.NONE == ap.permission) {
+			switch (repository.accessRestriction) {
+			case VIEW:
+				// no implicit permissions possible
+				break;
+			case CLONE:
+				// implied view permission
+				ap.permission = AccessPermission.VIEW;
+				ap.permissionType = PermissionType.ANONYMOUS;
+				break;
+			case PUSH:
+				// implied clone permission
+				ap.permission = AccessPermission.CLONE;
+				ap.permissionType = PermissionType.ANONYMOUS;
+				break;
+			case NONE:
+				// implied REWIND or CLONE
+				ap.permission = maxPermission;
+				ap.permissionType = PermissionType.ANONYMOUS;
+				break;
+			}
+		}
+
+		return ap;
+	}
+	
+	protected boolean canAccess(RepositoryModel repository, AccessRestrictionType ifRestriction, AccessPermission requirePermission) {
+		if (repository.accessRestriction.atLeast(ifRestriction)) {
+			RegistrantAccessPermission ap = getRepositoryPermission(repository);
+			return ap.permission.atLeast(requirePermission);
+		}
+		return true;
+	}
+	
+	public boolean canView(RepositoryModel repository) {
+		return canAccess(repository, AccessRestrictionType.VIEW, AccessPermission.VIEW);
+	}
+
+	public boolean canClone(RepositoryModel repository) {
+		return canAccess(repository, AccessRestrictionType.CLONE, AccessPermission.CLONE);
+	}
+
+	public boolean canPush(RepositoryModel repository) {
+		if (repository.isFrozen) {
+			return false;
+		}
+		return canAccess(repository, AccessRestrictionType.PUSH, AccessPermission.PUSH);
+	}
+
+	public boolean canCreateRef(RepositoryModel repository) {
+		if (repository.isFrozen) {
+			return false;
+		}
+		return canAccess(repository, AccessRestrictionType.PUSH, AccessPermission.CREATE);
+	}
+
+	public boolean canDeleteRef(RepositoryModel repository) {
+		if (repository.isFrozen) {
+			return false;
+		}
+		return canAccess(repository, AccessRestrictionType.PUSH, AccessPermission.DELETE);
+	}
+
+	public boolean canRewindRef(RepositoryModel repository) {
+		if (repository.isFrozen) {
+			return false;
+		}
+		return canAccess(repository, AccessRestrictionType.PUSH, AccessPermission.REWIND);
+	}
+
+	public boolean hasUser(String name) {
+		return users.contains(name.toLowerCase());
+	}
+
+	public void addUser(String name) {
+		users.add(name.toLowerCase());
+	}
+
+	public void addUsers(Collection<String> names) {
+		for (String name:names) {
+			users.add(name.toLowerCase());
+		}
+	}
+
+	public void removeUser(String name) {
+		users.remove(name.toLowerCase());
+	}
+
+	public void addMailingLists(Collection<String> addresses) {
+		for (String address:addresses) {
+			mailingLists.add(address.toLowerCase());
+		}
+	}
+
+	@Override
+	public String toString() {
+		return name;
+	}
+
+	@Override
+	public int compareTo(TeamModel o) {
+		return name.compareTo(o.name);
+	}
+}
diff --git a/src/com/gitblit/models/TicketModel.java b/src/main/java/com/gitblit/models/TicketModel.java
similarity index 100%
rename from src/com/gitblit/models/TicketModel.java
rename to src/main/java/com/gitblit/models/TicketModel.java
diff --git a/src/main/java/com/gitblit/models/UserModel.java b/src/main/java/com/gitblit/models/UserModel.java
new file mode 100644
index 0000000..6d58512
--- /dev/null
+++ b/src/main/java/com/gitblit/models/UserModel.java
@@ -0,0 +1,680 @@
+/*
+ * 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.models;
+
+import java.io.Serializable;
+import java.security.Principal;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+
+import com.gitblit.Constants.AccessPermission;
+import com.gitblit.Constants.AccessRestrictionType;
+import com.gitblit.Constants.AccountType;
+import com.gitblit.Constants.AuthorizationControl;
+import com.gitblit.Constants.PermissionType;
+import com.gitblit.Constants.RegistrantType;
+import com.gitblit.Constants.Unused;
+import com.gitblit.utils.ArrayUtils;
+import com.gitblit.utils.StringUtils;
+
+/**
+ * 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;
+	public String cookie;
+	public String displayName;
+	public String emailAddress;
+	public String organizationalUnit;
+	public String organization;
+	public String locality;
+	public String stateProvince;
+	public String countryCode;
+	public boolean canAdmin;
+	public boolean canFork;
+	public boolean canCreate;
+	public boolean excludeFromFederation;
+	// retained for backwards-compatibility with RPC clients
+	@Deprecated
+	public final Set<String> repositories = new HashSet<String>();
+	public final Map<String, AccessPermission> permissions = new LinkedHashMap<String, AccessPermission>();
+	public final Set<TeamModel> teams = new TreeSet<TeamModel>();
+
+	// non-persisted fields
+	public boolean isAuthenticated;
+	public AccountType accountType;
+
+	public UserPreferences userPreferences;
+	
+	public UserModel(String username) {
+		this.username = username;
+		this.isAuthenticated = true;
+		this.accountType = AccountType.LOCAL;
+		this.userPreferences = new UserPreferences(this.username);
+	}
+
+	private UserModel() {
+		this.username = "$anonymous";
+		this.isAuthenticated = false;
+		this.accountType = AccountType.LOCAL;
+		this.userPreferences = new UserPreferences(this.username);
+	}
+	
+	public boolean isLocalAccount() {
+		return accountType.isLocal();
+	}
+
+	/**
+	 * This method does not take into consideration Ownership where the
+	 * administrator has not explicitly granted access to the owner.
+	 * 
+	 * @param repositoryName
+	 * @return
+	 */
+	@Deprecated
+	public boolean canAccessRepository(String repositoryName) {
+		return canAdmin() || repositories.contains(repositoryName.toLowerCase())
+				|| hasTeamAccess(repositoryName);
+	}
+
+	@Deprecated
+	@Unused
+	public boolean canAccessRepository(RepositoryModel repository) {
+		boolean isOwner = repository.isOwner(username);
+		boolean allowAuthenticated = isAuthenticated && AuthorizationControl.AUTHENTICATED.equals(repository.authorizationControl);
+		return canAdmin() || isOwner || repositories.contains(repository.name.toLowerCase())
+				|| hasTeamAccess(repository.name) || allowAuthenticated;
+	}
+
+	@Deprecated
+	@Unused
+	public boolean hasTeamAccess(String repositoryName) {
+		for (TeamModel team : teams) {
+			if (team.hasRepositoryPermission(repositoryName)) {
+				return true;
+			}
+		}
+		return false;
+	}
+	
+	@Deprecated
+	@Unused
+	public boolean hasRepository(String name) {
+		return hasRepositoryPermission(name);
+	}
+
+	@Deprecated
+	@Unused
+	public void addRepository(String name) {
+		addRepositoryPermission(name);
+	}
+
+	@Deprecated
+	@Unused
+	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() {
+		List<RegistrantAccessPermission> list = new ArrayList<RegistrantAccessPermission>();
+		if (canAdmin()) {
+			// user has REWIND access to all repositories
+			return list;
+		}
+		for (Map.Entry<String, AccessPermission> entry : permissions.entrySet()) {
+			String registrant = entry.getKey();
+			AccessPermission ap = entry.getValue();
+			String source = null;
+			boolean mutable = true;
+			PermissionType pType = PermissionType.EXPLICIT;
+			if (isMyPersonalRepository(registrant)) {
+				pType = PermissionType.OWNER;
+				ap = AccessPermission.REWIND;
+				mutable = false;
+			} else if (StringUtils.findInvalidCharacter(registrant) != null) {
+				// a regex will have at least 1 invalid character
+				pType = PermissionType.REGEX;
+				source = registrant;
+			}
+			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) {
+			for (RegistrantAccessPermission teamPermission : team.getRepositoryPermissions()) {
+				// we can not change an inherited team permission, though we can override
+				teamPermission.registrantType = RegistrantType.REPOSITORY;
+				teamPermission.permissionType = PermissionType.TEAM;
+				teamPermission.source = team.name;
+				teamPermission.mutable = false;
+				set.add(teamPermission);
+			}
+		}
+		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
+	 */
+	public boolean hasRepositoryPermission(String name) {
+		String repository = AccessPermission.repositoryFromRole(name).toLowerCase();
+		if (permissions.containsKey(repository)) {
+			// exact repository permission specified
+			return true;
+		} else {
+			// search for regex permission match
+			for (String key : permissions.keySet()) {
+				if (name.matches(key)) {
+					AccessPermission p = permissions.get(key);
+					if (p != null) {
+						return true;
+					}
+				}
+			}
+		}
+		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
+	 */
+	public boolean hasExplicitRepositoryPermission(String name) {
+		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
+	 */
+	public boolean hasTeamRepositoryPermission(String name) {
+		if (teams != null) {
+			for (TeamModel team : teams) {
+				if (team.hasRepositoryPermission(name)) {
+					return true;
+				}
+			}
+		}
+		return false;
+	}
+	
+	/**
+	 * Adds a repository permission to the team.
+	 * <p>
+	 * Role may be formatted as:
+	 * <ul>
+	 * <li> myrepo.git <i>(this is implicitly RW+)</i>
+	 * <li> RW+:myrepo.git
+	 * </ul>
+	 * @param role
+	 */
+	public void addRepositoryPermission(String role) {
+		AccessPermission permission = AccessPermission.permissionFromRole(role);
+		String repository = AccessPermission.repositoryFromRole(role).toLowerCase();
+		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
+			permissions.remove(repository.toLowerCase());
+		} else {
+			// set the new permission
+			permissions.put(repository.toLowerCase(), permission);
+		}
+	}
+
+	public RegistrantAccessPermission getRepositoryPermission(RepositoryModel repository) {
+		RegistrantAccessPermission ap = new RegistrantAccessPermission();
+		ap.registrant = username;
+		ap.registrantType = RegistrantType.USER;
+		ap.permission = AccessPermission.NONE;
+		ap.mutable = false;
+		
+		// determine maximum permission for the repository
+		final AccessPermission maxPermission = 
+				(repository.isFrozen || !repository.isBare) ?
+						AccessPermission.CLONE : AccessPermission.REWIND;
+
+		if (AccessRestrictionType.NONE.equals(repository.accessRestriction)) {
+			// anonymous rewind
+			ap.permissionType = PermissionType.ANONYMOUS;
+			if (AccessPermission.REWIND.atMost(maxPermission)) {
+				ap.permission = AccessPermission.REWIND;
+			} else {
+				ap.permission = maxPermission;
+			}
+			return ap;
+		}
+
+		// administrator
+		if (canAdmin()) {
+			ap.permissionType = PermissionType.ADMINISTRATOR;
+			if (AccessPermission.REWIND.atMost(maxPermission)) {
+				ap.permission = AccessPermission.REWIND;
+			} else {
+				ap.permission = maxPermission;
+			}
+			if (!canAdmin) {
+				// administator permission from team membership
+				for (TeamModel team : teams) {
+					if (team.canAdmin) {
+						ap.source = team.name;
+						break;
+					}
+				}
+			}
+			return ap;
+		}
+		
+		// repository owner - either specified owner or personal repository
+		if (repository.isOwner(username) || repository.isUsersPersonalRepository(username)) {
+			ap.permissionType = PermissionType.OWNER;
+			if (AccessPermission.REWIND.atMost(maxPermission)) {
+				ap.permission = AccessPermission.REWIND;
+			} else {
+				ap.permission = maxPermission;
+			}
+			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)) {
+				ap.permission = AccessPermission.REWIND;
+			} else {
+				ap.permission = maxPermission;
+			}
+			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())) {
+			// exact repository permission specified, use it
+			AccessPermission p = permissions.get(repository.name.toLowerCase());
+			if (p != null && repository.accessRestriction.isValidPermission(p)) {
+				ap.permissionType = PermissionType.EXPLICIT;
+				if (p.atMost(maxPermission)) {
+					ap.permission = p;
+				} else {
+					ap.permission = maxPermission;
+				}
+				ap.mutable = true;
+				return ap;
+			}
+		} else {
+			// search for case-insensitive regex permission match
+			for (String key : permissions.keySet()) {
+				if (StringUtils.matchesIgnoreCase(repository.name, key)) {
+					AccessPermission p = permissions.get(key);
+					if (p != null && repository.accessRestriction.isValidPermission(p)) {
+						// take first match
+						ap.permissionType = PermissionType.REGEX;
+						if (p.atMost(maxPermission)) {
+							ap.permission = p;
+						} else {
+							ap.permission = maxPermission;
+						}
+						ap.source = key;
+						return ap;
+					}
+				}
+			}
+		}
+		
+		// try to find a team match
+		for (TeamModel team : teams) {
+			RegistrantAccessPermission p = team.getRepositoryPermission(repository);
+			if (p.permission.atMost(maxPermission) && p.permission.exceeds(ap.permission) && PermissionType.ANONYMOUS != p.permissionType) {
+				// use highest team permission that is not an implicit permission
+				ap.permission = p.permission;
+				ap.source = team.name;
+				ap.permissionType = PermissionType.TEAM;
+			}
+		}
+		
+		// still no explicit, regex, or team match, check for implicit permissions
+		if (AccessPermission.NONE == ap.permission) {
+			switch (repository.accessRestriction) {
+			case VIEW:
+				// no implicit permissions possible
+				break;
+			case CLONE:
+				// implied view permission
+				ap.permission = AccessPermission.VIEW;
+				ap.permissionType = PermissionType.ANONYMOUS;
+				break;
+			case PUSH:
+				// implied clone permission
+				ap.permission = AccessPermission.CLONE;
+				ap.permissionType = PermissionType.ANONYMOUS;
+				break;
+			case NONE:
+				// implied REWIND or CLONE
+				ap.permission = maxPermission;
+				ap.permissionType = PermissionType.ANONYMOUS;
+				break;
+			}
+		}
+		
+		return ap;
+	}
+	
+	protected boolean canAccess(RepositoryModel repository, AccessRestrictionType ifRestriction, AccessPermission requirePermission) {
+		if (repository.accessRestriction.atLeast(ifRestriction)) {
+			RegistrantAccessPermission ap = getRepositoryPermission(repository);
+			return ap.permission.atLeast(requirePermission);
+		}
+		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.
+		return canView(repository);
+	}
+
+	public boolean canClone(RepositoryModel repository) {
+		return canAccess(repository, AccessRestrictionType.CLONE, AccessPermission.CLONE);
+	}
+
+	public boolean canPush(RepositoryModel repository) {
+		if (repository.isFrozen) {
+			return false;
+		}
+		return canAccess(repository, AccessRestrictionType.PUSH, AccessPermission.PUSH);
+	}
+
+	public boolean canCreateRef(RepositoryModel repository) {
+		if (repository.isFrozen) {
+			return false;
+		}
+		return canAccess(repository, AccessRestrictionType.PUSH, AccessPermission.CREATE);
+	}
+
+	public boolean canDeleteRef(RepositoryModel repository) {
+		if (repository.isFrozen) {
+			return false;
+		}
+		return canAccess(repository, AccessRestrictionType.PUSH, AccessPermission.DELETE);
+	}
+
+	public boolean canRewindRef(RepositoryModel repository) {
+		if (repository.isFrozen) {
+			return false;
+		}
+		return canAccess(repository, AccessRestrictionType.PUSH, AccessPermission.REWIND);
+	}
+
+	public boolean canFork(RepositoryModel repository) {
+		if (repository.isUsersPersonalRepository(username)) {
+			// can not fork your own repository
+			return false;
+		}
+		if (canAdmin() || repository.isOwner(username)) {
+			return true;
+		}
+		if (!repository.allowForks) {
+			return false;
+		}
+		if (!isAuthenticated || !canFork()) {
+			return false;
+		}
+		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() {
+		if (canFork) {
+			return true;
+		}
+		if (!ArrayUtils.isEmpty(teams)) {
+			for (TeamModel team : teams) {
+				if (team.canFork) {
+					return true;
+				}
+			}
+		}
+		return false;
+	}
+
+	/**
+	 * 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() {
+		if (canAdmin) {
+			return true;
+		}
+		if (!ArrayUtils.isEmpty(teams)) {
+			for (TeamModel team : teams) {
+				if (team.canAdmin) {
+					return true;
+				}
+			}
+		}
+		return false;
+	}
+
+	/**
+	 * 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() {
+		if (canCreate) {
+			return true;
+		}
+		if (!ArrayUtils.isEmpty(teams)) {
+			for (TeamModel team : teams) {
+				if (team.canCreate) {
+					return true;
+				}
+			}
+		}
+		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
+	 */
+	public boolean canCreate(String repository) {
+		if (canAdmin()) {
+			// admins can create any repository
+			return true;
+		}
+		if (canCreate) {
+			String projectPath = StringUtils.getFirstPathElement(repository);
+			if (!StringUtils.isEmpty(projectPath) && projectPath.equalsIgnoreCase("~" + username)) {
+				// personal repository
+				return true;
+			}
+		}
+		return false;
+	}
+
+	public boolean isTeamMember(String teamname) {
+		for (TeamModel team : teams) {
+			if (team.name.equalsIgnoreCase(teamname)) {
+				return true;
+			}
+		}
+		return false;
+	}
+
+	public TeamModel getTeam(String teamname) {
+		if (teams == null) {
+			return null;
+		}
+		for (TeamModel team : teams) {
+			if (team.name.equalsIgnoreCase(teamname)) {
+				return team;
+			}
+		}
+		return null;
+	}
+
+	@Override
+	public String getName() {
+		return username;
+	}
+	
+	public String getDisplayName() {
+		if (StringUtils.isEmpty(displayName)) {
+			return username;
+		}
+		return displayName;
+	}
+	
+	public String getPersonalPath() {
+		return "~" + username;
+	}
+	
+	public UserPreferences getPreferences() {
+		return userPreferences;
+	}
+	
+	@Override
+	public int hashCode() {
+		return username.hashCode();
+	}
+	
+	@Override
+	public boolean equals(Object o) {
+		if (o instanceof UserModel) {
+			return username.equals(((UserModel) o).username);
+		}
+		return false;
+	}
+
+	@Override
+	public String toString() {
+		return username;
+	}
+
+	@Override
+	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
+	 */
+	public boolean is(String name, String email) {
+		// at a minimum a usename or display name must be supplied
+		if (StringUtils.isEmpty(name)) {
+			return false;
+		}
+		boolean nameVerified = name.equalsIgnoreCase(username) || name.equalsIgnoreCase(getDisplayName());
+		boolean emailVerified = false;
+		if (StringUtils.isEmpty(emailAddress)) {
+			// user account has not specified an email address
+			// rely on username/displayname verification
+			emailVerified = true;
+		} else {
+			// user account has specified an email address
+			// require email address verification
+			if (!StringUtils.isEmpty(email)) {
+				emailVerified = email.equalsIgnoreCase(emailAddress);
+			}
+		}
+		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("~" + username);
+	}
+}
diff --git a/src/main/java/com/gitblit/models/UserPreferences.java b/src/main/java/com/gitblit/models/UserPreferences.java
new file mode 100644
index 0000000..e6baa28
--- /dev/null
+++ b/src/main/java/com/gitblit/models/UserPreferences.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2013 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit.models;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.TreeMap;
+
+import com.gitblit.utils.StringUtils;
+
+/**
+ * User preferences.
+ * 
+ * @author James Moger
+ *
+ */
+public class UserPreferences implements Serializable {
+
+	private static final long serialVersionUID = 1L;
+
+	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)) {
+			// default preferences
+			UserRepositoryPreferences prefs = new UserRepositoryPreferences();
+			prefs.username = username;
+			prefs.repositoryName = repositoryName;
+			repositoryPreferences.put(key, prefs);
+		}
+		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;
+		}
+		String key = repository.toLowerCase();
+		if (repositoryPreferences.containsKey(key)) {
+			UserRepositoryPreferences pref = repositoryPreferences.get(key);
+			return pref.starred;
+		}
+		return false;
+	}
+	
+	public List<String> getStarredRepositories() {
+		List<String> list = new ArrayList<String>();
+		for (UserRepositoryPreferences prefs : repositoryPreferences.values()) {
+			if (prefs.starred) {
+				list.add(prefs.repositoryName);
+			}
+		}
+		Collections.sort(list);
+		return list;
+	}
+}
diff --git a/src/main/java/com/gitblit/models/UserRepositoryPreferences.java b/src/main/java/com/gitblit/models/UserRepositoryPreferences.java
new file mode 100644
index 0000000..bc3a184
--- /dev/null
+++ b/src/main/java/com/gitblit/models/UserRepositoryPreferences.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2013 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit.models;
+
+import java.io.Serializable;
+
+/**
+ * User repository preferences.
+ * 
+ * @author James Moger
+ *
+ */
+public class UserRepositoryPreferences implements Serializable {
+
+	private static final long serialVersionUID = 1L;
+
+	public String username;
+	
+	public String repositoryName;
+	
+	public boolean starred;
+	
+	@Override
+	public String toString() {
+		return username + ":" + repositoryName;
+	}
+}
diff --git a/src/main/java/com/gitblit/utils/ActivityUtils.java b/src/main/java/com/gitblit/utils/ActivityUtils.java
new file mode 100644
index 0000000..c4e9587
--- /dev/null
+++ b/src/main/java/com/gitblit/utils/ActivityUtils.java
@@ -0,0 +1,233 @@
+/*
+ * Copyright 2011 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit.utils;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.lang.reflect.Type;
+import java.text.DateFormat;
+import java.text.MessageFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TimeZone;
+import java.util.TreeSet;
+
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.Repository;
+
+import com.gitblit.GitBlit;
+import com.gitblit.Keys;
+import com.gitblit.models.Activity;
+import com.gitblit.models.GravatarProfile;
+import com.gitblit.models.RefModel;
+import com.gitblit.models.RepositoryCommit;
+import com.gitblit.models.RepositoryModel;
+import com.google.gson.reflect.TypeToken;
+
+/**
+ * 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
+	 *            the number of days back from Now to collect
+	 * @param objectId
+	 *            the branch to retrieve. If this value is null or empty all
+	 *            branches are queried.
+	 * @param timezone
+	 *            the timezone for aggregating commits
+	 * @return
+	 */
+	public static List<Activity> getRecentActivity(List<RepositoryModel> models, int daysBack,
+			String objectId, TimeZone timezone) {
+
+		// Activity panel shows last daysBack of activity across all
+		// repositories.
+		Date thresholdDate = new Date(System.currentTimeMillis() - daysBack * TimeUtils.ONEDAY);
+
+		// Build a map of DailyActivity from the available repositories for the
+		// specified threshold date.
+		DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
+		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));
+		for (RepositoryModel model : models) {
+			if (!ArrayUtils.isEmpty(model.metricAuthorExclusions)) {
+				authorExclusions.addAll(model.metricAuthorExclusions);
+			}
+		}
+
+		Map<String, Activity> activity = new HashMap<String, Activity>();
+		for (RepositoryModel model : models) {
+			if (!model.isShowActivity()) {
+				// skip this repository
+				continue;
+			}
+			if (model.hasCommits && model.lastChange.after(thresholdDate)) {
+				if (model.isCollectingGarbage) {
+					continue;
+				}
+				Repository repository = GitBlit.self()
+						.getRepository(model.name);
+				List<String> branches = new ArrayList<String>();
+				if (StringUtils.isEmpty(objectId)) {
+					for (RefModel local : JGitUtils.getLocalBranches(
+							repository, true, -1)) {
+			        	if (!local.getDate().after(thresholdDate)) {
+							// branch not recently updated
+			        		continue;
+			        	}
+						branches.add(local.getName());
+					}
+				} else {
+					branches.add(objectId);
+				}
+
+				for (String branch : branches) {
+					String shortName = branch;
+					if (shortName.startsWith(Constants.R_HEADS)) {
+						shortName = shortName.substring(Constants.R_HEADS.length());
+					}
+					List<RepositoryCommit> commits = CommitCache.instance().getCommits(model.name, repository, branch, thresholdDate);
+					if (model.maxActivityCommits > 0 && commits.size() > model.maxActivityCommits) {
+						// trim commits to maximum count
+						commits = commits.subList(0,  model.maxActivityCommits);
+					}
+					for (RepositoryCommit commit : commits) {						
+						Date date = commit.getCommitDate();
+						String dateStr = df.format(date);
+						if (!activity.containsKey(dateStr)) {
+							// Normalize the date to midnight
+							cal.setTime(date);
+							cal.set(Calendar.HOUR_OF_DAY, 0);
+							cal.set(Calendar.MINUTE, 0);
+							cal.set(Calendar.SECOND, 0);
+							cal.set(Calendar.MILLISECOND, 0);
+							Activity a = new Activity(cal.getTime());
+							a.excludeAuthors(authorExclusions);
+							activity.put(dateStr, a);
+						}
+						activity.get(dateStr).addCommit(commit);
+					}
+				}
+				
+				// close the repository
+				repository.close();
+			}
+		}
+
+		List<Activity> recentActivity = new ArrayList<Activity>(activity.values());
+		return recentActivity;
+	}
+
+	/**
+	 * Returns the Gravatar profile, if available, for the specified email
+	 * address.
+	 * 
+	 * @param emailaddress
+	 * @return a Gravatar Profile
+	 * @throws IOException
+	 */
+	public static GravatarProfile getGravatarProfileFromAddress(String emailaddress)
+			throws IOException {
+		return getGravatarProfile(StringUtils.getMD5(emailaddress.toLowerCase()));
+	}
+
+	/**
+	 * Creates a Gravatar thumbnail url from the specified email address.
+	 * 
+	 * @param email
+	 *            address to query Gravatar
+	 * @param width
+	 *            size of thumbnail. if width <= 0, the default of 50 is used.
+	 * @return
+	 */
+	public static String getGravatarIdenticonUrl(String email, int width) {
+		if (width <= 0) {
+			width = 50;
+		}
+		String emailHash = StringUtils.getMD5(email);
+		String url = MessageFormat.format(
+				"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
+	 *            size of thumbnail. if width <= 0, the default of 50 is used.
+	 * @return
+	 */
+	public static String getGravatarThumbnailUrl(String email, int width) {
+		if (width <= 0) {
+			width = 50;
+		}
+		String emailHash = StringUtils.getMD5(email);
+		String url = MessageFormat.format(
+				"https://www.gravatar.com/avatar/{0}?s={1,number,0}&d=mm", emailHash, width);
+		return url;
+	}
+
+	/**
+	 * Returns the Gravatar profile, if available, for the specified hashcode.
+	 * address.
+	 * 
+	 * @param hash
+	 *            the hash of the email address
+	 * @return a Gravatar Profile
+	 * @throws IOException
+	 */
+	public static GravatarProfile getGravatarProfile(String hash) throws IOException {
+		String url = MessageFormat.format("https://www.gravatar.com/{0}.json", hash);
+		// Gravatar has a complex json structure
+		Type profileType = new TypeToken<Map<String, List<GravatarProfile>>>() {
+		}.getType();
+		Map<String, List<GravatarProfile>> profiles = null;
+		try {
+			profiles = JsonUtils.retrieveJson(url, profileType);
+		} catch (FileNotFoundException e) {
+		}
+		if (profiles == null || profiles.size() == 0) {
+			return null;
+		}
+		// due to the complex json structure we need to pull out the profile
+		// from a list 2 levels deep
+		GravatarProfile profile = profiles.values().iterator().next().get(0);
+		return profile;
+	}
+}
diff --git a/src/com/gitblit/utils/ArrayUtils.java b/src/main/java/com/gitblit/utils/ArrayUtils.java
similarity index 100%
rename from src/com/gitblit/utils/ArrayUtils.java
rename to src/main/java/com/gitblit/utils/ArrayUtils.java
diff --git a/src/com/gitblit/utils/Base64.java b/src/main/java/com/gitblit/utils/Base64.java
similarity index 100%
rename from src/com/gitblit/utils/Base64.java
rename to src/main/java/com/gitblit/utils/Base64.java
diff --git a/src/com/gitblit/utils/ByteFormat.java b/src/main/java/com/gitblit/utils/ByteFormat.java
similarity index 100%
rename from src/com/gitblit/utils/ByteFormat.java
rename to src/main/java/com/gitblit/utils/ByteFormat.java
diff --git a/src/com/gitblit/utils/ClientLogger.java b/src/main/java/com/gitblit/utils/ClientLogger.java
similarity index 100%
rename from src/com/gitblit/utils/ClientLogger.java
rename to src/main/java/com/gitblit/utils/ClientLogger.java
diff --git a/src/main/java/com/gitblit/utils/CommitCache.java b/src/main/java/com/gitblit/utils/CommitCache.java
new file mode 100644
index 0000000..e84506e
--- /dev/null
+++ b/src/main/java/com/gitblit/utils/CommitCache.java
@@ -0,0 +1,270 @@
+/*
+ * Copyright 2013 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit.utils;
+
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.gitblit.models.RefModel;
+import com.gitblit.models.RepositoryCommit;
+
+/**
+ * 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() {
+		final Calendar cal = Calendar.getInstance();
+		cal.setTimeInMillis(System.currentTimeMillis());
+		cal.set(Calendar.HOUR_OF_DAY, 0);
+		cal.set(Calendar.MINUTE, 0);
+		cal.set(Calendar.SECOND, 0);
+		cal.set(Calendar.MILLISECOND, 0);
+		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) {
+		String repoKey = repositoryName.toLowerCase();
+		ObjectCache<List<RepositoryCommit>> repoCache = cache.remove(repoKey);
+		if (repoCache != null) {
+			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
+	 */
+	public void clear(String repositoryName, String branch) {
+		String repoKey = repositoryName.toLowerCase();
+		ObjectCache<List<RepositoryCommit>> repoCache = cache.get(repoKey);
+		if (repoCache != null) {
+			List<RepositoryCommit> commits = repoCache.remove(branch.toLowerCase());
+			if (!ArrayUtils.isEmpty(commits)) {
+				logger.info(MessageFormat.format("{0}:{1} commit cache cleared", repositoryName, branch));
+			}
+		}
+	}
+	
+	/**
+	 * Get all commits for the specified repository:branch that are in the cache.
+	 * 
+	 * @param repositoryName
+	 * @param repository
+	 * @param branch
+	 * @return a list of commits
+	 */
+	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
+	 * @param sinceDate
+	 * @return a list of commits
+	 */
+	public List<RepositoryCommit> getCommits(String repositoryName, Repository repository, String branch, Date sinceDate) {
+		long start = System.nanoTime();
+		Date cacheCutoffDate = getCutoffDate();
+		List<RepositoryCommit> list;
+		if (cacheDays > 0 && (sinceDate.getTime() >= cacheCutoffDate.getTime())) {
+			// request fits within the cache window
+			String repoKey = repositoryName.toLowerCase();
+			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);
+				if (ArrayUtils.isEmpty(commits)) {
+					// we don't have any cached commits for this branch, reload
+					commits = get(repositoryName, repository, branch, cacheCutoffDate);
+					repoCache.updateObject(branchKey, tipDate, commits);
+					logger.debug(MessageFormat.format("parsed {0} commits from {1}:{2} since {3,date,yyyy-MM-dd} in {4} msecs",
+							commits.size(), repositoryName, branch, cacheCutoffDate, TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start)));
+				} else {
+					// incrementally update cache since the last cached commit
+					ObjectId sinceCommit = commits.get(0).getId();
+					List<RepositoryCommit> incremental = get(repositoryName, repository, branch, sinceCommit);
+					logger.info(MessageFormat.format("incrementally added {0} commits to cache for {1}:{2} in {3} msecs",
+							incremental.size(), repositoryName, branch, TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start)));
+					incremental.addAll(commits);
+					repoCache.updateObject(branchKey, tipDate, incremental);
+					commits = incremental;
+				}
+			} else {
+				// cache is current
+				commits = repoCache.getObject(branchKey);
+				// evict older commits outside the cache window
+				commits = reduce(commits, cacheCutoffDate);
+				// update cache
+				repoCache.updateObject(branchKey, tipDate, commits);
+			}
+			
+			if (sinceDate.equals(cacheCutoffDate)) {
+				list = commits;
+			} else {
+				// reduce the commits to those since the specified date
+				list = reduce(commits, sinceDate);
+			}
+			logger.debug(MessageFormat.format("retrieved {0} commits from cache of {1}:{2} since {3,date,yyyy-MM-dd} in {4} msecs",
+					list.size(), repositoryName, branch, sinceDate, TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start)));
+		} else {
+			// not caching or request outside cache window
+			list = get(repositoryName, repository, branch, sinceDate);
+			logger.debug(MessageFormat.format("parsed {0} commits from {1}:{2} since {3,date,yyyy-MM-dd} in {4} msecs",
+					list.size(), repositoryName, branch, sinceDate, TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start)));
+		}
+		return list;
+	}
+	
+	/**
+	 * Returns a list of commits for the specified repository branch. 
+	 * 
+	 * @param repositoryName
+	 * @param repository
+	 * @param branch
+	 * @param sinceDate
+	 * @return a list of commits
+	 */
+	protected List<RepositoryCommit> get(String repositoryName, Repository repository, String branch, Date sinceDate) {
+		Map<ObjectId, List<RefModel>> allRefs = JGitUtils.getAllRefs(repository, false);
+		List<RepositoryCommit> commits = new ArrayList<RepositoryCommit>();
+		for (RevCommit commit : JGitUtils.getRevLog(repository, branch, sinceDate)) {
+			RepositoryCommit commitModel = new RepositoryCommit(repositoryName, branch, commit);
+			commitModel.setRefs(allRefs.get(commitModel.getName()));
+			commits.add(commitModel);
+		}
+		return commits;
+	}
+	
+	/**
+	 * Returns a list of commits for the specified repository branch since the specified commit. 
+	 * 
+	 * @param repositoryName
+	 * @param repository
+	 * @param branch
+	 * @param sinceCommit
+	 * @return a list of commits
+	 */
+	protected List<RepositoryCommit> get(String repositoryName, Repository repository, String branch, ObjectId sinceCommit) {
+		Map<ObjectId, List<RefModel>> allRefs = JGitUtils.getAllRefs(repository, false);
+		List<RepositoryCommit> commits = new ArrayList<RepositoryCommit>();
+		for (RevCommit commit : JGitUtils.getRevLog(repository, sinceCommit.getName(), branch)) {
+			RepositoryCommit commitModel = new RepositoryCommit(repositoryName, branch, commit);
+			commitModel.setRefs(allRefs.get(commitModel.getName()));
+			commits.add(commitModel);
+		}
+		return commits;
+	}
+	
+	/**
+	 * Reduces the list of commits to those since the specified date.
+	 * 
+	 * @param commits
+	 * @param sinceDate
+	 * @return  a list of commits
+	 */
+	protected List<RepositoryCommit> reduce(List<RepositoryCommit> commits, Date sinceDate) {
+		List<RepositoryCommit> filtered = new ArrayList<RepositoryCommit>();
+		for (RepositoryCommit commit : commits) {
+			if (commit.getCommitDate().compareTo(sinceDate) >= 0) {
+				filtered.add(commit);
+			}
+		}
+		return filtered;
+	}
+}
diff --git a/src/com/gitblit/utils/CompressionUtils.java b/src/main/java/com/gitblit/utils/CompressionUtils.java
similarity index 100%
rename from src/com/gitblit/utils/CompressionUtils.java
rename to src/main/java/com/gitblit/utils/CompressionUtils.java
diff --git a/src/main/java/com/gitblit/utils/ConnectionUtils.java b/src/main/java/com/gitblit/utils/ConnectionUtils.java
new file mode 100644
index 0000000..feeedd2
--- /dev/null
+++ b/src/main/java/com/gitblit/utils/ConnectionUtils.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright 2011 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit.utils;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.UnknownHostException;
+import java.security.GeneralSecurityException;
+import java.security.SecureRandom;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+
+import javax.net.SocketFactory;
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.SSLSocketFactory;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
+
+
+/**
+ * Utility class for establishing HTTP/HTTPS connections.
+ * 
+ * @author James Moger
+ * 
+ */
+public class ConnectionUtils {
+
+	static final String CHARSET;
+
+	private static final SSLContext SSL_CONTEXT;
+
+	private static final DummyHostnameVerifier HOSTNAME_VERIFIER;
+
+	static {
+		SSLContext context = null;
+		try {
+			context = SSLContext.getInstance("SSL");
+			context.init(null, new TrustManager[] { new DummyTrustManager() }, new SecureRandom());
+		} catch (Throwable t) {
+			t.printStackTrace();
+		}
+		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");
+	}
+
+	public static void setAuthorization(URLConnection conn, String username, char[] password) {
+		if (!StringUtils.isEmpty(username) && (password != null && password.length > 0)) {
+			conn.setRequestProperty(
+					"Authorization",
+					"Basic "
+							+ Base64.encodeBytes((username + ":" + new String(password)).getBytes()));
+		}
+	}
+
+	public static URLConnection openReadConnection(String url, String username, char[] password)
+			throws IOException {
+		URLConnection conn = openConnection(url, username, password);
+		conn.setRequestProperty("Accept-Charset", ConnectionUtils.CHARSET);
+		return conn;
+	}
+
+	public static URLConnection openConnection(String url, String username, char[] password)
+			throws IOException {
+		URL urlObject = new URL(url);
+		URLConnection conn = urlObject.openConnection();
+		setAuthorization(conn, username, password);
+		conn.setUseCaches(false);
+		conn.setDoOutput(true);
+		if (conn instanceof HttpsURLConnection) {
+			HttpsURLConnection secureConn = (HttpsURLConnection) conn;
+			secureConn.setSSLSocketFactory(SSL_CONTEXT.getSocketFactory());
+			secureConn.setHostnameVerifier(HOSTNAME_VERIFIER);
+		}
+		return conn;
+	}
+		
+	// Copyright (C) 2009 The Android Open Source Project
+	//
+	// 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.
+	public static class BlindSSLSocketFactory extends SSLSocketFactory {
+		private static final BlindSSLSocketFactory INSTANCE;
+
+		static {
+			try {
+				final SSLContext context = SSLContext.getInstance("SSL");
+				final TrustManager[] trustManagers = { new DummyTrustManager() };
+				final SecureRandom rng = new SecureRandom();
+				context.init(null, trustManagers, rng);
+				INSTANCE = new BlindSSLSocketFactory(context.getSocketFactory());
+			} catch (GeneralSecurityException e) {
+				throw new RuntimeException("Cannot create BlindSslSocketFactory", e);
+			}
+		}
+
+		public static SocketFactory getDefault() {
+			return INSTANCE;
+		}
+
+		private final SSLSocketFactory sslFactory;
+
+		private BlindSSLSocketFactory(final SSLSocketFactory sslFactory) {
+			this.sslFactory = sslFactory;
+		}
+
+		@Override
+		public Socket createSocket(Socket s, String host, int port, boolean autoClose)
+				throws IOException {
+			return sslFactory.createSocket(s, host, port, autoClose);
+		}
+
+		@Override
+		public String[] getDefaultCipherSuites() {
+			return sslFactory.getDefaultCipherSuites();
+		}
+
+		@Override
+		public String[] getSupportedCipherSuites() {
+			return sslFactory.getSupportedCipherSuites();
+		}
+
+		@Override
+		public Socket createSocket() throws IOException {
+			return sslFactory.createSocket();
+		}
+
+		@Override
+		public Socket createSocket(String host, int port) throws IOException,
+		UnknownHostException {
+			return sslFactory.createSocket(host, port);
+		}
+
+		@Override
+		public Socket createSocket(InetAddress host, int port) throws IOException {
+			return sslFactory.createSocket(host, port);
+		}
+
+		@Override
+		public Socket createSocket(String host, int port, InetAddress localHost,
+				int localPort) throws IOException, UnknownHostException {
+			return sslFactory.createSocket(host, port, localHost, localPort);
+		}
+
+		@Override
+		public Socket createSocket(InetAddress address, int port,
+				InetAddress localAddress, int localPort) throws IOException {
+			return sslFactory.createSocket(address, port, localAddress, localPort);
+		}
+	}
+
+	/**
+	 * DummyTrustManager trusts all certificates.
+	 * 
+	 * @author James Moger
+	 */
+	private static class DummyTrustManager implements X509TrustManager {
+
+		@Override
+		public void checkClientTrusted(X509Certificate[] certs, String authType)
+				throws CertificateException {
+		}
+
+		@Override
+		public void checkServerTrusted(X509Certificate[] certs, String authType)
+				throws CertificateException {
+		}
+
+		@Override
+		public X509Certificate[] getAcceptedIssuers() {
+			return null;
+		}
+	}
+
+	/**
+	 * Trusts all hostnames from a certificate, including self-signed certs.
+	 * 
+	 * @author James Moger
+	 */
+	private static class DummyHostnameVerifier implements HostnameVerifier {
+		@Override
+		public boolean verify(String hostname, SSLSession session) {
+			return true;
+		}
+	}
+}
diff --git a/src/com/gitblit/utils/ContainerUtils.java b/src/main/java/com/gitblit/utils/ContainerUtils.java
old mode 100755
new mode 100644
similarity index 100%
rename from src/com/gitblit/utils/ContainerUtils.java
rename to src/main/java/com/gitblit/utils/ContainerUtils.java
diff --git a/src/com/gitblit/utils/DeepCopier.java b/src/main/java/com/gitblit/utils/DeepCopier.java
similarity index 100%
rename from src/com/gitblit/utils/DeepCopier.java
rename to src/main/java/com/gitblit/utils/DeepCopier.java
diff --git a/src/main/java/com/gitblit/utils/DiffUtils.java b/src/main/java/com/gitblit/utils/DiffUtils.java
new file mode 100644
index 0000000..67871d2
--- /dev/null
+++ b/src/main/java/com/gitblit/utils/DiffUtils.java
@@ -0,0 +1,282 @@
+/*
+ * Copyright 2011 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit.utils;
+
+import java.io.ByteArrayOutputStream;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.jgit.api.BlameCommand;
+import org.eclipse.jgit.blame.BlameResult;
+import org.eclipse.jgit.diff.DiffEntry;
+import org.eclipse.jgit.diff.DiffFormatter;
+import org.eclipse.jgit.diff.RawText;
+import org.eclipse.jgit.diff.RawTextComparator;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevTree;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.gitblit.models.AnnotatedLine;
+
+/**
+ * DiffUtils is a class of utility methods related to diff, patch, and blame.
+ * 
+ * The diff methods support pluggable diff output types like Gitblit, Gitweb,
+ * and Plain.
+ * 
+ * @author James Moger
+ * 
+ */
+public class DiffUtils {
+
+	private static final Logger LOGGER = LoggerFactory.getLogger(DiffUtils.class);
+
+	/**
+	 * Enumeration for the diff output types.
+	 */
+	public static enum DiffOutputType {
+		PLAIN, GITWEB, GITBLIT;
+
+		public static DiffOutputType forName(String name) {
+			for (DiffOutputType type : values()) {
+				if (type.name().equalsIgnoreCase(name)) {
+					return type;
+				}
+			}
+			return null;
+		}
+	}
+
+	/**
+	 * Returns the complete diff of the specified commit compared to its primary
+	 * parent.
+	 * 
+	 * @param repository
+	 * @param commit
+	 * @param outputType
+	 * @return the diff as a string
+	 */
+	public static String getCommitDiff(Repository repository, RevCommit commit,
+			DiffOutputType outputType) {
+		return getDiff(repository, null, commit, null, outputType);
+	}
+
+	/**
+	 * Returns the diff for the specified file or folder from the specified
+	 * commit compared to its primary parent.
+	 * 
+	 * @param repository
+	 * @param commit
+	 * @param path
+	 * @param outputType
+	 * @return the diff as a string
+	 */
+	public static String getDiff(Repository repository, RevCommit commit, String path,
+			DiffOutputType outputType) {
+		return getDiff(repository, null, commit, path, outputType);
+	}
+
+	/**
+	 * Returns the complete diff between the two specified commits.
+	 * 
+	 * @param repository
+	 * @param baseCommit
+	 * @param commit
+	 * @param outputType
+	 * @return the diff as a string
+	 */
+	public static String getDiff(Repository repository, RevCommit baseCommit, RevCommit commit,
+			DiffOutputType outputType) {
+		return getDiff(repository, baseCommit, commit, null, outputType);
+	}
+
+	/**
+	 * Returns the diff between two commits for the specified file.
+	 * 
+	 * @param repository
+	 * @param baseCommit
+	 *            if base commit is null the diff is to the primary parent of
+	 *            the commit.
+	 * @param commit
+	 * @param path
+	 *            if the path is specified, the diff is restricted to that file
+	 *            or folder. if unspecified, the diff is for the entire commit.
+	 * @param outputType
+	 * @return the diff as a string
+	 */
+	public static String getDiff(Repository repository, RevCommit baseCommit, RevCommit commit,
+			String path, DiffOutputType outputType) {
+		String diff = null;
+		try {
+			final ByteArrayOutputStream os = new ByteArrayOutputStream();
+			RawTextComparator cmp = RawTextComparator.DEFAULT;
+			DiffFormatter df;
+			switch (outputType) {
+			case GITWEB:
+				df = new GitWebDiffFormatter(os);
+				break;
+			case GITBLIT:
+				df = new GitBlitDiffFormatter(os);
+				break;
+			case PLAIN:
+			default:
+				df = new DiffFormatter(os);
+				break;
+			}
+			df.setRepository(repository);
+			df.setDiffComparator(cmp);
+			df.setDetectRenames(true);
+
+			RevTree commitTree = commit.getTree();
+			RevTree baseTree;
+			if (baseCommit == null) {
+				if (commit.getParentCount() > 0) {
+					final RevWalk rw = new RevWalk(repository);
+					RevCommit parent = rw.parseCommit(commit.getParent(0).getId());
+					rw.dispose();
+					baseTree = parent.getTree();
+				} else {
+					// FIXME initial commit. no parent?!
+					baseTree = commitTree;
+				}
+			} else {
+				baseTree = baseCommit.getTree();
+			}
+
+			List<DiffEntry> diffEntries = df.scan(baseTree, commitTree);
+			if (path != null && path.length() > 0) {
+				for (DiffEntry diffEntry : diffEntries) {
+					if (diffEntry.getNewPath().equalsIgnoreCase(path)) {
+						df.format(diffEntry);
+						break;
+					}
+				}
+			} else {
+				df.format(diffEntries);
+			}
+			if (df instanceof GitWebDiffFormatter) {
+				// workaround for complex private methods in DiffFormatter
+				diff = ((GitWebDiffFormatter) df).getHtml();
+			} else {
+				diff = os.toString();
+			}
+			df.flush();
+		} catch (Throwable t) {
+			LOGGER.error("failed to generate commit diff!", t);
+		}
+		return diff;
+	}
+
+	/**
+	 * Returns the diff between the two commits for the specified file or folder
+	 * formatted as a patch.
+	 * 
+	 * @param repository
+	 * @param baseCommit
+	 *            if base commit is unspecified, the patch is generated against
+	 *            the primary parent of the specified commit.
+	 * @param commit
+	 * @param path
+	 *            if path is specified, the patch is generated only for the
+	 *            specified file or folder. if unspecified, the patch is
+	 *            generated for the entire diff between the two commits.
+	 * @return patch as a string
+	 */
+	public static String getCommitPatch(Repository repository, RevCommit baseCommit,
+			RevCommit commit, String path) {
+		String diff = null;
+		try {
+			final ByteArrayOutputStream os = new ByteArrayOutputStream();
+			RawTextComparator cmp = RawTextComparator.DEFAULT;
+			PatchFormatter df = new PatchFormatter(os);
+			df.setRepository(repository);
+			df.setDiffComparator(cmp);
+			df.setDetectRenames(true);
+
+			RevTree commitTree = commit.getTree();
+			RevTree baseTree;
+			if (baseCommit == null) {
+				if (commit.getParentCount() > 0) {
+					final RevWalk rw = new RevWalk(repository);
+					RevCommit parent = rw.parseCommit(commit.getParent(0).getId());
+					baseTree = parent.getTree();
+				} else {
+					// FIXME initial commit. no parent?!
+					baseTree = commitTree;
+				}
+			} else {
+				baseTree = baseCommit.getTree();
+			}
+
+			List<DiffEntry> diffEntries = df.scan(baseTree, commitTree);
+			if (path != null && path.length() > 0) {
+				for (DiffEntry diffEntry : diffEntries) {
+					if (diffEntry.getNewPath().equalsIgnoreCase(path)) {
+						df.format(diffEntry);
+						break;
+					}
+				}
+			} else {
+				df.format(diffEntries);
+			}
+			diff = df.getPatch(commit);
+			df.flush();
+		} catch (Throwable t) {
+			LOGGER.error("failed to generate commit diff!", t);
+		}
+		return diff;
+	}
+
+	/**
+	 * Returns the list of lines in the specified source file annotated with the
+	 * source commit metadata.
+	 * 
+	 * @param repository
+	 * @param blobPath
+	 * @param objectId
+	 * @return list of annotated lines
+	 */
+	public static List<AnnotatedLine> blame(Repository repository, String blobPath, String objectId) {
+		List<AnnotatedLine> lines = new ArrayList<AnnotatedLine>();
+		try {
+			ObjectId object;
+			if (StringUtils.isEmpty(objectId)) {
+				object = JGitUtils.getDefaultBranch(repository);
+			} else {
+				object = repository.resolve(objectId);
+			}
+			BlameCommand blameCommand = new BlameCommand(repository);
+			blameCommand.setFilePath(blobPath);
+			blameCommand.setStartCommit(object);
+			BlameResult blameResult = blameCommand.call();
+			RawText rawText = blameResult.getResultContents();
+			int length = rawText.size();
+			for (int i = 0; i < length; i++) {
+				RevCommit commit = blameResult.getSourceCommit(i);
+				AnnotatedLine line = new AnnotatedLine(commit, i + 1, rawText.getString(i));
+				lines.add(line);
+			}
+		} catch (Throwable t) {
+			LOGGER.error(MessageFormat.format("failed to generate blame for {0} {1}!", blobPath, objectId), t);
+		}
+		return lines;
+	}
+}
diff --git a/src/com/gitblit/utils/FederationUtils.java b/src/main/java/com/gitblit/utils/FederationUtils.java
similarity index 100%
rename from src/com/gitblit/utils/FederationUtils.java
rename to src/main/java/com/gitblit/utils/FederationUtils.java
diff --git a/src/com/gitblit/utils/FileUtils.java b/src/main/java/com/gitblit/utils/FileUtils.java
similarity index 100%
rename from src/com/gitblit/utils/FileUtils.java
rename to src/main/java/com/gitblit/utils/FileUtils.java
diff --git a/src/main/java/com/gitblit/utils/GitBlitDiffFormatter.java b/src/main/java/com/gitblit/utils/GitBlitDiffFormatter.java
new file mode 100644
index 0000000..62966de
--- /dev/null
+++ b/src/main/java/com/gitblit/utils/GitBlitDiffFormatter.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright 2011 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit.utils;
+
+import static org.eclipse.jgit.lib.Constants.encode;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.text.MessageFormat;
+
+import org.eclipse.jgit.diff.RawText;
+import org.eclipse.jgit.util.RawParseUtils;
+
+/**
+ * Generates an html snippet of a diff in Gitblit's style.
+ * 
+ * @author James Moger
+ * 
+ */
+public class GitBlitDiffFormatter extends GitWebDiffFormatter {
+
+	private final OutputStream os;
+
+	private int left, right;
+
+	public GitBlitDiffFormatter(OutputStream os) {
+		super(os);
+		this.os = os;
+	}
+
+	/**
+	 * Output a hunk header
+	 * 
+	 * @param aStartLine
+	 *            within first source
+	 * @param aEndLine
+	 *            within first source
+	 * @param bStartLine
+	 *            within second source
+	 * @param bEndLine
+	 *            within second source
+	 * @throws IOException
+	 */
+	@Override
+	protected void writeHunkHeader(int aStartLine, int aEndLine, int bStartLine, int bEndLine)
+			throws IOException {
+		os.write("<tr><th>..</th><th>..</th><td class='hunk_header'>".getBytes());
+		os.write('@');
+		os.write('@');
+		writeRange('-', aStartLine + 1, aEndLine - aStartLine);
+		writeRange('+', bStartLine + 1, bEndLine - bStartLine);
+		os.write(' ');
+		os.write('@');
+		os.write('@');
+		os.write("</td></tr>\n".getBytes());
+		left = aStartLine + 1;
+		right = bStartLine + 1;
+	}
+
+	@Override
+	protected void writeLine(final char prefix, final RawText text, final int cur)
+			throws IOException {
+		os.write("<tr>".getBytes());
+		switch (prefix) {
+		case '+':
+			os.write(("<th></th><th>" + (right++) + "</th>").getBytes());
+			os.write("<td><div class=\"diff add2\">".getBytes());
+			break;
+		case '-':
+			os.write(("<th>" + (left++) + "</th><th></th>").getBytes());
+			os.write("<td><div class=\"diff remove2\">".getBytes());
+			break;
+		default:
+			os.write(("<th>" + (left++) + "</th><th>" + (right++) + "</th>").getBytes());
+			os.write("<td>".getBytes());
+			break;
+		}
+		os.write(prefix);
+		String line = text.getString(cur);
+		line = StringUtils.escapeForHtml(line, false);
+		os.write(encode(line));
+		switch (prefix) {
+		case '+':
+		case '-':
+			os.write("</div>".getBytes());
+			break;
+		default:
+			os.write("</td>".getBytes());
+		}
+		os.write("</tr>\n".getBytes());
+	}
+
+	/**
+	 * Workaround function for complex private methods in DiffFormatter. This
+	 * sets the html for the diff headers.
+	 * 
+	 * @return
+	 */
+	@Override
+	public String getHtml() {
+		ByteArrayOutputStream bos = (ByteArrayOutputStream) os;
+		String html = RawParseUtils.decode(bos.toByteArray());
+		String[] lines = html.split("\n");
+		StringBuilder sb = new StringBuilder();
+		boolean inFile = false;
+		String oldnull = "a/dev/null";
+		for (String line : lines) {
+			if (line.startsWith("index")) {
+				// skip index lines
+			} else if (line.startsWith("new file")) {
+				// skip new file lines
+			} else if (line.startsWith("\\ No newline")) {
+				// skip no new line
+			} else if (line.startsWith("---") || line.startsWith("+++")) {
+				// skip --- +++ lines
+			} else if (line.startsWith("diff")) {
+				line = StringUtils.convertOctal(line);
+				if (line.indexOf(oldnull) > -1) {
+					// a is null, use b
+					line = line.substring(("diff --git " + oldnull).length()).trim();
+					// trim b/
+					line = line.substring(2).trim();
+				} else {
+					// use a
+					line = line.substring("diff --git ".length()).trim();
+					line = line.substring(line.startsWith("\"a/") ? 3 : 2);					
+					line = line.substring(0, line.indexOf(" b/") > -1 ? line.indexOf(" b/") : line.indexOf("\"b/")).trim();
+				}
+				
+				if (line.charAt(0) == '"') {
+					line = line.substring(1);
+				}
+				if (line.charAt(line.length() - 1) == '"') {
+					line = line.substring(0, line.length() - 1);
+				}
+				if (inFile) {
+					sb.append("</tbody></table></div>\n");
+					inFile = false;
+				}
+				
+				sb.append(MessageFormat.format("<div class='header'><div class=\"diffHeader\" id=\"{0}\"><i class=\"icon-file\"></i> ", line)).append(line).append("</div></div>");
+				sb.append("<div class=\"diff\">");
+				sb.append("<table><tbody>");
+				inFile = true;
+			} else {
+				boolean gitLinkDiff = line.length() > 0 && line.substring(1).startsWith("Subproject commit");
+				if (gitLinkDiff) {
+					sb.append("<tr><th></th><th></th>");
+					if (line.charAt(0) == '+') {
+						sb.append("<td><div class=\"diff add2\">");
+					} else {
+						sb.append("<td><div class=\"diff remove2\">");
+					}
+				}
+				sb.append(line);
+				if (gitLinkDiff) {
+					sb.append("</div></td></tr>");
+				}
+			}
+		}
+		sb.append("</table></div>");
+		return sb.toString();
+	}
+}
diff --git a/src/com/gitblit/utils/GitWebDiffFormatter.java b/src/main/java/com/gitblit/utils/GitWebDiffFormatter.java
similarity index 100%
rename from src/com/gitblit/utils/GitWebDiffFormatter.java
rename to src/main/java/com/gitblit/utils/GitWebDiffFormatter.java
diff --git a/src/com/gitblit/utils/HttpUtils.java b/src/main/java/com/gitblit/utils/HttpUtils.java
similarity index 100%
rename from src/com/gitblit/utils/HttpUtils.java
rename to src/main/java/com/gitblit/utils/HttpUtils.java
diff --git a/src/com/gitblit/utils/IssueUtils.java b/src/main/java/com/gitblit/utils/IssueUtils.java
similarity index 100%
rename from src/com/gitblit/utils/IssueUtils.java
rename to src/main/java/com/gitblit/utils/IssueUtils.java
diff --git a/src/main/java/com/gitblit/utils/JGitUtils.java b/src/main/java/com/gitblit/utils/JGitUtils.java
new file mode 100644
index 0000000..8676d74
--- /dev/null
+++ b/src/main/java/com/gitblit/utils/JGitUtils.java
@@ -0,0 +1,1917 @@
+/*
+ * Copyright 2011 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit.utils;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.text.DecimalFormat;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.regex.Pattern;
+
+import org.eclipse.jgit.api.CloneCommand;
+import org.eclipse.jgit.api.FetchCommand;
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.api.TagCommand;
+import org.eclipse.jgit.api.errors.GitAPIException;
+import org.eclipse.jgit.diff.DiffEntry;
+import org.eclipse.jgit.diff.DiffEntry.ChangeType;
+import org.eclipse.jgit.diff.DiffFormatter;
+import org.eclipse.jgit.diff.RawTextComparator;
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.errors.StopWalkException;
+import org.eclipse.jgit.lib.BlobBasedConfig;
+import org.eclipse.jgit.lib.CommitBuilder;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.FileMode;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectInserter;
+import org.eclipse.jgit.lib.ObjectLoader;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.RefUpdate;
+import org.eclipse.jgit.lib.RefUpdate.Result;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.RepositoryCache.FileKey;
+import org.eclipse.jgit.lib.TreeFormatter;
+import org.eclipse.jgit.revwalk.RevBlob;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevObject;
+import org.eclipse.jgit.revwalk.RevSort;
+import org.eclipse.jgit.revwalk.RevTree;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.revwalk.filter.CommitTimeRevFilter;
+import org.eclipse.jgit.revwalk.filter.RevFilter;
+import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
+import org.eclipse.jgit.transport.CredentialsProvider;
+import org.eclipse.jgit.transport.FetchResult;
+import org.eclipse.jgit.transport.RefSpec;
+import org.eclipse.jgit.treewalk.TreeWalk;
+import org.eclipse.jgit.treewalk.filter.AndTreeFilter;
+import org.eclipse.jgit.treewalk.filter.OrTreeFilter;
+import org.eclipse.jgit.treewalk.filter.PathFilter;
+import org.eclipse.jgit.treewalk.filter.PathFilterGroup;
+import org.eclipse.jgit.treewalk.filter.PathSuffixFilter;
+import org.eclipse.jgit.treewalk.filter.TreeFilter;
+import org.eclipse.jgit.util.FS;
+import org.eclipse.jgit.util.io.DisabledOutputStream;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.gitblit.models.GitNote;
+import com.gitblit.models.PathModel;
+import com.gitblit.models.PathModel.PathChangeModel;
+import com.gitblit.models.RefModel;
+import com.gitblit.models.SubmoduleModel;
+
+/**
+ * Collection of static methods for retrieving information from a repository.
+ * 
+ * @author James Moger
+ * 
+ */
+public class JGitUtils {
+
+	static final Logger LOGGER = LoggerFactory.getLogger(JGitUtils.class);
+
+	/**
+	 * Log an error message and exception.
+	 * 
+	 * @param t
+	 * @param repository
+	 *            if repository is not null it MUST be the {0} parameter in the
+	 *            pattern.
+	 * @param pattern
+	 * @param objects
+	 */
+	private static void error(Throwable t, Repository repository, String pattern, Object... objects) {
+		List<Object> parameters = new ArrayList<Object>();
+		if (objects != null && objects.length > 0) {
+			for (Object o : objects) {
+				parameters.add(o);
+			}
+		}
+		if (repository != null) {
+			parameters.add(0, repository.getDirectory().getAbsolutePath());
+		}
+		LOGGER.error(MessageFormat.format(pattern, parameters.toArray()), t);
+	}
+
+	/**
+	 * 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"
+	 */
+	public static String getDisplayName(PersonIdent person) {
+		if (StringUtils.isEmpty(person.getEmailAddress())) {
+			return person.getName();
+		}
+		final StringBuilder r = new StringBuilder();
+		r.append(person.getName());
+		r.append(" <");
+		r.append(person.getEmailAddress());
+		r.append('>');
+		return r.toString().trim();
+	}
+
+	/**
+	 * Encapsulates the result of cloning or pulling from a repository.
+	 */
+	public static class CloneResult {
+		public String name;
+		public FetchResult fetchResult;
+		public boolean createdRepository;
+	}
+
+	/**
+	 * 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
+	 * @return CloneResult
+	 * @throws Exception
+	 */
+	public static CloneResult cloneRepository(File repositoriesFolder, String name, String fromUrl)
+			throws Exception {
+		return cloneRepository(repositoriesFolder, name, fromUrl, true, null);
+	}
+
+	/**
+	 * 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
+	 * @param bare
+	 * @param credentialsProvider
+	 * @return CloneResult
+	 * @throws Exception
+	 */
+	public static CloneResult cloneRepository(File repositoriesFolder, String name, String fromUrl,
+			boolean bare, CredentialsProvider credentialsProvider) throws Exception {
+		CloneResult result = new CloneResult();
+		if (bare) {
+			// bare repository, ensure .git suffix
+			if (!name.toLowerCase().endsWith(Constants.DOT_GIT_EXT)) {
+				name += Constants.DOT_GIT_EXT;
+			}
+		} else {
+			// normal repository, strip .git suffix
+			if (name.toLowerCase().endsWith(Constants.DOT_GIT_EXT)) {
+				name = name.substring(0, name.indexOf(Constants.DOT_GIT_EXT));
+			}
+		}
+		result.name = name;
+
+		File folder = new File(repositoriesFolder, name);
+		if (folder.exists()) {
+			File gitDir = FileKey.resolve(new File(repositoriesFolder, name), FS.DETECTED);
+			Repository repository = new FileRepositoryBuilder().setGitDir(gitDir).build();
+			result.fetchResult = fetchRepository(credentialsProvider, repository);
+			repository.close();
+		} else {
+			CloneCommand clone = new CloneCommand();
+			clone.setBare(bare);
+			clone.setCloneAllBranches(true);
+			clone.setURI(fromUrl);
+			clone.setDirectory(folder);
+			if (credentialsProvider != null) {
+				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;
+			result.fetchResult = fetchRepository(credentialsProvider, repository);
+			repository.close();
+		}
+		return result;
+	}
+
+	/**
+	 * Fetch updates from the remote repository. If refSpecs is unspecifed,
+	 * remote heads, tags, and notes are retrieved.
+	 * 
+	 * @param credentialsProvider
+	 * @param repository
+	 * @param refSpecs
+	 * @return FetchResult
+	 * @throws Exception
+	 */
+	public static FetchResult fetchRepository(CredentialsProvider credentialsProvider,
+			Repository repository, RefSpec... refSpecs) throws Exception {
+		Git git = new Git(repository);
+		FetchCommand fetch = git.fetch();
+		List<RefSpec> specs = new ArrayList<RefSpec>();
+		if (refSpecs == null || refSpecs.length == 0) {
+			specs.add(new RefSpec("+refs/heads/*:refs/remotes/origin/*"));
+			specs.add(new RefSpec("+refs/tags/*:refs/tags/*"));
+			specs.add(new RefSpec("+refs/notes/*:refs/notes/*"));
+		} else {
+			specs.addAll(Arrays.asList(refSpecs));
+		}
+		if (credentialsProvider != null) {
+			fetch.setCredentialsProvider(credentialsProvider);
+		}
+		fetch.setRefSpecs(specs);
+		FetchResult fetchRes = fetch.call();
+		return fetchRes;
+	}
+
+	/**
+	 * Creates a bare repository.
+	 * 
+	 * @param repositoriesFolder
+	 * @param name
+	 * @return Repository
+	 */
+	public static Repository createRepository(File repositoriesFolder, String name) {
+		try {
+			Git git = Git.init().setDirectory(new File(repositoriesFolder, name)).setBare(true).call();
+			return git.getRepository();
+		} catch (GitAPIException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+	/**
+	 * Returns a list of repository names in the specified folder.
+	 * 
+	 * @param repositoriesFolder
+	 * @param onlyBare
+	 *            if true, only bare repositories repositories are listed. If
+	 *            false all repositories are included.
+	 * @param searchSubfolders
+	 *            recurse into subfolders to find grouped repositories
+	 * @param depth
+	 *            optional recursion depth, -1 = infinite recursion
+	 * @param exclusions
+	 *            list of regex exclusions for matching to folder names
+	 * @return list of repository names
+	 */
+	public static List<String> getRepositoryList(File repositoriesFolder, boolean onlyBare,
+			boolean searchSubfolders, int depth, List<String> exclusions) {
+		List<String> list = new ArrayList<String>();
+		if (repositoriesFolder == null || !repositoriesFolder.exists()) {
+			return list;
+		}
+		List<Pattern> patterns = new ArrayList<Pattern>();
+		if (!ArrayUtils.isEmpty(exclusions)) {
+			for (String regex : exclusions) {
+				patterns.add(Pattern.compile(regex));
+			}
+		}
+		list.addAll(getRepositoryList(repositoriesFolder.getAbsolutePath(), repositoriesFolder,
+				onlyBare, searchSubfolders, depth, patterns));
+		StringUtils.sortRepositorynames(list);
+		list.remove(".git"); // issue-256
+		return list;
+	}
+
+	/**
+	 * Recursive function to find git repositories.
+	 * 
+	 * @param basePath
+	 *            basePath is stripped from the repository name as repositories
+	 *            are relative to this path
+	 * @param searchFolder
+	 * @param onlyBare
+	 *            if true only bare repositories will be listed. if false all
+	 *            repositories are included.
+	 * @param searchSubfolders
+	 *            recurse into subfolders to find grouped repositories
+	 * @param depth
+	 *            recursion depth, -1 = infinite recursion
+	 * @param patterns
+	 *            list of regex patterns for matching to folder names
+	 * @return
+	 */
+	private static List<String> getRepositoryList(String basePath, File searchFolder,
+			boolean onlyBare, boolean searchSubfolders, int depth, List<Pattern> patterns) {
+		File baseFile = new File(basePath);
+		List<String> list = new ArrayList<String>();
+		if (depth == 0) {
+			return list;
+		}
+		
+		int nextDepth = (depth == -1) ? -1 : depth - 1;
+		for (File file : searchFolder.listFiles()) {
+			if (file.isDirectory()) {
+				boolean exclude = false;
+				for (Pattern pattern : patterns) {
+					String path = FileUtils.getRelativePath(baseFile, file).replace('\\',  '/');
+					if (pattern.matcher(path).matches()) {
+						LOGGER.debug(MessageFormat.format("excluding {0} because of rule {1}", path, pattern.pattern()));
+						exclude = true;
+						break;
+					}
+				}
+				if (exclude) {
+					// skip to next file
+					continue;
+				}
+
+				File gitDir = FileKey.resolve(new File(searchFolder, file.getName()), FS.DETECTED);
+				if (gitDir != null) {
+					if (onlyBare && gitDir.getName().equals(".git")) {
+						continue;
+					}
+					if (gitDir.equals(file) || gitDir.getParentFile().equals(file)) {
+						// determine repository name relative to base path
+						String repository = FileUtils.getRelativePath(baseFile, file);
+						list.add(repository);
+					} else if (searchSubfolders && file.canRead()) {
+						// look for repositories in subfolders
+						list.addAll(getRepositoryList(basePath, file, onlyBare, searchSubfolders,
+								nextDepth, patterns));
+					}
+				} else if (searchSubfolders && file.canRead()) {
+					// look for repositories in subfolders
+					list.addAll(getRepositoryList(basePath, file, onlyBare, searchSubfolders,
+							nextDepth, patterns));
+				}
+			}
+		}
+		return list;
+	}
+
+	/**
+	 * 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.
+	 * @return RevCommit
+	 */
+	public static RevCommit getFirstCommit(Repository repository, String branch) {
+		if (!hasCommits(repository)) {
+			return null;
+		}
+		RevCommit commit = null;
+		try {
+			// resolve branch
+			ObjectId branchObject;
+			if (StringUtils.isEmpty(branch)) {
+				branchObject = getDefaultBranch(repository);
+			} else {
+				branchObject = repository.resolve(branch);
+			}
+
+			RevWalk walk = new RevWalk(repository);
+			walk.sort(RevSort.REVERSE);
+			RevCommit head = walk.parseCommit(branchObject);
+			walk.markStart(head);
+			commit = walk.next();
+			walk.dispose();
+		} catch (Throwable t) {
+			error(t, repository, "{0} failed to determine first commit");
+		}
+		return commit;
+	}
+
+	/**
+	 * 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.
+	 * @return Date of the first commit on a branch
+	 */
+	public static Date getFirstChange(Repository repository, String branch) {
+		RevCommit commit = getFirstCommit(repository, branch);
+		if (commit == null) {
+			if (repository == null || !repository.getDirectory().exists()) {
+				return new Date(0);
+			}
+			// fresh repository
+			return new Date(repository.getDirectory().lastModified());
+		}
+		return getCommitDate(commit);
+	}
+
+	/**
+	 * 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
+	 */
+	public static boolean hasCommits(Repository repository) {
+		if (repository != null && repository.getDirectory().exists()) {
+			return (new File(repository.getDirectory(), "objects").list().length > 2)
+					|| (new File(repository.getDirectory(), "objects/pack").list().length > 0);
+		}
+		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);			
+		}
+		
+		LastChange(long lastModified) {
+			this.when = new Date(lastModified);
+		}
+	}
+
+	/**
+	 * 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
+	 */
+	public static LastChange getLastChange(Repository repository) {
+		if (!hasCommits(repository)) {
+			// null repository
+			if (repository == null) {
+				return new LastChange();
+			}
+			// fresh repository
+			return new LastChange(repository.getDirectory().lastModified());
+		}
+
+		List<RefModel> branchModels = getLocalBranches(repository, true, -1);
+		if (branchModels.size() > 0) {
+			// find most recent branch update
+			LastChange lastChange = new LastChange();			
+			for (RefModel branchModel : branchModels) {
+				if (branchModel.getDate().after(lastChange.when)) {
+					lastChange.when = branchModel.getDate();
+					lastChange.who = branchModel.getAuthorIdent().getName();
+				}
+			}
+			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
+	 */
+	public static Date getCommitDate(RevCommit commit) {
+		if (commit == null) {
+			return new Date(0);
+		}
+		return new Date(commit.getCommitTime() * 1000L);
+	}
+
+	/**
+	 * Retrieves a Java Date from a Git commit.
+	 * 
+	 * @param commit
+	 * @return date of the commit or Date(0) if the commit is null
+	 */
+	public static Date getAuthorDate(RevCommit commit) {
+		if (commit == null) {
+			return new Date(0);
+		}
+		return commit.getAuthorIdent().getWhen();
+	}
+
+	/**
+	 * 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.
+	 * @return RevCommit
+	 */
+	public static RevCommit getCommit(Repository repository, String objectId) {
+		if (!hasCommits(repository)) {
+			return null;
+		}
+		RevCommit commit = null;
+		try {
+			// resolve object id
+			ObjectId branchObject;
+			if (StringUtils.isEmpty(objectId)) {
+				branchObject = getDefaultBranch(repository);
+			} else {
+				branchObject = repository.resolve(objectId);
+			}
+			RevWalk walk = new RevWalk(repository);
+			RevCommit rev = walk.parseCommit(branchObject);
+			commit = rev;
+			walk.dispose();
+		} catch (Throwable t) {
+			error(t, repository, "{0} failed to get commit {1}", objectId);
+		}
+		return commit;
+	}
+
+	/**
+	 * Retrieves the raw byte content of a file in the specified tree.
+	 * 
+	 * @param repository
+	 * @param tree
+	 *            if null, the RevTree from HEAD is assumed.
+	 * @param path
+	 * @return content as a byte []
+	 */
+	public static byte[] getByteContent(Repository repository, RevTree tree, final String path, boolean throwError) {
+		RevWalk rw = new RevWalk(repository);
+		TreeWalk tw = new TreeWalk(repository);
+		tw.setFilter(PathFilterGroup.createFromStrings(Collections.singleton(path)));
+		byte[] content = null;
+		try {
+			if (tree == null) {
+				ObjectId object = getDefaultBranch(repository);
+				RevCommit commit = rw.parseCommit(object);
+				tree = commit.getTree();
+			}
+			tw.reset(tree);
+			while (tw.next()) {
+				if (tw.isSubtree() && !path.equals(tw.getPathString())) {
+					tw.enterSubtree();
+					continue;
+				}
+				ObjectId entid = tw.getObjectId(0);
+				FileMode entmode = tw.getFileMode(0);
+				if (entmode != FileMode.GITLINK) {
+					RevObject ro = rw.lookupAny(entid, entmode.getObjectType());
+					rw.parseBody(ro);
+					ByteArrayOutputStream os = new ByteArrayOutputStream();
+					ObjectLoader ldr = repository.open(ro.getId(), Constants.OBJ_BLOB);
+					byte[] tmp = new byte[4096];
+					InputStream in = ldr.openStream();
+					int n;
+					while ((n = in.read(tmp)) > 0) {
+						os.write(tmp, 0, n);
+					}
+					in.close();
+					content = os.toByteArray();
+				}
+			}
+		} catch (Throwable t) {
+			if (throwError) {
+				error(t, repository, "{0} can't find {1} in tree {2}", path, tree.name());
+			}
+		} finally {
+			rw.dispose();
+			tw.release();
+		}
+		return content;
+	}
+
+	/**
+	 * 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.
+	 * @param blobPath
+	 * @param charsets optional
+	 * @return UTF-8 string content
+	 */
+	public static String getStringContent(Repository repository, RevTree tree, String blobPath, String... charsets) {
+		byte[] content = getByteContent(repository, tree, blobPath, true);
+		if (content == null) {
+			return null;
+		}
+		return StringUtils.decodeString(content, charsets);
+	}
+
+	/**
+	 * Gets the raw byte content of the specified blob object.
+	 * 
+	 * @param repository
+	 * @param objectId
+	 * @return byte [] blob content
+	 */
+	public static byte[] getByteContent(Repository repository, String objectId) {
+		RevWalk rw = new RevWalk(repository);
+		byte[] content = null;
+		try {
+			RevBlob blob = rw.lookupBlob(ObjectId.fromString(objectId));
+			rw.parseBody(blob);
+			ByteArrayOutputStream os = new ByteArrayOutputStream();
+			ObjectLoader ldr = repository.open(blob.getId(), Constants.OBJ_BLOB);
+			byte[] tmp = new byte[4096];
+			InputStream in = ldr.openStream();
+			int n;
+			while ((n = in.read(tmp)) > 0) {
+				os.write(tmp, 0, n);
+			}
+			in.close();
+			content = os.toByteArray();
+		} catch (Throwable t) {
+			error(t, repository, "{0} can't find blob {1}", objectId);
+		} finally {
+			rw.dispose();
+		}
+		return content;
+	}
+
+	/**
+	 * Gets the UTF-8 string content of the blob specified by objectId.
+	 * 
+	 * @param repository
+	 * @param objectId
+	 * @param charsets optional
+	 * @return UTF-8 string content
+	 */
+	public static String getStringContent(Repository repository, String objectId, String... charsets) {
+		byte[] content = getByteContent(repository, objectId);
+		if (content == null) {
+			return null;
+		}
+		return StringUtils.decodeString(content, charsets);
+	}
+
+	/**
+	 * 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.
+	 * @param commit
+	 *            if null, HEAD is assumed.
+	 * @return list of files in specified path
+	 */
+	public static List<PathModel> getFilesInPath(Repository repository, String path,
+			RevCommit commit) {
+		List<PathModel> list = new ArrayList<PathModel>();
+		if (!hasCommits(repository)) {
+			return list;
+		}
+		if (commit == null) {
+			commit = getCommit(repository, null);
+		}
+		final TreeWalk tw = new TreeWalk(repository);
+		try {
+			tw.addTree(commit.getTree());
+			if (!StringUtils.isEmpty(path)) {
+				PathFilter f = PathFilter.create(path);
+				tw.setFilter(f);
+				tw.setRecursive(false);
+				boolean foundFolder = false;
+				while (tw.next()) {
+					if (!foundFolder && tw.isSubtree()) {
+						tw.enterSubtree();
+					}
+					if (tw.getPathString().equals(path)) {
+						foundFolder = true;
+						continue;
+					}
+					if (foundFolder) {
+						list.add(getPathModel(tw, path, commit));
+					}
+				}
+			} else {
+				tw.setRecursive(false);
+				while (tw.next()) {
+					list.add(getPathModel(tw, null, commit));
+				}
+			}
+		} catch (IOException e) {
+			error(e, repository, "{0} failed to get files for commit {1}", commit.getName());
+		} finally {
+			tw.release();
+		}
+		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.
+	 * @return list of files changed in a commit
+	 */
+	public static List<PathChangeModel> getFilesInCommit(Repository repository, RevCommit commit) {
+		List<PathChangeModel> list = new ArrayList<PathChangeModel>();
+		if (!hasCommits(repository)) {
+			return list;
+		}
+		RevWalk rw = new RevWalk(repository);
+		try {
+			if (commit == null) {
+				ObjectId object = getDefaultBranch(repository);
+				commit = rw.parseCommit(object);
+			}
+
+			if (commit.getParentCount() == 0) {
+				TreeWalk tw = new TreeWalk(repository);
+				tw.reset();
+				tw.setRecursive(true);
+				tw.addTree(commit.getTree());
+				while (tw.next()) {
+					list.add(new PathChangeModel(tw.getPathString(), tw.getPathString(), 0, tw
+							.getRawMode(0), tw.getObjectId(0).getName(), commit.getId().getName(),
+							ChangeType.ADD));
+				}
+				tw.release();
+			} else {
+				RevCommit parent = rw.parseCommit(commit.getParent(0).getId());
+				DiffFormatter df = new DiffFormatter(DisabledOutputStream.INSTANCE);
+				df.setRepository(repository);
+				df.setDiffComparator(RawTextComparator.DEFAULT);
+				df.setDetectRenames(true);
+				List<DiffEntry> diffs = df.scan(parent.getTree(), commit.getTree());
+				for (DiffEntry diff : diffs) {
+					String objectId = diff.getNewId().name();
+					if (diff.getChangeType().equals(ChangeType.DELETE)) {
+						list.add(new PathChangeModel(diff.getOldPath(), diff.getOldPath(), 0, diff
+								.getNewMode().getBits(), objectId, commit.getId().getName(), diff
+								.getChangeType()));
+					} else if (diff.getChangeType().equals(ChangeType.RENAME)) {
+						list.add(new PathChangeModel(diff.getOldPath(), diff.getNewPath(), 0, diff
+								.getNewMode().getBits(), objectId, commit.getId().getName(), diff
+								.getChangeType()));
+					} else {
+						list.add(new PathChangeModel(diff.getNewPath(), diff.getNewPath(), 0, diff
+								.getNewMode().getBits(), objectId, commit.getId().getName(), diff
+								.getChangeType()));
+					}
+				}
+			}
+		} catch (Throwable t) {
+			error(t, repository, "{0} failed to determine files in commit!");
+		} finally {
+			rw.dispose();
+		}
+		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 startCommit
+	 *            earliest commit
+	 * @param endCommit
+	 *            most recent commit. if null, HEAD is assumed.
+	 * @return list of files changed in a commit range
+	 */
+	public static List<PathChangeModel> getFilesInRange(Repository repository, RevCommit startCommit, RevCommit endCommit) {
+		List<PathChangeModel> list = new ArrayList<PathChangeModel>();
+		if (!hasCommits(repository)) {
+			return list;
+		}
+		try {
+			DiffFormatter df = new DiffFormatter(null);
+			df.setRepository(repository);
+			df.setDiffComparator(RawTextComparator.DEFAULT);
+			df.setDetectRenames(true);
+
+			List<DiffEntry> diffEntries = df.scan(startCommit.getTree(), endCommit.getTree());
+			for (DiffEntry diff : diffEntries) {
+				
+				if (diff.getChangeType().equals(ChangeType.DELETE)) {
+					list.add(new PathChangeModel(diff.getOldPath(), diff.getOldPath(), 0, diff
+							.getNewMode().getBits(), diff.getOldId().name(), null, diff
+							.getChangeType()));
+				} else if (diff.getChangeType().equals(ChangeType.RENAME)) {
+					list.add(new PathChangeModel(diff.getOldPath(), diff.getNewPath(), 0, diff
+							.getNewMode().getBits(), diff.getNewId().name(), null, diff
+							.getChangeType()));
+				} else {
+					list.add(new PathChangeModel(diff.getNewPath(), diff.getNewPath(), 0, diff
+							.getNewMode().getBits(), diff.getNewId().name(), null, diff
+							.getChangeType()));
+				}
+			}			
+			Collections.sort(list);
+		} catch (Throwable t) {
+			error(t, repository, "{0} failed to determine files in range {1}..{2}!", startCommit, endCommit);
+		}
+		return list;
+	}
+	/**
+	 * 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
+	 */
+	public static List<PathModel> getDocuments(Repository repository, List<String> extensions) {
+		return getDocuments(repository, extensions, null);
+	}
+
+	/**
+	 * 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
+	 * @return list of files in repository with a matching extension
+	 */
+	public static List<PathModel> getDocuments(Repository repository, List<String> extensions,
+			String objectId) {
+		List<PathModel> list = new ArrayList<PathModel>();
+		if (!hasCommits(repository)) {
+			return list;
+		}
+		RevCommit commit = getCommit(repository, objectId);
+		final TreeWalk tw = new TreeWalk(repository);
+		try {
+			tw.addTree(commit.getTree());
+			if (extensions != null && extensions.size() > 0) {
+				List<TreeFilter> suffixFilters = new ArrayList<TreeFilter>();
+				for (String extension : extensions) {
+					if (extension.charAt(0) == '.') {
+						suffixFilters.add(PathSuffixFilter.create("\\" + extension));
+					} else {
+						// escape the . since this is a regexp filter
+						suffixFilters.add(PathSuffixFilter.create("\\." + extension));
+					}
+				}
+				TreeFilter filter;
+				if (suffixFilters.size() == 1) {
+					filter = suffixFilters.get(0);
+				} else {
+					filter = OrTreeFilter.create(suffixFilters);
+				}
+				tw.setFilter(filter);
+				tw.setRecursive(true);
+			}
+			while (tw.next()) {
+				list.add(getPathModel(tw, null, commit));
+			}
+		} catch (IOException e) {
+			error(e, repository, "{0} failed to get documents for commit {1}", commit.getName());
+		} finally {
+			tw.release();
+		}
+		Collections.sort(list);
+		return list;
+	}
+
+	/**
+	 * Returns a path model of the current file in the treewalk.
+	 * 
+	 * @param tw
+	 * @param basePath
+	 * @param commit
+	 * @return a path model of the current file in the treewalk
+	 */
+	private static PathModel getPathModel(TreeWalk tw, String basePath, RevCommit commit) {
+		String name;
+		long size = 0;
+		if (StringUtils.isEmpty(basePath)) {
+			name = tw.getPathString();
+		} else {
+			name = tw.getPathString().substring(basePath.length() + 1);
+		}
+		ObjectId objectId = tw.getObjectId(0);
+		try {
+			if (!tw.isSubtree() && (tw.getFileMode(0) != FileMode.GITLINK)) {
+				size = tw.getObjectReader().getObjectSize(objectId, Constants.OBJ_BLOB);
+			}
+		} catch (Throwable t) {
+			error(t, null, "failed to retrieve blob size for " + tw.getPathString());
+		}
+		return new PathModel(name, tw.getPathString(), size, tw.getFileMode(0).getBits(),
+				objectId.getName(), commit.getName());
+	}
+
+	/**
+	 * Returns a permissions representation of the mode bits.
+	 * 
+	 * @param mode
+	 * @return string representation of the mode bits
+	 */
+	public static String getPermissionsFromMode(int mode) {
+		if (FileMode.TREE.equals(mode)) {
+			return "drwxr-xr-x";
+		} else if (FileMode.REGULAR_FILE.equals(mode)) {
+			return "-rw-r--r--";
+		} else if (FileMode.EXECUTABLE_FILE.equals(mode)) {
+			return "-rwxr-xr-x";
+		} else if (FileMode.SYMLINK.equals(mode)) {
+			return "symlink";
+		} else if (FileMode.GITLINK.equals(mode)) {
+			return "submodule";
+		}
+		return "missing";
+	}
+
+	/**
+	 * Returns a list of commits since the minimum date starting from the
+	 * specified object id.
+	 * 
+	 * @param repository
+	 * @param objectId
+	 *            if unspecified, HEAD is assumed.
+	 * @param minimumDate
+	 * @return list of commits
+	 */
+	public static List<RevCommit> getRevLog(Repository repository, String objectId, Date minimumDate) {
+		List<RevCommit> list = new ArrayList<RevCommit>();
+		if (!hasCommits(repository)) {
+			return list;
+		}
+		try {
+			// resolve branch
+			ObjectId branchObject;
+			if (StringUtils.isEmpty(objectId)) {
+				branchObject = getDefaultBranch(repository);
+			} else {
+				branchObject = repository.resolve(objectId);
+			}
+
+			RevWalk rw = new RevWalk(repository);
+			rw.markStart(rw.parseCommit(branchObject));
+			rw.setRevFilter(CommitTimeRevFilter.after(minimumDate));
+			Iterable<RevCommit> revlog = rw;
+			for (RevCommit rev : revlog) {
+				list.add(rev);
+			}
+			rw.dispose();
+		} catch (Throwable t) {
+			error(t, repository, "{0} failed to get {1} revlog for minimum date {2}", objectId,
+					minimumDate);
+		}
+		return list;
+	}
+
+	/**
+	 * Returns a list of commits starting from HEAD and working backwards.
+	 * 
+	 * @param repository
+	 * @param maxCount
+	 *            if < 0, all commits for the repository are returned.
+	 * @return list of commits
+	 */
+	public static List<RevCommit> getRevLog(Repository repository, int maxCount) {
+		return getRevLog(repository, null, 0, maxCount);
+	}
+
+	/**
+	 * Returns a list of commits starting from the specified objectId using an
+	 * 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.
+	 * @param offset
+	 * @param maxCount
+	 *            if < 0, all commits are returned.
+	 * @return a paged list of commits
+	 */
+	public static List<RevCommit> getRevLog(Repository repository, String objectId, int offset,
+			int maxCount) {
+		return getRevLog(repository, objectId, null, offset, maxCount);
+	}
+
+	/**
+	 * Returns a list of commits for the repository or a path within the
+	 * 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.
+	 * @param path
+	 *            if unspecified, commits for repository are returned. If
+	 *            specified, commits for the path are returned.
+	 * @param offset
+	 * @param maxCount
+	 *            if < 0, all commits are returned.
+	 * @return a paged list of commits
+	 */
+	public static List<RevCommit> getRevLog(Repository repository, String objectId, String path,
+			int offset, int maxCount) {
+		List<RevCommit> list = new ArrayList<RevCommit>();
+		if (maxCount == 0) {
+			return list;
+		}
+		if (!hasCommits(repository)) {
+			return list;
+		}
+		try {
+			// resolve branch
+			ObjectId startRange = null;
+			ObjectId endRange;
+			if (StringUtils.isEmpty(objectId)) {
+				endRange = getDefaultBranch(repository);
+			} else {
+				if( objectId.contains("..") ) {
+					// range expression
+					String[] parts = objectId.split("\\.\\.");
+					startRange = repository.resolve(parts[0]);
+					endRange = repository.resolve(parts[1]);
+				} else {
+					// objectid
+					endRange= repository.resolve(objectId);
+				}
+			}
+			if (endRange == null) {
+				return list;
+			}
+
+			RevWalk rw = new RevWalk(repository);
+			rw.markStart(rw.parseCommit(endRange));
+			if (startRange != null) {
+				rw.markUninteresting(rw.parseCommit(startRange));	
+			}
+			if (!StringUtils.isEmpty(path)) {
+				TreeFilter filter = AndTreeFilter.create(
+						PathFilterGroup.createFromStrings(Collections.singleton(path)),
+						TreeFilter.ANY_DIFF);
+				rw.setTreeFilter(filter);
+			}
+			Iterable<RevCommit> revlog = rw;
+			if (offset > 0) {
+				int count = 0;
+				for (RevCommit rev : revlog) {
+					count++;
+					if (count > offset) {
+						list.add(rev);
+						if (maxCount > 0 && list.size() == maxCount) {
+							break;
+						}
+					}
+				}
+			} else {
+				for (RevCommit rev : revlog) {
+					list.add(rev);
+					if (maxCount > 0 && list.size() == maxCount) {
+						break;
+					}
+				}
+			}
+			rw.dispose();
+		} catch (Throwable t) {
+			error(t, repository, "{0} failed to get {1} revlog for path {2}", objectId, path);
+		}
+		return list;
+	}
+
+	/**
+	 * 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)
+	 * @param endRangeId
+	 *            the end commit (included in results)
+	 * @return a list of commits
+	 */
+	public static List<RevCommit> getRevLog(Repository repository, String startRangeId,
+			String endRangeId) {
+		List<RevCommit> list = new ArrayList<RevCommit>();
+		if (!hasCommits(repository)) {
+			return list;
+		}
+		try {
+			ObjectId endRange = repository.resolve(endRangeId);
+			ObjectId startRange = repository.resolve(startRangeId);
+
+			RevWalk rw = new RevWalk(repository);
+			rw.markStart(rw.parseCommit(endRange));
+			if (startRange.equals(ObjectId.zeroId())) {
+				// maybe this is a tag or an orphan branch
+				list.add(rw.parseCommit(endRange));
+				rw.dispose();
+				return list;
+			} else {
+				rw.markUninteresting(rw.parseCommit(startRange));
+			}
+
+			Iterable<RevCommit> revlog = rw;
+			for (RevCommit rev : revlog) {
+				list.add(rev);
+			}
+			rw.dispose();
+		} catch (Throwable t) {
+			error(t, repository, "{0} failed to get revlog for {1}..{2}", startRangeId, endRangeId);
+		}
+		return list;
+	}
+
+	/**
+	 * Search the commit history for a case-insensitive match to the value.
+	 * 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.
+	 * @param value
+	 * @param type
+	 *            AUTHOR, COMMITTER, COMMIT
+	 * @param offset
+	 * @param maxCount
+	 *            if < 0, all matches are returned
+	 * @return matching list of commits
+	 */
+	public static List<RevCommit> searchRevlogs(Repository repository, String objectId,
+			String value, final com.gitblit.Constants.SearchType type, int offset, int maxCount) {
+		final String lcValue = value.toLowerCase();
+		List<RevCommit> list = new ArrayList<RevCommit>();
+		if (maxCount == 0) {
+			return list;
+		}
+		if (!hasCommits(repository)) {
+			return list;
+		}
+		try {
+			// resolve branch
+			ObjectId branchObject;
+			if (StringUtils.isEmpty(objectId)) {
+				branchObject = getDefaultBranch(repository);
+			} else {
+				branchObject = repository.resolve(objectId);
+			}
+
+			RevWalk rw = new RevWalk(repository);
+			rw.setRevFilter(new RevFilter() {
+
+				@Override
+				public RevFilter clone() {
+					// FindBugs complains about this method name.
+					// This is part of JGit design and unrelated to Cloneable.
+					return this;
+				}
+
+				@Override
+				public boolean include(RevWalk walker, RevCommit commit) throws StopWalkException,
+						MissingObjectException, IncorrectObjectTypeException, IOException {
+					boolean include = false;
+					switch (type) {
+					case AUTHOR:
+						include = (commit.getAuthorIdent().getName().toLowerCase().indexOf(lcValue) > -1)
+								|| (commit.getAuthorIdent().getEmailAddress().toLowerCase()
+										.indexOf(lcValue) > -1);
+						break;
+					case COMMITTER:
+						include = (commit.getCommitterIdent().getName().toLowerCase()
+								.indexOf(lcValue) > -1)
+								|| (commit.getCommitterIdent().getEmailAddress().toLowerCase()
+										.indexOf(lcValue) > -1);
+						break;
+					case COMMIT:
+						include = commit.getFullMessage().toLowerCase().indexOf(lcValue) > -1;
+						break;
+					}
+					return include;
+				}
+
+			});
+			rw.markStart(rw.parseCommit(branchObject));
+			Iterable<RevCommit> revlog = rw;
+			if (offset > 0) {
+				int count = 0;
+				for (RevCommit rev : revlog) {
+					count++;
+					if (count > offset) {
+						list.add(rev);
+						if (maxCount > 0 && list.size() == maxCount) {
+							break;
+						}
+					}
+				}
+			} else {
+				for (RevCommit rev : revlog) {
+					list.add(rev);
+					if (maxCount > 0 && list.size() == maxCount) {
+						break;
+					}
+				}
+			}
+			rw.dispose();
+		} catch (Throwable t) {
+			error(t, repository, "{0} failed to {1} search revlogs for {2}", type.name(), value);
+		}
+		return list;
+	}
+
+	/**
+	 * 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
+	 */
+	public static ObjectId getDefaultBranch(Repository repository) throws Exception {
+		ObjectId object = repository.resolve(Constants.HEAD);
+		if (object == null) {
+			// no HEAD
+			// perhaps non-standard repository, try local branches
+			List<RefModel> branchModels = getLocalBranches(repository, true, -1);
+			if (branchModels.size() > 0) {
+				// use most recently updated branch
+				RefModel branch = null;
+				Date lastDate = new Date(0);
+				for (RefModel branchModel : branchModels) {
+					if (branchModel.getDate().after(lastDate)) {
+						branch = branchModel;
+						lastDate = branch.getDate();
+					}
+				}
+				object = branch.getReferencedObjectId();
+			}
+		}
+		return object;
+	}
+
+	/**
+	 * Returns the target of the symbolic HEAD reference for a repository.
+	 * Normally returns a branch reference name, but when HEAD is detached,
+	 * the commit is matched against the known tags. The most recent matching
+	 * tag ref name will be returned if it references the HEAD commit. If
+	 * no match is found, the SHA1 is returned.
+	 *
+	 * @param repository
+	 * @return the ref name or the SHA1 for a detached HEAD
+	 */
+	public static String getHEADRef(Repository repository) {
+		String target = null;
+		try {
+			target = repository.getFullBranch();
+			if (!target.startsWith(Constants.R_HEADS)) {
+				// refers to an actual commit, probably a tag
+				// find latest tag that matches the commit, if any
+				List<RefModel> tagModels = getTags(repository, true, -1);
+				if (tagModels.size() > 0) {
+					RefModel tag = null;
+					Date lastDate = new Date(0);
+					for (RefModel tagModel : tagModels) {
+						if (tagModel.getReferencedObjectId().getName().equals(target) &&
+								tagModel.getDate().after(lastDate)) {
+							tag = tagModel;
+							lastDate = tag.getDate();
+						}
+					}
+					target = tag.getName();
+				}
+			}
+		} catch (Throwable t) {
+			error(t, repository, "{0} failed to get symbolic HEAD target");
+		}
+		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.
+	 *
+	 * @param repository
+	 * @param targetRef
+	 * @return true if successful
+	 */
+	public static boolean setHEADtoRef(Repository repository, String targetRef) {
+		try {
+			 // detach HEAD if target ref is not a branch
+			boolean detach = !targetRef.startsWith(Constants.R_HEADS);
+			RefUpdate.Result result;
+			RefUpdate head = repository.updateRef(Constants.HEAD, detach);
+			if (detach) { // Tag
+				RevCommit commit = getCommit(repository, targetRef);
+				head.setNewObjectId(commit.getId());
+				result = head.forceUpdate();
+			} else {
+				result = head.link(targetRef);
+			}
+			switch (result) {
+			case NEW:
+			case FORCED:
+			case NO_CHANGE:
+			case FAST_FORWARD:
+				return true;				
+			default:
+				LOGGER.error(MessageFormat.format("{0} HEAD update to {1} returned result {2}",
+						repository.getDirectory().getAbsolutePath(), targetRef, result));
+			}
+		} catch (Throwable t) {
+			error(t, repository, "{0} failed to set HEAD to {1}", targetRef);
+		}
+		return false;
+	}
+	
+	/**
+	 * Sets the local branch ref to point to the specified commit id.
+	 *
+	 * @param repository
+	 * @param branch
+	 * @param commitId
+	 * @return true if successful
+	 */
+	public static boolean setBranchRef(Repository repository, String branch, String commitId) {
+		String branchName = branch;
+		if (!branchName.startsWith(Constants.R_HEADS)) {
+			branchName = Constants.R_HEADS + branch;
+		}
+
+		try {
+			RefUpdate refUpdate = repository.updateRef(branchName, false);
+			refUpdate.setNewObjectId(ObjectId.fromString(commitId));
+			RefUpdate.Result result = refUpdate.forceUpdate();
+
+			switch (result) {
+			case NEW:
+			case FORCED:
+			case NO_CHANGE:
+			case FAST_FORWARD:
+				return true;				
+			default:
+				LOGGER.error(MessageFormat.format("{0} {1} update to {2} returned result {3}",
+						repository.getDirectory().getAbsolutePath(), branchName, commitId, result));
+			}
+		} catch (Throwable t) {
+			error(t, repository, "{0} failed to set {1} to {2}", branchName, commitId);
+		}
+		return false;
+	}
+	
+	/**
+	 * Deletes the specified branch ref.
+	 *  
+	 * @param repository
+	 * @param branch
+	 * @return true if successful
+	 */
+	public static boolean deleteBranchRef(Repository repository, String branch) {
+		String branchName = branch;
+		if (!branchName.startsWith(Constants.R_HEADS)) {
+			branchName = Constants.R_HEADS + branch;
+		}
+
+		try {
+			RefUpdate refUpdate = repository.updateRef(branchName, false);
+			refUpdate.setForceUpdate(true);
+			RefUpdate.Result result = refUpdate.delete();
+			switch (result) {
+			case NEW:
+			case FORCED:
+			case NO_CHANGE:
+			case FAST_FORWARD:
+				return true;				
+			default:
+				LOGGER.error(MessageFormat.format("{0} failed to delete to {1} returned result {2}",
+						repository.getDirectory().getAbsolutePath(), branchName, result));
+			}
+		} catch (Throwable t) {
+			error(t, repository, "{0} failed to delete {1}", branchName);
+		}
+		return false;
+	}
+	
+	/**
+	 * Get the full branch and tag ref names for any potential HEAD targets.
+	 *
+	 * @param repository
+	 * @return a list of ref names
+	 */
+	public static List<String> getAvailableHeadTargets(Repository repository) {
+		List<String> targets = new ArrayList<String>();
+		for (RefModel branchModel : JGitUtils.getLocalBranches(repository, true, -1)) {
+			targets.add(branchModel.getName());
+		}
+
+		for (RefModel tagModel : JGitUtils.getTags(repository, true, -1)) {
+			targets.add(tagModel.getName());
+		}
+		return targets;
+	}
+
+	/**
+	 * 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
+	 */
+	public static Map<ObjectId, List<RefModel>> getAllRefs(Repository repository, boolean includeRemoteRefs) {
+		List<RefModel> list = getRefs(repository, org.eclipse.jgit.lib.RefDatabase.ALL, true, -1);
+		Map<ObjectId, List<RefModel>> refs = new HashMap<ObjectId, List<RefModel>>();
+		for (RefModel ref : list) {
+			if (!includeRemoteRefs && ref.getName().startsWith(Constants.R_REMOTES)) {
+				continue;
+			}
+			ObjectId objectid = ref.getReferencedObjectId();
+			if (!refs.containsKey(objectid)) {
+				refs.put(objectid, new ArrayList<RefModel>());
+			}
+			refs.get(objectid).add(ref);
+		}
+		return refs;
+	}
+
+	/**
+	 * 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,
+	 *            yadayadayada is returned.
+	 * @param maxCount
+	 *            if < 0, all tags are returned
+	 * @return list of tags
+	 */
+	public static List<RefModel> getTags(Repository repository, boolean fullName, int maxCount) {
+		return getRefs(repository, Constants.R_TAGS, fullName, maxCount);
+	}
+
+	/**
+	 * 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,
+	 *            yadayadayada is returned.
+	 * @param maxCount
+	 *            if < 0, all local branches are returned
+	 * @return list of local branches
+	 */
+	public static List<RefModel> getLocalBranches(Repository repository, boolean fullName,
+			int maxCount) {
+		return getRefs(repository, Constants.R_HEADS, fullName, maxCount);
+	}
+
+	/**
+	 * 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,
+	 *            yadayadayada is returned.
+	 * @param maxCount
+	 *            if < 0, all remote branches are returned
+	 * @return list of remote branches
+	 */
+	public static List<RefModel> getRemoteBranches(Repository repository, boolean fullName,
+			int maxCount) {
+		return getRefs(repository, Constants.R_REMOTES, fullName, maxCount);
+	}
+
+	/**
+	 * 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,
+	 *            yadayadayada is returned.
+	 * @param maxCount
+	 *            if < 0, all note branches are returned
+	 * @return list of note branches
+	 */
+	public static List<RefModel> getNoteBranches(Repository repository, boolean fullName,
+			int maxCount) {
+		return getRefs(repository, Constants.R_NOTES, fullName, maxCount);
+	}
+	
+	/**
+	 * 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,
+	 *            yadayadayada is returned.
+	 * @return list of refs
+	 */
+	public static List<RefModel> getRefs(Repository repository, String baseRef) {
+		return getRefs(repository, baseRef, true, -1);
+	}
+
+	/**
+	 * 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
+	 * @param fullName
+	 *            if true, /refs/something/yadayadayada is returned. If false,
+	 *            yadayadayada is returned.
+	 * @param maxCount
+	 *            if < 0, all references are returned
+	 * @return list of references
+	 */
+	private static List<RefModel> getRefs(Repository repository, String refs, boolean fullName,
+			int maxCount) {
+		List<RefModel> list = new ArrayList<RefModel>();
+		if (maxCount == 0) {
+			return list;
+		}
+		if (!hasCommits(repository)) {
+			return list;
+		}
+		try {
+			Map<String, Ref> map = repository.getRefDatabase().getRefs(refs);
+			RevWalk rw = new RevWalk(repository);
+			for (Entry<String, Ref> entry : map.entrySet()) {
+				Ref ref = entry.getValue();
+				RevObject object = rw.parseAny(ref.getObjectId());
+				String name = entry.getKey();
+				if (fullName && !StringUtils.isEmpty(refs)) {
+					name = refs + name;
+				}
+				list.add(new RefModel(name, ref, object));
+			}
+			rw.dispose();
+			Collections.sort(list);
+			Collections.reverse(list);
+			if (maxCount > 0 && list.size() > maxCount) {
+				list = new ArrayList<RefModel>(list.subList(0, maxCount));
+			}
+		} catch (IOException e) {
+			error(e, repository, "{0} failed to retrieve {1}", refs);
+		}
+		return list;
+	}
+
+	/**
+	 * 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
+	 */
+	public static RefModel getPagesBranch(Repository repository) {
+		return getBranch(repository, "gh-pages");
+	}
+
+	/**
+	 * 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
+	 */
+	public static RefModel getBranch(Repository repository, String name) {
+		RefModel branch = null;
+		try {
+			// search for the branch in local heads
+			for (RefModel ref : JGitUtils.getLocalBranches(repository, false, -1)) {
+				if (ref.reference.getName().endsWith(name)) {
+					branch = ref;
+					break;
+				}
+			}
+
+			// search for the branch in remote heads
+			if (branch == null) {
+				for (RefModel ref : JGitUtils.getRemoteBranches(repository, false, -1)) {
+					if (ref.reference.getName().endsWith(name)) {
+						branch = ref;
+						break;
+					}
+				}
+			}
+		} catch (Throwable t) {
+			LOGGER.error(MessageFormat.format("Failed to find {0} branch!", name), t);
+		}
+		return branch;
+	}
+		
+	/**
+	 * Returns the list of submodules for this repository.
+	 * 
+	 * @param repository
+	 * @param commit
+	 * @return list of submodules
+	 */
+	public static List<SubmoduleModel> getSubmodules(Repository repository, String commitId) {
+		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
+	 */
+	public static List<SubmoduleModel> getSubmodules(Repository repository, RevTree tree) {
+		List<SubmoduleModel> list = new ArrayList<SubmoduleModel>();
+		byte [] blob = getByteContent(repository, tree, ".gitmodules", false);
+		if (blob == null) {
+			return list;
+		}
+		try {
+			BlobBasedConfig config = new BlobBasedConfig(repository.getConfig(), blob);
+			for (String module : config.getSubsections("submodule")) {
+				String path = config.getString("submodule", module, "path");
+				String url = config.getString("submodule", module, "url");
+				list.add(new SubmoduleModel(module, path, url));
+			}
+		} catch (ConfigInvalidException e) {
+			LOGGER.error("Failed to load .gitmodules file for " + repository.getDirectory(), e);
+		}
+		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
+	 * @return a submodule definition or null if there is no submodule
+	 */
+	public static SubmoduleModel getSubmoduleModel(Repository repository, String commitId, String path) {
+		for (SubmoduleModel model : getSubmodules(repository, commitId)) {
+			if (model.path.equals(path)) {
+				return model;
+			}
+		}
+		return null;
+	}
+	
+	public static String getSubmoduleCommitId(Repository repository, String path, RevCommit commit) {
+		String commitId = null;
+		RevWalk rw = new RevWalk(repository);
+		TreeWalk tw = new TreeWalk(repository);
+		tw.setFilter(PathFilterGroup.createFromStrings(Collections.singleton(path)));
+		try {
+			tw.reset(commit.getTree());
+			while (tw.next()) {
+				if (tw.isSubtree() && !path.equals(tw.getPathString())) {
+					tw.enterSubtree();
+					continue;
+				}
+				if (FileMode.GITLINK == tw.getFileMode(0)) {
+					commitId = tw.getObjectId(0).getName();
+					break;
+				}
+			}
+		} catch (Throwable t) {
+			error(t, repository, "{0} can't find {1} in commit {2}", path, commit.name());
+		} finally {
+			rw.dispose();
+			tw.release();
+		}
+		return commitId;
+	}
+
+	/**
+	 * 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
+	 */
+	public static List<GitNote> getNotesOnCommit(Repository repository, RevCommit commit) {
+		List<GitNote> list = new ArrayList<GitNote>();
+		if (!hasCommits(repository)) {
+			return list;
+		}
+		List<RefModel> noteBranches = getNoteBranches(repository, true, -1);
+		for (RefModel notesRef : noteBranches) {
+			RevTree notesTree = JGitUtils.getCommit(repository, notesRef.getName()).getTree();
+			// flat notes list
+			String notePath = commit.getName();
+			String text = getStringContent(repository, notesTree, notePath);
+			if (!StringUtils.isEmpty(text)) {
+				List<RevCommit> history = getRevLog(repository, notesRef.getName(), notePath, 0, -1);
+				RefModel noteRef = new RefModel(notesRef.displayName, null, history.get(history
+						.size() - 1));
+				GitNote gitNote = new GitNote(noteRef, text);
+				list.add(gitNote);
+				continue;
+			}
+			
+			// folder structure
+			StringBuilder sb = new StringBuilder(commit.getName());
+			sb.insert(2, '/');
+			notePath = sb.toString();
+			text = getStringContent(repository, notesTree, notePath);
+			if (!StringUtils.isEmpty(text)) {
+				List<RevCommit> history = getRevLog(repository, notesRef.getName(), notePath, 0, -1);
+				RefModel noteRef = new RefModel(notesRef.displayName, null, history.get(history
+						.size() - 1));
+				GitNote gitNote = new GitNote(noteRef, text);
+				list.add(gitNote);
+			}
+		}
+		return list;
+	}
+
+	/**
+	 * 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
+	 * @param prefix
+	 * @param intPattern
+	 * @param message
+	 * @return true if operation was successful, otherwise false
+	 */
+	public static boolean createIncrementalRevisionTag(Repository repository,
+			String objectId, PersonIdent tagger, String prefix, String intPattern, String message) {
+		boolean result = false;
+		Iterator<Entry<String, Ref>> iterator = repository.getTags().entrySet().iterator();
+		long lastRev = 0;
+		while (iterator.hasNext()) {
+			Entry<String, Ref> entry = iterator.next();
+			if (entry.getKey().startsWith(prefix)) {
+				try {
+					long val = Long.parseLong(entry.getKey().substring(prefix.length()));
+					if (val > lastRev) {
+						lastRev = val;
+					}
+				} catch (Exception e) {
+					// this tag is NOT an incremental revision tag
+				}
+			}
+		}
+		DecimalFormat df = new DecimalFormat(intPattern);
+		result = createTag(repository, objectId, tagger, prefix + df.format((lastRev + 1)), message);
+		return result;
+	}
+
+	/**
+	 * creates a tag in a repository
+	 * 
+	 * @param repository
+	 * @param objectId, the ref the tag points towards
+	 * @param tagger, the person tagging the object
+	 * @param tag, the string label
+	 * @param message, the string message
+	 * @return boolean, true if operation was successful, otherwise false
+	 */
+	public static boolean createTag(Repository repository, String objectId, PersonIdent tagger, String tag, String message) {
+		try {			
+			Git gitClient = Git.open(repository.getDirectory());
+			TagCommand tagCommand = gitClient.tag();
+			tagCommand.setTagger(tagger);
+			tagCommand.setMessage(message);
+			if (objectId != null) {
+				RevObject revObj = getCommit(repository, objectId);
+				tagCommand.setObjectId(revObj);
+			}
+			tagCommand.setName(tag);
+			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
+	 *            if unspecified, Gitblit will be the author of this new branch
+	 * @return true if successful
+	 */
+	public static boolean createOrphanBranch(Repository repository, String branchName,
+			PersonIdent author) {
+		boolean success = false;
+		String message = "Created branch " + branchName;
+		if (author == null) {
+			author = new PersonIdent("Gitblit", "gitblit@localhost");
+		}
+		try {
+			ObjectInserter odi = repository.newObjectInserter();
+			try {
+				// Create a blob object to insert into a tree
+				ObjectId blobId = odi.insert(Constants.OBJ_BLOB,
+						message.getBytes(Constants.CHARACTER_ENCODING));
+
+				// Create a tree object to reference from a commit
+				TreeFormatter tree = new TreeFormatter();
+				tree.append(".branch", FileMode.REGULAR_FILE, blobId);
+				ObjectId treeId = odi.insert(tree);
+
+				// Create a commit object
+				CommitBuilder commit = new CommitBuilder();
+				commit.setAuthor(author);
+				commit.setCommitter(author);
+				commit.setEncoding(Constants.CHARACTER_ENCODING);
+				commit.setMessage(message);
+				commit.setTreeId(treeId);
+
+				// Insert the commit into the repository
+				ObjectId commitId = odi.insert(commit);
+				odi.flush();
+
+				RevWalk revWalk = new RevWalk(repository);
+				try {
+					RevCommit revCommit = revWalk.parseCommit(commitId);
+					if (!branchName.startsWith("refs/")) {
+						branchName = "refs/heads/" + branchName;
+					}
+					RefUpdate ru = repository.updateRef(branchName);
+					ru.setNewObjectId(commitId);
+					ru.setRefLogMessage("commit: " + revCommit.getShortMessage(), false);
+					Result rc = ru.forceUpdate();
+					switch (rc) {
+					case NEW:
+					case FORCED:
+					case FAST_FORWARD:
+						success = true;
+						break;
+					default:
+						success = false;
+					}
+				} finally {
+					revWalk.release();
+				}
+			} finally {
+				odi.release();
+			}
+		} catch (Throwable t) {
+			error(t, repository, "Failed to create orphan branch {1} in repository {0}", branchName);
+		}
+		return success;
+	}
+	
+	/**
+	 * Reads the sparkleshare id, if present, from the repository.
+	 * 
+	 * @param repository
+	 * @return an id or null
+	 */
+	public static String getSparkleshareId(Repository repository) {
+		byte[] content = getByteContent(repository, null, ".sparkleshare", false);
+		if (content == null) {
+			return null;
+		}
+		return StringUtils.decodeString(content);
+	}
+}
diff --git a/src/main/java/com/gitblit/utils/JsonUtils.java b/src/main/java/com/gitblit/utils/JsonUtils.java
new file mode 100644
index 0000000..e924182
--- /dev/null
+++ b/src/main/java/com/gitblit/utils/JsonUtils.java
@@ -0,0 +1,345 @@
+/*
+ * Copyright 2011 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit.utils;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.lang.reflect.Type;
+import java.net.HttpURLConnection;
+import java.net.URLConnection;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Collection;
+import java.util.Date;
+import java.util.Locale;
+import java.util.Map;
+import java.util.TimeZone;
+
+import com.gitblit.Constants.AccessPermission;
+import com.gitblit.GitBlitException.ForbiddenException;
+import com.gitblit.GitBlitException.NotAllowedException;
+import com.gitblit.GitBlitException.UnauthorizedException;
+import com.gitblit.GitBlitException.UnknownRequestException;
+import com.gitblit.models.RepositoryModel;
+import com.gitblit.models.UserModel;
+import com.google.gson.ExclusionStrategy;
+import com.google.gson.FieldAttributes;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonPrimitive;
+import com.google.gson.JsonSerializationContext;
+import com.google.gson.JsonSerializer;
+import com.google.gson.JsonSyntaxException;
+import com.google.gson.reflect.TypeToken;
+
+/**
+ * Utility methods for json calls to a Gitblit server.
+ * 
+ * @author James Moger
+ * 
+ */
+public class JsonUtils {
+
+	public static final Type REPOSITORIES_TYPE = new TypeToken<Map<String, RepositoryModel>>() {
+	}.getType();
+
+	public static final Type USERS_TYPE = new TypeToken<Collection<UserModel>>() {
+	}.getType();
+
+	/**
+	 * Creates JSON from the specified object.
+	 * 
+	 * @param o
+	 * @return json
+	 */
+	public static String toJsonString(Object o) {
+		String json = gson().toJson(o);
+		return json;
+	}
+
+	/**
+	 * Convert a json string to an object of the specified type.
+	 * 
+	 * @param json
+	 * @param clazz
+	 * @return an object
+	 */
+	public static <X> X fromJsonString(String json, Class<X> clazz) {
+		return gson().fromJson(json, clazz);
+	}
+
+	/**
+	 * Convert a json string to an object of the specified type.
+	 * 
+	 * @param json
+	 * @param clazz
+	 * @return an object
+	 */
+	public static <X> X fromJsonString(String json, Type type) {
+		return gson().fromJson(json, type);
+	}
+
+	/**
+	 * Reads a gson object from the specified url.
+	 * 
+	 * @param url
+	 * @param type
+	 * @return the deserialized object
+	 * @throws {@link IOException}
+	 */
+	public static <X> X retrieveJson(String url, Type type) throws IOException,
+			UnauthorizedException {
+		return retrieveJson(url, type, null, null);
+	}
+
+	/**
+	 * Reads a gson object from the specified url.
+	 * 
+	 * @param url
+	 * @param type
+	 * @return the deserialized object
+	 * @throws {@link IOException}
+	 */
+	public static <X> X retrieveJson(String url, Class<? extends X> clazz) throws IOException,
+			UnauthorizedException {
+		return retrieveJson(url, clazz, null, null);
+	}
+
+	/**
+	 * Reads a gson object from the specified url.
+	 * 
+	 * @param url
+	 * @param type
+	 * @param username
+	 * @param password
+	 * @return the deserialized object
+	 * @throws {@link IOException}
+	 */
+	public static <X> X retrieveJson(String url, Type type, String username, char[] password)
+			throws IOException {
+		String json = retrieveJsonString(url, username, password);
+		if (StringUtils.isEmpty(json)) {
+			return null;
+		}
+		return gson().fromJson(json, type);
+	}
+
+	/**
+	 * Reads a gson object from the specified url.
+	 * 
+	 * @param url
+	 * @param clazz
+	 * @param username
+	 * @param password
+	 * @return the deserialized object
+	 * @throws {@link IOException}
+	 */
+	public static <X> X retrieveJson(String url, Class<X> clazz, String username, char[] password)
+			throws IOException {
+		String json = retrieveJsonString(url, username, password);
+		if (StringUtils.isEmpty(json)) {
+			return null;
+		}
+		return gson().fromJson(json, clazz);
+	}
+
+	/**
+	 * Retrieves a JSON message.
+	 * 
+	 * @param url
+	 * @return the JSON message as a string
+	 * @throws {@link IOException}
+	 */
+	public static String retrieveJsonString(String url, String username, char[] password)
+			throws IOException {
+		try {
+			URLConnection conn = ConnectionUtils.openReadConnection(url, username, password);
+			InputStream is = conn.getInputStream();
+			BufferedReader reader = new BufferedReader(new InputStreamReader(is,
+					ConnectionUtils.CHARSET));
+			StringBuilder json = new StringBuilder();
+			char[] buffer = new char[4096];
+			int len = 0;
+			while ((len = reader.read(buffer)) > -1) {
+				json.append(buffer, 0, len);
+			}
+			is.close();
+			return json.toString();
+		} catch (IOException e) {
+			if (e.getMessage().indexOf("401") > -1) {
+				// unauthorized
+				throw new UnauthorizedException(url);
+			} else if (e.getMessage().indexOf("403") > -1) {
+				// requested url is forbidden by the requesting user
+				throw new ForbiddenException(url);
+			} else if (e.getMessage().indexOf("405") > -1) {
+				// requested url is not allowed by the server
+				throw new NotAllowedException(url);
+			} else if (e.getMessage().indexOf("501") > -1) {
+				// requested url is not recognized by the server
+				throw new UnknownRequestException(url);
+			}
+			throw e;
+		}
+	}
+
+	/**
+	 * Sends a JSON message.
+	 * 
+	 * @param url
+	 *            the url to write to
+	 * @param json
+	 *            the json message to send
+	 * @return the http request result code
+	 * @throws {@link IOException}
+	 */
+	public static int sendJsonString(String url, String json) throws IOException {
+		return sendJsonString(url, json, null, null);
+	}
+
+	/**
+	 * Sends a JSON message.
+	 * 
+	 * @param url
+	 *            the url to write to
+	 * @param json
+	 *            the json message to send
+	 * @param username
+	 * @param password
+	 * @return the http request result code
+	 * @throws {@link IOException}
+	 */
+	public static int sendJsonString(String url, String json, String username, char[] password)
+			throws IOException {
+		try {
+			byte[] jsonBytes = json.getBytes(ConnectionUtils.CHARSET);
+			URLConnection conn = ConnectionUtils.openConnection(url, username, password);
+			conn.setRequestProperty("Content-Type", "text/plain;charset=" + ConnectionUtils.CHARSET);
+			conn.setRequestProperty("Content-Length", "" + jsonBytes.length);
+
+			// write json body
+			OutputStream os = conn.getOutputStream();
+			os.write(jsonBytes);
+			os.close();
+
+			int status = ((HttpURLConnection) conn).getResponseCode();
+			return status;
+		} catch (IOException e) {
+			if (e.getMessage().indexOf("401") > -1) {
+				// unauthorized
+				throw new UnauthorizedException(url);
+			} else if (e.getMessage().indexOf("403") > -1) {
+				// requested url is forbidden by the requesting user
+				throw new ForbiddenException(url);
+			} else if (e.getMessage().indexOf("405") > -1) {
+				// requested url is not allowed by the server
+				throw new NotAllowedException(url);
+			} else if (e.getMessage().indexOf("501") > -1) {
+				// requested url is not recognized by the server
+				throw new UnknownRequestException(url);
+			}
+			throw e;
+		}
+	}
+
+	// build custom gson instance with GMT date serializer/deserializer
+	// http://code.google.com/p/google-gson/issues/detail?id=281
+	public static Gson gson(ExclusionStrategy... strategies) {
+		GsonBuilder builder = new GsonBuilder();
+		builder.registerTypeAdapter(Date.class, new GmtDateTypeAdapter());
+		builder.registerTypeAdapter(AccessPermission.class, new AccessPermissionTypeAdapter());
+		if (!ArrayUtils.isEmpty(strategies)) {
+			builder.setExclusionStrategies(strategies);
+		}
+		return builder.create();
+	}
+
+	private static class GmtDateTypeAdapter implements JsonSerializer<Date>, JsonDeserializer<Date> {
+		private final DateFormat dateFormat;
+
+		private GmtDateTypeAdapter() {
+			dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US);
+			dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
+		}
+
+		@Override
+		public synchronized JsonElement serialize(Date date, Type type,
+				JsonSerializationContext jsonSerializationContext) {
+			synchronized (dateFormat) {
+				String dateFormatAsString = dateFormat.format(date);
+				return new JsonPrimitive(dateFormatAsString);
+			}
+		}
+
+		@Override
+		public synchronized Date deserialize(JsonElement jsonElement, Type type,
+				JsonDeserializationContext jsonDeserializationContext) {
+			try {
+				synchronized (dateFormat) {
+					Date date = dateFormat.parse(jsonElement.getAsString());					
+					return new Date((date.getTime() / 1000) * 1000);
+				}
+			} catch (ParseException e) {
+				throw new JsonSyntaxException(jsonElement.getAsString(), e);
+			}
+		}
+	}
+	
+	private static class AccessPermissionTypeAdapter implements JsonSerializer<AccessPermission>, JsonDeserializer<AccessPermission> {
+
+		private AccessPermissionTypeAdapter() {
+		}
+
+		@Override
+		public synchronized JsonElement serialize(AccessPermission permission, Type type,
+				JsonSerializationContext jsonSerializationContext) {
+			return new JsonPrimitive(permission.code);
+		}
+
+		@Override
+		public synchronized AccessPermission deserialize(JsonElement jsonElement, Type type,
+				JsonDeserializationContext jsonDeserializationContext) {
+			return AccessPermission.fromCode(jsonElement.getAsString());					
+		}
+	}
+
+	public static class ExcludeField implements ExclusionStrategy {
+
+		private Class<?> c;
+		private String fieldName;
+
+		public ExcludeField(String fqfn) throws SecurityException, NoSuchFieldException,
+				ClassNotFoundException {
+			this.c = Class.forName(fqfn.substring(0, fqfn.lastIndexOf(".")));
+			this.fieldName = fqfn.substring(fqfn.lastIndexOf(".") + 1);
+		}
+
+		public boolean shouldSkipClass(Class<?> arg0) {
+			return false;
+		}
+
+		public boolean shouldSkipField(FieldAttributes f) {
+			return (f.getDeclaringClass() == c && f.getName().equals(fieldName));
+		}
+	}
+}
diff --git a/src/com/gitblit/utils/MarkdownUtils.java b/src/main/java/com/gitblit/utils/MarkdownUtils.java
similarity index 100%
rename from src/com/gitblit/utils/MarkdownUtils.java
rename to src/main/java/com/gitblit/utils/MarkdownUtils.java
diff --git a/src/com/gitblit/utils/MetricUtils.java b/src/main/java/com/gitblit/utils/MetricUtils.java
similarity index 100%
rename from src/com/gitblit/utils/MetricUtils.java
rename to src/main/java/com/gitblit/utils/MetricUtils.java
diff --git a/src/main/java/com/gitblit/utils/ObjectCache.java b/src/main/java/com/gitblit/utils/ObjectCache.java
new file mode 100644
index 0000000..692669f
--- /dev/null
+++ b/src/main/java/com/gitblit/utils/ObjectCache.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2011 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit.utils;
+
+import java.io.Serializable;
+import java.util.Date;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * Reusable coarse date-based object cache. The date precision is in
+ * 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 {
+
+	private static final long serialVersionUID = 1L;
+
+	private final Map<String, CachedObject<X>> cache = new ConcurrentHashMap<String, CachedObject<X>>();
+
+	private class CachedObject<Y> {
+
+		public final String name;
+
+		private volatile Date date;
+
+		private volatile Y object;
+
+		CachedObject(String name) {
+			this.name = name;
+			date = new Date(0);
+		}
+
+		@Override
+		public String toString() {
+			return getClass().getSimpleName() + ": " + name;
+		}
+	}
+
+	public boolean hasCurrent(String name, Date date) {
+		return cache.containsKey(name) && cache.get(name).date.compareTo(date) == 0;
+	}
+
+	public Date getDate(String name) {
+		return cache.get(name).date;
+	}
+
+	public X getObject(String name) {
+		if (cache.containsKey(name)) {
+			return cache.get(name).object;
+		}
+		return null;
+	}
+
+	public void updateObject(String name, X object) {
+		this.updateObject(name, new Date(), object);
+	}
+
+	public void updateObject(String name, Date date, X object) {
+		CachedObject<X> obj;
+		if (cache.containsKey(name)) {
+			obj = cache.get(name);
+		} else {
+			obj = new CachedObject<X>(name);
+			cache.put(name, obj);
+		}
+		obj.date = date;
+		obj.object = object;
+	}
+
+	public X remove(String name) {
+		if (cache.containsKey(name)) {
+			return cache.remove(name).object;
+		}
+		return null;
+	}
+	
+	public int size() {
+		return cache.size();
+	}
+}
diff --git a/src/com/gitblit/utils/PatchFormatter.java b/src/main/java/com/gitblit/utils/PatchFormatter.java
similarity index 100%
rename from src/com/gitblit/utils/PatchFormatter.java
rename to src/main/java/com/gitblit/utils/PatchFormatter.java
diff --git a/src/main/java/com/gitblit/utils/RefLogUtils.java b/src/main/java/com/gitblit/utils/RefLogUtils.java
new file mode 100644
index 0000000..643fbc0
--- /dev/null
+++ b/src/main/java/com/gitblit/utils/RefLogUtils.java
@@ -0,0 +1,666 @@
+/*
+ * Copyright 2013 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit.utils;
+
+import java.io.IOException;
+import java.text.DateFormat;
+import java.text.MessageFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TimeZone;
+import java.util.TreeSet;
+
+import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException;
+import org.eclipse.jgit.api.errors.JGitInternalException;
+import org.eclipse.jgit.dircache.DirCache;
+import org.eclipse.jgit.dircache.DirCacheBuilder;
+import org.eclipse.jgit.dircache.DirCacheEntry;
+import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.lib.CommitBuilder;
+import org.eclipse.jgit.lib.FileMode;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectInserter;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.RefRename;
+import org.eclipse.jgit.lib.RefUpdate;
+import org.eclipse.jgit.lib.RefUpdate.Result;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.transport.ReceiveCommand;
+import org.eclipse.jgit.treewalk.CanonicalTreeParser;
+import org.eclipse.jgit.treewalk.TreeWalk;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.gitblit.Constants;
+import com.gitblit.models.DailyLogEntry;
+import com.gitblit.models.PathModel.PathChangeModel;
+import com.gitblit.models.RefLogEntry;
+import com.gitblit.models.RefModel;
+import com.gitblit.models.RepositoryCommit;
+import com.gitblit.models.UserModel;
+
+/**
+ * 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
+	 *            pattern.
+	 * @param pattern
+	 * @param objects
+	 */
+	private static void error(Throwable t, Repository repository, String pattern, Object... objects) {
+		List<Object> parameters = new ArrayList<Object>();
+		if (objects != null && objects.length > 0) {
+			for (Object o : objects) {
+				parameters.add(o);
+			}
+		}
+		if (repository != null) {
+			parameters.add(0, repository.getDirectory().getAbsolutePath());
+		}
+		LOGGER.error(MessageFormat.format(pattern, parameters.toArray()), t);
+	}
+
+	/**
+	 * 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
+	 */
+	public static RefModel getRefLogBranch(Repository repository) {
+		List<RefModel> refs = JGitUtils.getRefs(repository, com.gitblit.Constants.R_GITBLIT);
+		RefModel pushLog = null;
+		final String GB_PUSHES = "refs/gitblit/pushes";
+		for (RefModel ref : refs) {
+			if (ref.reference.getName().equals(GB_REFLOG)) {
+				return ref;
+			} else if (ref.reference.getName().equals(GB_PUSHES)) {
+				pushLog = ref;
+			}
+		}
+		if (pushLog != null) {
+			// rename refs/gitblit/pushes to refs/gitblit/reflog
+			RefRename cmd;
+			try {
+				cmd = repository.renameRef(GB_PUSHES, GB_REFLOG);
+				cmd.setRefLogIdent(new PersonIdent("Gitblit", "gitblit@localhost"));
+				cmd.setRefLogMessage("renamed " + GB_PUSHES + " => " + GB_REFLOG);
+				Result res = cmd.rename();
+				switch (res) {
+				case RENAMED:
+					return getRefLogBranch(repository);
+				default:
+					LOGGER.error("failed to rename " + GB_PUSHES + " => " + GB_REFLOG + " (" + res.name() + ")");
+				}
+			} catch (IOException e) {
+				LOGGER.error("failed to rename pushlog", e);
+			}
+		}
+		return null;
+	}
+	
+	private static UserModel newUserModelFrom(PersonIdent ident) {
+		String name = ident.getName();
+		String username;
+		String displayname;
+		if (name.indexOf('/') > -1) {
+			int slash = name.indexOf('/');
+			displayname = name.substring(0, slash);
+			username = name.substring(slash + 1);
+		} else {
+			displayname = name;
+			username = ident.getEmailAddress();
+		}
+		
+		UserModel user = new UserModel(username);
+		user.displayName = displayname;
+		user.emailAddress = ident.getEmailAddress();
+		return user;
+	}
+	
+	/**
+	 * Updates the reflog with the received commands.
+	 * 
+	 * @param user
+	 * @param repository
+	 * @param commands
+	 * @return true, if the update was successful
+	 */
+	public static boolean updateRefLog(UserModel user, Repository repository,
+			Collection<ReceiveCommand> commands) {
+		RefModel reflogBranch = getRefLogBranch(repository);
+		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();
+			try {
+				// Create the in-memory index of the push log entry
+				DirCache index = createIndex(repository, headId, commands);
+				ObjectId indexTreeId = index.writeTree(odi);
+
+				PersonIdent ident;
+				if (UserModel.ANONYMOUS.equals(user)) {
+					// anonymous push
+					ident = new PersonIdent(user.username + "/" + user.username, user.username);
+				} else {
+					// construct real pushing account
+					ident =	new PersonIdent(MessageFormat.format("{0}/{1}", user.getDisplayName(), user.username),
+						user.emailAddress == null ? user.username : user.emailAddress);
+				}
+
+				// Create a commit object
+				CommitBuilder commit = new CommitBuilder();
+				commit.setAuthor(ident);
+				commit.setCommitter(ident);
+				commit.setEncoding(Constants.ENCODING);
+				commit.setMessage(message);
+				commit.setParentId(headId);
+				commit.setTreeId(indexTreeId);
+
+				// Insert the commit into the repository
+				ObjectId commitId = odi.insert(commit);
+				odi.flush();
+
+				RevWalk revWalk = new RevWalk(repository);
+				try {
+					RevCommit revCommit = revWalk.parseCommit(commitId);
+					RefUpdate ru = repository.updateRef(GB_REFLOG);
+					ru.setNewObjectId(commitId);
+					ru.setExpectedOldObjectId(headId);
+					ru.setRefLogMessage("commit: " + revCommit.getShortMessage(), false);
+					Result rc = ru.forceUpdate();
+					switch (rc) {
+					case NEW:
+					case FORCED:
+					case FAST_FORWARD:
+						success = true;
+						break;
+					case REJECTED:
+					case LOCK_FAILURE:
+						throw new ConcurrentRefUpdateException(JGitText.get().couldNotLockHEAD,
+								ru.getRef(), rc);
+					default:
+						throw new JGitInternalException(MessageFormat.format(
+								JGitText.get().updatingRefFailed, GB_REFLOG, commitId.toString(),
+								rc));
+					}
+				} finally {
+					revWalk.release();
+				}
+			} finally {
+				odi.release();
+			}
+		} catch (Throwable t) {
+			error(t, repository, "Failed to commit reflog entry to {0}");
+		}
+		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, 
+			Collection<ReceiveCommand> commands) throws IOException {
+
+		DirCache inCoreIndex = DirCache.newInCore();
+		DirCacheBuilder dcBuilder = inCoreIndex.builder();
+		ObjectInserter inserter = repo.newObjectInserter();
+
+		long now = System.currentTimeMillis();
+		Set<String> ignorePaths = new TreeSet<String>();
+		try {
+			// add receive commands to the temporary index
+			for (ReceiveCommand command : commands) {
+				// use the ref names as the path names
+				String path = command.getRefName();
+				ignorePaths.add(path);
+
+				StringBuilder change = new StringBuilder();
+				change.append(command.getType().name()).append(' ');
+				switch (command.getType()) {
+				case CREATE:
+					change.append(ObjectId.zeroId().getName());
+					change.append(' ');
+					change.append(command.getNewId().getName());
+					break;
+				case UPDATE:
+				case UPDATE_NONFASTFORWARD:
+					change.append(command.getOldId().getName());
+					change.append(' ');
+					change.append(command.getNewId().getName());
+					break;
+				case DELETE:
+					change = null;
+					break;
+				}
+				if (change == null) {
+					// ref deleted
+					continue;
+				}
+				String content = change.toString();
+				
+				// create an index entry for this attachment
+				final DirCacheEntry dcEntry = new DirCacheEntry(path);
+				dcEntry.setLength(content.length());
+				dcEntry.setLastModified(now);
+				dcEntry.setFileMode(FileMode.REGULAR_FILE);
+
+				// insert object
+				dcEntry.setObjectId(inserter.insert(org.eclipse.jgit.lib.Constants.OBJ_BLOB, content.getBytes("UTF-8")));
+
+				// add to temporary in-core index
+				dcBuilder.add(dcEntry);
+			}
+
+			// Traverse HEAD to add all other paths
+			TreeWalk treeWalk = new TreeWalk(repo);
+			int hIdx = -1;
+			if (headId != null)
+				hIdx = treeWalk.addTree(new RevWalk(repo).parseTree(headId));
+			treeWalk.setRecursive(true);
+
+			while (treeWalk.next()) {
+				String path = treeWalk.getPathString();
+				CanonicalTreeParser hTree = null;
+				if (hIdx != -1)
+					hTree = treeWalk.getTree(hIdx, CanonicalTreeParser.class);
+				if (!ignorePaths.contains(path)) {
+					// add entries from HEAD for all other paths
+					if (hTree != null) {
+						// create a new DirCacheEntry with data retrieved from
+						// HEAD
+						final DirCacheEntry dcEntry = new DirCacheEntry(path);
+						dcEntry.setObjectId(hTree.getEntryObjectId());
+						dcEntry.setFileMode(hTree.getEntryFileMode());
+
+						// add to temporary in-core index
+						dcBuilder.add(dcEntry);
+					}
+				}
+			}
+
+			// release the treewalk
+			treeWalk.release();
+
+			// finish temporary in-core index used for this commit
+			dcBuilder.finish();
+		} finally {
+			inserter.release();
+		}
+		return inCoreIndex;
+	}
+	
+	public static List<RefLogEntry> getRefLog(String repositoryName, Repository repository) {
+		return getRefLog(repositoryName, repository, null, 0, -1);
+	}
+
+	public static List<RefLogEntry> getRefLog(String repositoryName, Repository repository, int maxCount) {
+		return getRefLog(repositoryName, repository, null, 0, maxCount);
+	}
+
+	public static List<RefLogEntry> getRefLog(String repositoryName, Repository repository, int offset, int maxCount) {
+		return getRefLog(repositoryName, repository, null, offset, maxCount);
+	}
+
+	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
+	 * @param offset
+	 * @param maxCount
+	 * 			if < 0, all pushes are returned.
+	 * @return a list of push log entries
+	 */
+	public static List<RefLogEntry> getRefLog(String repositoryName, Repository repository,
+			Date minimumDate, int offset, int maxCount) {
+		List<RefLogEntry> list = new ArrayList<RefLogEntry>();
+		RefModel ref = getRefLogBranch(repository);
+		if (ref == null) {
+			return list;
+		}
+		if (maxCount == 0) {
+			return list;
+		}
+		
+		Map<ObjectId, List<RefModel>> allRefs = JGitUtils.getAllRefs(repository);
+		List<RevCommit> pushes;
+		if (minimumDate == null) {
+			pushes = JGitUtils.getRevLog(repository, GB_REFLOG, offset, maxCount);
+		} else {
+			pushes = JGitUtils.getRevLog(repository, GB_REFLOG, minimumDate);
+		}
+		for (RevCommit push : pushes) {
+			if (push.getAuthorIdent().getName().equalsIgnoreCase("gitblit")) {
+				// skip gitblit/internal commits
+				continue;
+			}
+
+			UserModel user = newUserModelFrom(push.getAuthorIdent());
+			Date date = push.getAuthorIdent().getWhen();
+			
+			RefLogEntry log = new RefLogEntry(repositoryName, date, user);
+			list.add(log);
+			List<PathChangeModel> changedRefs = JGitUtils.getFilesInCommit(repository, push);
+			for (PathChangeModel change : changedRefs) {
+				switch (change.changeType) {
+				case DELETE:
+					log.updateRef(change.path, ReceiveCommand.Type.DELETE);
+					break;
+				case ADD:
+					log.updateRef(change.path, ReceiveCommand.Type.CREATE);
+				default:
+					String content = JGitUtils.getStringContent(repository, push.getTree(), change.path);
+					String [] fields = content.split(" ");
+					String oldId = fields[1];
+					String newId = fields[2];
+					log.updateRef(change.path, ReceiveCommand.Type.valueOf(fields[0]), oldId, newId);
+					List<RevCommit> pushedCommits = JGitUtils.getRevLog(repository, oldId, newId);
+					for (RevCommit pushedCommit : pushedCommits) {
+						RepositoryCommit repoCommit = log.addCommit(change.path, pushedCommit);
+						if (repoCommit != null) {
+							repoCommit.setRefs(allRefs.get(pushedCommit.getId()));
+						}
+					}
+				}
+			}
+		}
+		Collections.sort(list);
+		return list;
+	}
+
+	/**
+	 * Returns the list of pushes separated by ref (e.g. each ref has it's own
+	 * PushLogEntry object).
+	 *  
+	 * @param repositoryName
+	 * @param repository
+	 * @param maxCount
+	 * @return a list of push log entries separated by ref
+	 */
+	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
+	 * @param maxCount
+	 * @return a list of push log entries separated by ref
+	 */
+	public static List<RefLogEntry> getLogByRef(String repositoryName, Repository repository,  int offset,
+			int maxCount) {
+		// break the push log into ref push logs and then merge them back into a list
+		Map<String, List<RefLogEntry>> refMap = new HashMap<String, List<RefLogEntry>>();
+        List<RefLogEntry> refLog = getRefLog(repositoryName, repository, offset, maxCount);
+		for (RefLogEntry entry : refLog) {
+			for (String ref : entry.getChangedRefs()) {
+				if (!refMap.containsKey(ref)) {
+					refMap.put(ref, new ArrayList<RefLogEntry>());
+				}
+				
+				// construct new ref-specific ref change entry
+				RefLogEntry refChange;
+				if (entry instanceof DailyLogEntry) {
+					// simulated push log from commits grouped by date
+					refChange = new DailyLogEntry(entry.repository, entry.date);
+				} else {
+					// real push log entry
+					refChange = new RefLogEntry(entry.repository, entry.date, entry.user);
+				}
+				refChange.updateRef(ref, entry.getChangeType(ref), entry.getOldId(ref), entry.getNewId(ref));
+				refChange.addCommits(entry.getCommits(ref));
+				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
+	 * @return a list of ref log entries separated by ref
+	 */
+	public static List<RefLogEntry> getLogByRef(String repositoryName, Repository repository,  Date minimumDate) {
+		// break the push log into ref push logs and then merge them back into a list
+		Map<String, List<RefLogEntry>> refMap = new HashMap<String, List<RefLogEntry>>();
+		List<RefLogEntry> pushes = getRefLog(repositoryName, repository, minimumDate);
+		for (RefLogEntry push : pushes) {
+			for (String ref : push.getChangedRefs()) {
+				if (!refMap.containsKey(ref)) {
+					refMap.put(ref, new ArrayList<RefLogEntry>());
+				}
+
+                // construct new ref-specific push log entry
+                RefLogEntry refPush = new RefLogEntry(push.repository, push.date, push.user);
+                refPush.updateRef(ref, push.getChangeType(ref), push.getOldId(ref), push.getNewId(ref));
+				refPush.addCommits(push.getCommits(ref));
+				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;
+	}
+
+    /**
+     * Returns a commit log grouped by day.
+     *
+     * @param repositoryName
+     * @param repository
+     * @param minimumDate
+     * @param offset
+     * @param maxCount
+     * 			if < 0, all pushes are returned.
+     * @param the timezone to use when aggregating commits by date
+     * @return a list of grouped commit log entries
+     */
+    public static List<DailyLogEntry> getDailyLog(String repositoryName, Repository repository,
+                                                 Date minimumDate, int offset, int maxCount,
+                                                 TimeZone timezone) {
+        DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
+		df.setTimeZone(timezone);
+
+        Map<ObjectId, List<RefModel>> allRefs = JGitUtils.getAllRefs(repository);
+        Map<String, DailyLogEntry> tags = new HashMap<String, DailyLogEntry>();
+        Map<String, DailyLogEntry> pulls = new HashMap<String, DailyLogEntry>();
+        Map<String, DailyLogEntry> dailydigests = new HashMap<String, DailyLogEntry>();
+        String linearParent = null;
+        for (RefModel local : JGitUtils.getLocalBranches(repository, true, -1)) {
+        	if (!local.getDate().after(minimumDate)) {
+				// branch not recently updated
+        		continue;
+        	}
+            String branch = local.getName();
+            List<RepositoryCommit> commits = CommitCache.instance().getCommits(repositoryName, repository,  branch, minimumDate);
+            linearParent = null;
+            for (RepositoryCommit commit : commits) {
+            	if (linearParent != null) {
+            		if (!commit.getName().equals(linearParent)) {
+            			// only follow linear branch commits
+            			continue;
+            		}
+            	}
+                Date date = commit.getCommitDate();
+                String dateStr = df.format(date);
+                if (!dailydigests.containsKey(dateStr)) {
+                    dailydigests.put(dateStr, new DailyLogEntry(repositoryName, date));
+                }
+                DailyLogEntry digest = dailydigests.get(dateStr);
+                if (commit.getParentCount() == 0) {
+                	linearParent = null;
+                	digest.updateRef(branch, ReceiveCommand.Type.CREATE);
+                } else {
+                	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)) {
+                                // treat tags as special events in the log
+                                if (!tags.containsKey(dateStr)) {
+                        			UserModel tagUser = newUserModelFrom(commit.getAuthorIdent());
+                        			Date tagDate = commit.getAuthorIdent().getWhen();
+                        			tags.put(dateStr, new DailyLogEntry(repositoryName, tagDate, tagUser));
+                                }
+                                RefLogEntry tagEntry = tags.get(dateStr);
+                                tagEntry.updateRef(ref.getName(), ReceiveCommand.Type.CREATE);
+                                RepositoryCommit rc = repoCommit.clone(ref.getName());
+                                tagEntry.addCommit(rc);
+                            } else if (ref.getName().startsWith(Constants.R_PULL)) {
+                                // treat pull requests as special events in the log
+                                if (!pulls.containsKey(dateStr)) {
+                        			UserModel commitUser = newUserModelFrom(commit.getAuthorIdent());
+                        			Date commitDate = commit.getAuthorIdent().getWhen();
+                        			pulls.put(dateStr, new DailyLogEntry(repositoryName, commitDate, commitUser));
+                                }
+                                RefLogEntry pullEntry = pulls.get(dateStr);
+                                pullEntry.updateRef(ref.getName(), ReceiveCommand.Type.CREATE);
+                                RepositoryCommit rc = repoCommit.clone(ref.getName());
+                                pullEntry.addCommit(rc);
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        List<DailyLogEntry> list = new ArrayList<DailyLogEntry>(dailydigests.values());
+        list.addAll(tags.values());
+        //list.addAll(pulls.values());
+        Collections.sort(list);
+        return list;
+    }
+
+    /**
+     * Returns the list of commits separated by ref (e.g. each ref has it's own
+     * PushLogEntry object for each day).
+     *
+     * @param repositoryName
+     * @param repository
+     * @param minimumDate
+     * @param the timezone to use when aggregating commits by date
+     * @return a list of push log entries separated by ref and date
+     */
+    public static List<DailyLogEntry> getDailyLogByRef(String repositoryName, Repository repository,
+    		Date minimumDate, TimeZone timezone) {
+        // break the push log into ref push logs and then merge them back into a list
+        Map<String, List<DailyLogEntry>> refMap = new HashMap<String, List<DailyLogEntry>>();
+        List<DailyLogEntry> pushes = getDailyLog(repositoryName, repository, minimumDate, 0, -1, timezone);
+        for (DailyLogEntry push : pushes) {
+            for (String ref : push.getChangedRefs()) {
+                if (!refMap.containsKey(ref)) {
+                    refMap.put(ref, new ArrayList<DailyLogEntry>());
+                }
+
+                // construct new ref-specific push log entry
+                DailyLogEntry refPush = new DailyLogEntry(push.repository, push.date, push.user);
+                refPush.updateRef(ref, push.getChangeType(ref), push.getOldId(ref), push.getNewId(ref));
+                refPush.addCommits(push.getCommits(ref));
+                refMap.get(ref).add(refPush);
+            }
+        }
+
+        // merge individual ref pushes into master list
+        List<DailyLogEntry> refPushLog = new ArrayList<DailyLogEntry>();
+        for (List<DailyLogEntry> refPush : refMap.values()) {
+        	for (DailyLogEntry entry : refPush) {
+        		if (entry.getCommitCount() > 0) {
+        			refPushLog.add(entry);
+        		}
+        	}
+        }
+
+        // sort ref push log
+        Collections.sort(refPushLog);
+
+        return refPushLog;
+    }
+}
diff --git a/src/main/java/com/gitblit/utils/RpcUtils.java b/src/main/java/com/gitblit/utils/RpcUtils.java
new file mode 100644
index 0000000..cd80fc4
--- /dev/null
+++ b/src/main/java/com/gitblit/utils/RpcUtils.java
@@ -0,0 +1,637 @@
+/*
+ * Copyright 2011 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit.utils;
+
+import java.io.IOException;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import com.gitblit.Constants;
+import com.gitblit.Constants.RpcRequest;
+import com.gitblit.GitBlitException.UnknownRequestException;
+import com.gitblit.models.FederationModel;
+import com.gitblit.models.FederationProposal;
+import com.gitblit.models.FederationSet;
+import com.gitblit.models.FeedModel;
+import com.gitblit.models.RegistrantAccessPermission;
+import com.gitblit.models.RepositoryModel;
+import com.gitblit.models.ServerSettings;
+import com.gitblit.models.ServerStatus;
+import com.gitblit.models.TeamModel;
+import com.gitblit.models.UserModel;
+import com.google.gson.reflect.TypeToken;
+
+/**
+ * Utility methods for rpc calls.
+ * 
+ * @author James Moger
+ * 
+ */
+public class RpcUtils {
+
+	public static final Type NAMES_TYPE = new TypeToken<Collection<String>>() {
+	}.getType();
+
+	public static final Type SETTINGS_TYPE = new TypeToken<Map<String, String>>() {
+	}.getType();
+
+	private static final Type REPOSITORIES_TYPE = new TypeToken<Map<String, RepositoryModel>>() {
+	}.getType();
+
+	private static final Type USERS_TYPE = new TypeToken<Collection<UserModel>>() {
+	}.getType();
+
+	private static final Type TEAMS_TYPE = new TypeToken<Collection<TeamModel>>() {
+	}.getType();
+
+	private static final Type REGISTRATIONS_TYPE = new TypeToken<Collection<FederationModel>>() {
+	}.getType();
+
+	private static final Type PROPOSALS_TYPE = new TypeToken<Collection<FederationProposal>>() {
+	}.getType();
+
+	private static final Type SETS_TYPE = new TypeToken<Collection<FederationSet>>() {
+	}.getType();
+
+	private static final Type BRANCHES_TYPE = new TypeToken<Map<String, Collection<String>>>() {
+	}.getType();
+
+	public static final Type REGISTRANT_PERMISSIONS_TYPE = new TypeToken<Collection<RegistrantAccessPermission>>() {
+	}.getType();
+
+	/**
+	 * 
+	 * @param remoteURL
+	 *            the url of the remote gitblit instance
+	 * @param req
+	 *            the rpc request type
+	 * @return
+	 */
+	public static String asLink(String remoteURL, RpcRequest req) {
+		return asLink(remoteURL, req, null);
+	}
+
+	/**
+	 * 
+	 * @param remoteURL
+	 *            the url of the remote gitblit instance
+	 * @param req
+	 *            the rpc request type
+	 * @param name
+	 *            the name of the actionable object
+	 * @return
+	 */
+	public static String asLink(String remoteURL, RpcRequest req, String name) {
+		if (remoteURL.length() > 0 && remoteURL.charAt(remoteURL.length() - 1) == '/') {
+			remoteURL = remoteURL.substring(0, remoteURL.length() - 1);
+		}
+		if (req == null) {
+			req = RpcRequest.LIST_REPOSITORIES;
+		}
+		return remoteURL + Constants.RPC_PATH + "?req=" + req.name().toLowerCase()
+				+ (name == null ? "" : ("&name=" + StringUtils.encodeURL(name)));
+	}
+
+	/**
+	 * Returns the version of the RPC protocol on the server.
+	 * 
+	 * @param serverUrl
+	 * @param account
+	 * @param password
+	 * @return the protocol version
+	 * @throws IOException
+	 */
+	public static int getProtocolVersion(String serverUrl, String account, char[] password)
+			throws IOException {
+		String url = asLink(serverUrl, RpcRequest.GET_PROTOCOL);
+		int protocol = 1;
+		try {
+			protocol = JsonUtils.retrieveJson(url, Integer.class, account, password);
+		} catch (UnknownRequestException e) {
+			// v0.7.0 (protocol 1) did not have this request type 
+		}
+		return protocol;
+	}
+
+	/**
+	 * Retrieves a map of the repositories at the remote gitblit instance keyed
+	 * by the repository clone url.
+	 * 
+	 * @param serverUrl
+	 * @param account
+	 * @param password
+	 * @return a map of cloneable repositories
+	 * @throws IOException
+	 */
+	public static Map<String, RepositoryModel> getRepositories(String serverUrl, String account,
+			char[] password) throws IOException {
+		String url = asLink(serverUrl, RpcRequest.LIST_REPOSITORIES);
+		Map<String, RepositoryModel> models = JsonUtils.retrieveJson(url, REPOSITORIES_TYPE,
+				account, password);
+		return models;
+	}
+
+	/**
+	 * Tries to pull the gitblit user accounts from the remote gitblit instance.
+	 * 
+	 * @param serverUrl
+	 * @param account
+	 * @param password
+	 * @return a collection of UserModel objects
+	 * @throws IOException
+	 */
+	public static List<UserModel> getUsers(String serverUrl, String account, char[] password)
+			throws IOException {
+		String url = asLink(serverUrl, RpcRequest.LIST_USERS);
+		Collection<UserModel> models = JsonUtils.retrieveJson(url, USERS_TYPE, account, password);
+		List<UserModel> list = new ArrayList<UserModel>(models);
+		return list;
+	}
+
+	/**
+	 * Tries to pull the gitblit team definitions from the remote gitblit
+	 * instance.
+	 * 
+	 * @param serverUrl
+	 * @param account
+	 * @param password
+	 * @return a collection of UserModel objects
+	 * @throws IOException
+	 */
+	public static List<TeamModel> getTeams(String serverUrl, String account, char[] password)
+			throws IOException {
+		String url = asLink(serverUrl, RpcRequest.LIST_TEAMS);
+		Collection<TeamModel> models = JsonUtils.retrieveJson(url, TEAMS_TYPE, account, password);
+		List<TeamModel> list = new ArrayList<TeamModel>(models);
+		return list;
+	}
+
+	/**
+	 * Create a repository on the Gitblit server.
+	 * 
+	 * @param repository
+	 * @param serverUrl
+	 * @param account
+	 * @param password
+	 * @return true if the action succeeded
+	 * @throws IOException
+	 */
+	public static boolean createRepository(RepositoryModel repository, String serverUrl,
+			String account, char[] password) throws IOException {
+		// ensure repository name ends with .git
+		if (!repository.name.endsWith(".git")) {
+			repository.name += ".git";
+		}
+		return doAction(RpcRequest.CREATE_REPOSITORY, null, repository, serverUrl, account,
+				password);
+
+	}
+
+	/**
+	 * Send a revised version of the repository model to the Gitblit server.
+	 * 
+	 * @param repository
+	 * @param serverUrl
+	 * @param account
+	 * @param password
+	 * @return true if the action succeeded
+	 * @throws IOException
+	 */
+	public static boolean updateRepository(String repositoryName, RepositoryModel repository,
+			String serverUrl, String account, char[] password) throws IOException {
+		return doAction(RpcRequest.EDIT_REPOSITORY, repositoryName, repository, serverUrl, account,
+				password);
+	}
+
+	/**
+	 * Delete a repository from the Gitblit server.
+	 * 
+	 * @param repository
+	 * @param serverUrl
+	 * @param account
+	 * @param password
+	 * @return true if the action succeeded
+	 * @throws IOException
+	 */
+	public static boolean deleteRepository(RepositoryModel repository, String serverUrl,
+			String account, char[] password) throws IOException {
+		return doAction(RpcRequest.DELETE_REPOSITORY, null, repository, serverUrl, account,
+				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, 
+			char[] password) throws IOException {
+		return doAction(RpcRequest.CLEAR_REPOSITORY_CACHE, null, null, serverUrl, account,
+				password);
+	}
+
+	/**
+	 * Create a user on the Gitblit server.
+	 * 
+	 * @param user
+	 * @param serverUrl
+	 * @param account
+	 * @param password
+	 * @return true if the action succeeded
+	 * @throws IOException
+	 */
+	public static boolean createUser(UserModel user, String serverUrl, String account,
+			char[] password) throws IOException {
+		return doAction(RpcRequest.CREATE_USER, null, user, serverUrl, account, password);
+
+	}
+
+	/**
+	 * Send a revised version of the user model to the Gitblit server.
+	 * 
+	 * @param user
+	 * @param serverUrl
+	 * @param account
+	 * @param password
+	 * @return true if the action succeeded
+	 * @throws IOException
+	 */
+	public static boolean updateUser(String username, UserModel user, String serverUrl,
+			String account, char[] password) throws IOException {
+		return doAction(RpcRequest.EDIT_USER, username, user, serverUrl, account, password);
+
+	}
+
+	/**
+	 * Deletes a user from the Gitblit server.
+	 * 
+	 * @param user
+	 * @param serverUrl
+	 * @param account
+	 * @param password
+	 * @return true if the action succeeded
+	 * @throws IOException
+	 */
+	public static boolean deleteUser(UserModel user, String serverUrl, String account,
+			char[] password) throws IOException {
+		return doAction(RpcRequest.DELETE_USER, null, user, serverUrl, account, password);
+	}
+
+	/**
+	 * Create a team on the Gitblit server.
+	 * 
+	 * @param team
+	 * @param serverUrl
+	 * @param account
+	 * @param password
+	 * @return true if the action succeeded
+	 * @throws IOException
+	 */
+	public static boolean createTeam(TeamModel team, String serverUrl, String account,
+			char[] password) throws IOException {
+		return doAction(RpcRequest.CREATE_TEAM, null, team, serverUrl, account, password);
+
+	}
+
+	/**
+	 * Send a revised version of the team model to the Gitblit server.
+	 * 
+	 * @param team
+	 * @param serverUrl
+	 * @param account
+	 * @param password
+	 * @return true if the action succeeded
+	 * @throws IOException
+	 */
+	public static boolean updateTeam(String teamname, TeamModel team, String serverUrl,
+			String account, char[] password) throws IOException {
+		return doAction(RpcRequest.EDIT_TEAM, teamname, team, serverUrl, account, password);
+
+	}
+
+	/**
+	 * Deletes a team from the Gitblit server.
+	 * 
+	 * @param team
+	 * @param serverUrl
+	 * @param account
+	 * @param password
+	 * @return true if the action succeeded
+	 * @throws IOException
+	 */
+	public static boolean deleteTeam(TeamModel team, String serverUrl, String account,
+			char[] password) throws IOException {
+		return doAction(RpcRequest.DELETE_TEAM, null, team, serverUrl, account, password);
+	}
+
+	/**
+	 * Retrieves the list of users that can access the specified repository.
+	 * 
+	 * @param repository
+	 * @param serverUrl
+	 * @param account
+	 * @param password
+	 * @return list of members
+	 * @throws IOException
+	 */
+	public static List<String> getRepositoryMembers(RepositoryModel repository, String serverUrl,
+			String account, char[] password) throws IOException {
+		String url = asLink(serverUrl, RpcRequest.LIST_REPOSITORY_MEMBERS, repository.name);
+		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
+	 * @param password
+	 * @return list of User-AccessPermission tuples
+	 * @throws IOException
+	 */
+	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);
+		return new ArrayList<RegistrantAccessPermission>(list);
+	}
+
+	/**
+	 * Sets the repository user access permissions
+	 * 
+	 * @param repository
+	 * @param permissions
+	 * @param serverUrl
+	 * @param account
+	 * @param password
+	 * @return true if the action succeeded
+	 * @throws IOException
+	 */
+	public static boolean setRepositoryMemberPermissions(RepositoryModel repository,
+			List<RegistrantAccessPermission> permissions, String serverUrl, String account, char[] password)
+			throws IOException {
+		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
+	 * @param password
+	 * @return list of teams
+	 * @throws IOException
+	 */
+	public static List<String> getRepositoryTeams(RepositoryModel repository, String serverUrl,
+			String account, char[] password) throws IOException {
+		String url = asLink(serverUrl, RpcRequest.LIST_REPOSITORY_TEAMS, repository.name);
+		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
+	 * @param password
+	 * @return list of Team-AccessPermission tuples
+	 * @throws IOException
+	 */
+	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);
+		return new ArrayList<RegistrantAccessPermission>(list);
+	}
+
+	/**
+	 * Sets the repository team access permissions
+	 * 
+	 * @param repository
+	 * @param permissions
+	 * @param serverUrl
+	 * @param account
+	 * @param password
+	 * @return true if the action succeeded
+	 * @throws IOException
+	 */
+	public static boolean setRepositoryTeamPermissions(RepositoryModel repository,
+			List<RegistrantAccessPermission> permissions, String serverUrl, String account, char[] password)
+			throws IOException {
+		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
+	 * @return a collection of FederationRegistration objects
+	 * @throws IOException
+	 */
+	public static List<FederationModel> getFederationRegistrations(String serverUrl,
+			String account, char[] password) throws IOException {
+		String url = asLink(serverUrl, RpcRequest.LIST_FEDERATION_REGISTRATIONS);
+		Collection<FederationModel> registrations = JsonUtils.retrieveJson(url, REGISTRATIONS_TYPE,
+				account, password);
+		List<FederationModel> list = new ArrayList<FederationModel>(registrations);
+		return list;
+	}
+
+	/**
+	 * 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
+	 * @return a collection of FederationRegistration objects
+	 * @throws IOException
+	 */
+	public static List<FederationModel> getFederationResultRegistrations(String serverUrl,
+			String account, char[] password) throws IOException {
+		String url = asLink(serverUrl, RpcRequest.LIST_FEDERATION_RESULTS);
+		Collection<FederationModel> registrations = JsonUtils.retrieveJson(url, REGISTRATIONS_TYPE,
+				account, password);
+		List<FederationModel> list = new ArrayList<FederationModel>(registrations);
+		return list;
+	}
+
+	/**
+	 * Retrieves the list of federation proposals.
+	 * 
+	 * @param serverUrl
+	 * @param account
+	 * @param password
+	 * @return a collection of FederationProposal objects
+	 * @throws IOException
+	 */
+	public static List<FederationProposal> getFederationProposals(String serverUrl, String account,
+			char[] password) throws IOException {
+		String url = asLink(serverUrl, RpcRequest.LIST_FEDERATION_PROPOSALS);
+		Collection<FederationProposal> proposals = JsonUtils.retrieveJson(url, PROPOSALS_TYPE,
+				account, password);
+		List<FederationProposal> list = new ArrayList<FederationProposal>(proposals);
+		return list;
+	}
+
+	/**
+	 * Retrieves the list of federation repository sets.
+	 * 
+	 * @param serverUrl
+	 * @param account
+	 * @param password
+	 * @return a collection of FederationSet objects
+	 * @throws IOException
+	 */
+	public static List<FederationSet> getFederationSets(String serverUrl, String account,
+			char[] password) throws IOException {
+		String url = asLink(serverUrl, RpcRequest.LIST_FEDERATION_SETS);
+		Collection<FederationSet> sets = JsonUtils.retrieveJson(url, SETS_TYPE, account, password);
+		List<FederationSet> list = new ArrayList<FederationSet>(sets);
+		return list;
+	}
+
+	/**
+	 * Retrieves the settings of the Gitblit server.
+	 * 
+	 * @param serverUrl
+	 * @param account
+	 * @param password
+	 * @return an Settings object
+	 * @throws IOException
+	 */
+	public static ServerSettings getSettings(String serverUrl, String account, char[] password)
+			throws IOException {
+		String url = asLink(serverUrl, RpcRequest.LIST_SETTINGS);
+		ServerSettings settings = JsonUtils.retrieveJson(url, ServerSettings.class, account,
+				password);
+		return settings;
+	}
+
+	/**
+	 * Update the settings on the Gitblit server.
+	 * 
+	 * @param settings
+	 *            the settings to update
+	 * @param serverUrl
+	 * @param account
+	 * @param password
+	 * @return true if the action succeeded
+	 * @throws IOException
+	 */
+	public static boolean updateSettings(Map<String, String> settings, String serverUrl,
+			String account, char[] password) throws IOException {
+		return doAction(RpcRequest.EDIT_SETTINGS, null, settings, serverUrl, account, password);
+
+	}
+
+	/**
+	 * Retrieves the server status object.
+	 * 
+	 * @param serverUrl
+	 * @param account
+	 * @param password
+	 * @return an ServerStatus object
+	 * @throws IOException
+	 */
+	public static ServerStatus getStatus(String serverUrl, String account, char[] password)
+			throws IOException {
+		String url = asLink(serverUrl, RpcRequest.LIST_STATUS);
+		ServerStatus status = JsonUtils.retrieveJson(url, ServerStatus.class, account, password);
+		return status;
+	}
+
+	/**
+	 * Retrieves a map of local branches in the Gitblit server keyed by
+	 * repository.
+	 * 
+	 * @param serverUrl
+	 * @param account
+	 * @param password
+	 * @return
+	 * @throws IOException
+	 */
+	public static Map<String, Collection<String>> getBranches(String serverUrl, String account,
+			char[] password) throws IOException {
+		String url = asLink(serverUrl, RpcRequest.LIST_BRANCHES);
+		Map<String, Collection<String>> branches = JsonUtils.retrieveJson(url, BRANCHES_TYPE,
+				account, password);
+		return branches;
+	}
+
+	/**
+	 * Retrieves a list of available branch feeds in the Gitblit server.
+	 * 
+	 * @param serverUrl
+	 * @param account
+	 * @param password
+	 * @return
+	 * @throws IOException
+	 */
+	public static List<FeedModel> getBranchFeeds(String serverUrl, String account, char[] password)
+			throws IOException {
+		List<FeedModel> feeds = new ArrayList<FeedModel>();
+		Map<String, Collection<String>> allBranches = getBranches(serverUrl, account, password);
+		for (Map.Entry<String, Collection<String>> entry : allBranches.entrySet()) {
+			for (String branch : entry.getValue()) {
+				FeedModel feed = new FeedModel();
+				feed.repository = entry.getKey();
+				feed.branch = branch;
+				feeds.add(feed);
+			}
+		}
+		return feeds;
+	}
+
+	/**
+	 * Do the specified administrative action on the Gitblit server.
+	 * 
+	 * @param request
+	 * @param name
+	 *            the name of the object (may be null)
+	 * @param object
+	 * @param serverUrl
+	 * @param account
+	 * @param password
+	 * @return true if the action succeeded
+	 * @throws IOException
+	 */
+	protected static boolean doAction(RpcRequest request, String name, Object object,
+			String serverUrl, String account, char[] password) throws IOException {
+		String url = asLink(serverUrl, request, name);
+		String json = JsonUtils.toJsonString(object);
+		int resultCode = JsonUtils.sendJsonString(url, json, account, password);
+		return resultCode == 200;
+	}
+}
diff --git a/src/main/java/com/gitblit/utils/StringUtils.java b/src/main/java/com/gitblit/utils/StringUtils.java
new file mode 100644
index 0000000..cff3577
--- /dev/null
+++ b/src/main/java/com/gitblit/utils/StringUtils.java
@@ -0,0 +1,736 @@
+/*
+ * Copyright 2011 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit.utils;
+
+import java.io.ByteArrayOutputStream;
+import java.io.UnsupportedEncodingException;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.CharacterCodingException;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetDecoder;
+import java.nio.charset.IllegalCharsetNameException;
+import java.nio.charset.UnsupportedCharsetException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+
+/**
+ * Utility class of string functions.
+ * 
+ * @author James Moger
+ * 
+ */
+public class StringUtils {
+
+	public static final String MD5_TYPE = "MD5:";
+
+	public static final String COMBINED_MD5_TYPE = "CMD5:";
+
+	/**
+	 * Returns true if the string is null or empty.
+	 * 
+	 * @param value
+	 * @return true if string is null or empty
+	 */
+	public static boolean isEmpty(String value) {
+		return value == null || value.trim().length() == 0;
+	}
+
+	/**
+	 * Replaces carriage returns and line feeds with html line breaks.
+	 * 
+	 * @param string
+	 * @return plain text with html line breaks
+	 */
+	public static String breakLinesForHtml(String string) {
+		return string.replace("\r\n", "<br/>").replace("\r", "<br/>").replace("\n", "<br/>");
+	}
+
+	/**
+	 * Prepare text for html presentation. Replace sensitive characters with
+	 * html entities.
+	 * 
+	 * @param inStr
+	 * @param changeSpace
+	 * @return plain text escaped for html
+	 */
+	public static String escapeForHtml(String inStr, boolean changeSpace) {
+		StringBuilder retStr = new StringBuilder();
+		int i = 0;
+		while (i < inStr.length()) {
+			if (inStr.charAt(i) == '&') {
+				retStr.append("&amp;");
+			} else if (inStr.charAt(i) == '<') {
+				retStr.append("&lt;");
+			} else if (inStr.charAt(i) == '>') {
+				retStr.append("&gt;");
+			} else if (inStr.charAt(i) == '\"') {
+				retStr.append("&quot;");
+			} else if (changeSpace && inStr.charAt(i) == ' ') {
+				retStr.append("&nbsp;");
+			} else if (changeSpace && inStr.charAt(i) == '\t') {
+				retStr.append(" &nbsp; &nbsp;");
+			} else {
+				retStr.append(inStr.charAt(i));
+			}
+			i++;
+		}
+		return retStr.toString();
+	}
+
+	/**
+	 * Decode html entities back into plain text characters.
+	 * 
+	 * @param inStr
+	 * @return returns plain text from html
+	 */
+	public static String decodeFromHtml(String inStr) {
+		return inStr.replace("&amp;", "&").replace("&lt;", "<").replace("&gt;", ">")
+				.replace("&quot;", "\"").replace("&nbsp;", " ");
+	}
+
+	/**
+	 * Encodes a url parameter by escaping troublesome characters.
+	 * 
+	 * @param inStr
+	 * @return properly escaped url
+	 */
+	public static String encodeURL(String inStr) {
+		StringBuilder retStr = new StringBuilder();
+		int i = 0;
+		while (i < inStr.length()) {
+			if (inStr.charAt(i) == '/') {
+				retStr.append("%2F");
+			} else if (inStr.charAt(i) == ' ') {
+				retStr.append("%20");
+			} else {
+				retStr.append(inStr.charAt(i));
+			}
+			i++;
+		}
+		return retStr.toString();
+	}
+
+	/**
+	 * Flatten the list of strings into a single string with a space separator.
+	 * 
+	 * @param values
+	 * @return flattened list
+	 */
+	public static String flattenStrings(Collection<String> values) {
+		return flattenStrings(values, " ");
+	}
+
+	/**
+	 * Flatten the list of strings into a single string with the specified
+	 * separator.
+	 * 
+	 * @param values
+	 * @param separator
+	 * @return flattened list
+	 */
+	public static String flattenStrings(Collection<String> values, String separator) {
+		StringBuilder sb = new StringBuilder();
+		for (String value : values) {
+			sb.append(value).append(separator);
+		}
+		if (sb.length() > 0) {
+			// truncate trailing separator
+			sb.setLength(sb.length() - separator.length());
+		}
+		return sb.toString().trim();
+	}
+
+	/**
+	 * 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
+	 */
+	public static String trimString(String value, int max) {
+		if (value.length() <= max) {
+			return value;
+		}
+		return value.substring(0, max - 3) + "...";
+	}
+
+	/**
+	 * Left pad a string with the specified character, if the string length is
+	 * less than the specified length.
+	 * 
+	 * @param input
+	 * @param length
+	 * @param pad
+	 * @return left-padded string
+	 */
+	public static String leftPad(String input, int length, char pad) {
+		if (input.length() < length) {
+			StringBuilder sb = new StringBuilder();
+			for (int i = 0, len = length - input.length(); i < len; i++) {
+				sb.append(pad);
+			}
+			sb.append(input);
+			return sb.toString();
+		}
+		return input;
+	}
+
+	/**
+	 * Right pad a string with the specified character, if the string length is
+	 * less then the specified length.
+	 * 
+	 * @param input
+	 * @param length
+	 * @param pad
+	 * @return right-padded string
+	 */
+	public static String rightPad(String input, int length, char pad) {
+		if (input.length() < length) {
+			StringBuilder sb = new StringBuilder();
+			sb.append(input);
+			for (int i = 0, len = length - input.length(); i < len; i++) {
+				sb.append(pad);
+			}
+			return sb.toString();
+		}
+		return input;
+	}
+
+	/**
+	 * Calculates the SHA1 of the string.
+	 * 
+	 * @param text
+	 * @return sha1 of the string
+	 */
+	public static String getSHA1(String text) {
+		try {
+			byte[] bytes = text.getBytes("iso-8859-1");
+			return getSHA1(bytes);
+		} catch (UnsupportedEncodingException u) {
+			throw new RuntimeException(u);
+		}
+	}
+
+	/**
+	 * Calculates the SHA1 of the byte array.
+	 * 
+	 * @param bytes
+	 * @return sha1 of the byte array
+	 */
+	public static String getSHA1(byte[] bytes) {
+		try {
+			MessageDigest md = MessageDigest.getInstance("SHA-1");
+			md.update(bytes, 0, bytes.length);
+			byte[] digest = md.digest();
+			return toHex(digest);
+		} catch (NoSuchAlgorithmException t) {
+			throw new RuntimeException(t);
+		}
+	}
+
+	/**
+	 * Calculates the MD5 of the string.
+	 * 
+	 * @param string
+	 * @return md5 of the string
+	 */
+	public static String getMD5(String string) {
+		try {
+			return getMD5(string.getBytes("iso-8859-1"));
+		} catch (UnsupportedEncodingException u) {
+			throw new RuntimeException(u);
+		}
+	}
+	
+	/**
+	 * Calculates the MD5 of the string.
+	 * 
+	 * @param string
+	 * @return md5 of the string
+	 */
+	public static String getMD5(byte [] bytes) {
+		try {
+			MessageDigest md = MessageDigest.getInstance("MD5");
+			md.reset();
+			md.update(bytes);
+			byte[] digest = md.digest();
+			return toHex(digest);
+		} catch (NoSuchAlgorithmException t) {
+			throw new RuntimeException(t);
+		}
+	}
+
+	/**
+	 * 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) {
+				sb.append('0');
+			}
+			sb.append(Long.toString((int) bytes[i] & 0xff, 16));
+		}
+		return sb.toString();
+	}
+
+	/**
+	 * 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
+	 */
+	public static String getRootPath(String path) {
+		if (path.indexOf('/') > -1) {
+			return path.substring(0, path.lastIndexOf('/'));
+		}
+		return "";
+	}
+
+	/**
+	 * Returns the path remainder after subtracting the basePath from the
+	 * fullPath.
+	 * 
+	 * @param basePath
+	 * @param fullPath
+	 * @return the relative path
+	 */
+	public static String getRelativePath(String basePath, String fullPath) {
+		String bp = basePath.replace('\\', '/').toLowerCase();
+		String fp = fullPath.replace('\\', '/').toLowerCase();
+		if (fp.startsWith(bp)) {
+			String relativePath = fullPath.substring(basePath.length()).replace('\\', '/');
+			if (relativePath.length() > 0 && relativePath.charAt(0) == '/') {
+				relativePath = relativePath.substring(1);
+			}
+			return relativePath;
+		}
+		return fullPath;
+	}
+
+	/**
+	 * Splits the space-separated string into a list of strings.
+	 * 
+	 * @param value
+	 * @return list of strings
+	 */
+	public static List<String> getStringsFromValue(String value) {
+		return getStringsFromValue(value, " ");
+	}
+
+	/**
+	 * Splits the string into a list of string by the specified separator.
+	 * 
+	 * @param value
+	 * @param separator
+	 * @return list of strings
+	 */
+	public static List<String> getStringsFromValue(String value, String separator) {
+        List<String> strings = new ArrayList<String>();
+        try {
+            String[] chunks = value.split(separator + "(?=([^\"]*\"[^\"]*\")*[^\"]*$)");            
+            for (String chunk : chunks) {
+                chunk = chunk.trim();
+                if (chunk.length() > 0) {
+                    if (chunk.charAt(0) == '"' && chunk.charAt(chunk.length() - 1) == '"') {
+                        // strip double quotes
+                        chunk = chunk.substring(1, chunk.length() - 1).trim();
+                    }
+                    strings.add(chunk);
+                }
+            }
+        } catch (PatternSyntaxException e) {
+            throw new RuntimeException(e);
+        }
+        return strings;
+    }
+
+	/**
+	 * 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
+	 */
+	public static Character findInvalidCharacter(String name) {
+		char[] validChars = { '/', '.', '_', '-', '~' };
+		for (char c : name.toCharArray()) {
+			if (!Character.isLetterOrDigit(c)) {
+				boolean ok = false;
+				for (char vc : validChars) {
+					ok |= c == vc;
+				}
+				if (!ok) {
+					return c;
+				}
+			}
+		}
+		return null;
+	}
+
+	/**
+	 * 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
+	 */
+	public static boolean fuzzyMatch(String value, String pattern) {
+		if (value.equalsIgnoreCase(pattern)) {
+			return true;
+		}
+		if (pattern.contains("*")) {
+			boolean prefixMatches = false;
+			boolean suffixMatches = false;
+
+			int wildcard = pattern.indexOf('*');
+			String prefix = pattern.substring(0, wildcard).toLowerCase();
+			prefixMatches = value.toLowerCase().startsWith(prefix);
+
+			if (pattern.length() > (wildcard + 1)) {
+				String suffix = pattern.substring(wildcard + 1).toLowerCase();
+				suffixMatches = value.toLowerCase().endsWith(suffix);
+				return prefixMatches && suffixMatches;
+			}
+			return prefixMatches || suffixMatches;
+		}
+		return false;
+	}
+
+	/**
+	 * Compare two repository names for proper group sorting.
+	 * 
+	 * @param r1
+	 * @param r2
+	 * @return
+	 */
+	public static int compareRepositoryNames(String r1, String r2) {
+		// sort root repositories first, alphabetically
+		// then sort grouped repositories, alphabetically
+		r1 = r1.toLowerCase();
+		r2 = r2.toLowerCase();
+		int s1 = r1.indexOf('/');
+		int s2 = r2.indexOf('/');
+		if (s1 == -1 && s2 == -1) {
+			// neither grouped
+			return r1.compareTo(r2);
+		} else if (s1 > -1 && s2 > -1) {
+			// both grouped
+			return r1.compareTo(r2);
+		} else if (s1 == -1) {
+			return -1;
+		} else if (s2 == -1) {
+			return 1;
+		}
+		return 0;
+	}
+
+	/**
+	 * Sort grouped repository names.
+	 * 
+	 * @param list
+	 */
+	public static void sortRepositorynames(List<String> list) {
+		Collections.sort(list, new Comparator<String>() {
+			@Override
+			public int compare(String o1, String o2) {
+				return compareRepositoryNames(o1, o2);
+			}
+		});
+	}
+
+	public static String getColor(String value) {
+		int cs = 0;
+		for (char c : getMD5(value.toLowerCase()).toCharArray()) {
+			cs += c;
+		}
+		int n = (cs % 360);		
+		float hue = ((float) n) / 360;
+		return hsvToRgb(hue, 0.90f, 0.65f);
+	}
+
+	public static String hsvToRgb(float hue, float saturation, float value) {
+		int h = (int) (hue * 6);
+		float f = hue * 6 - h;
+		float p = value * (1 - saturation);
+		float q = value * (1 - f * saturation);
+		float t = value * (1 - (1 - f) * saturation);
+
+		switch (h) {
+		case 0:
+			return rgbToString(value, t, p);
+		case 1:
+			return rgbToString(q, value, p);
+		case 2:
+			return rgbToString(p, value, t);
+		case 3:
+			return rgbToString(p, q, value);
+		case 4:
+			return rgbToString(t, p, value);
+		case 5:
+			return rgbToString(value, p, q);
+		default:
+			throw new RuntimeException(
+					"Something went wrong when converting from HSV to RGB. Input was " + hue + ", "
+							+ saturation + ", " + value);
+		}
+	}
+
+	public static String rgbToString(float r, float g, float b) {
+		String rs = Integer.toHexString((int) (r * 256));
+		String gs = Integer.toHexString((int) (g * 256));
+		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
+	 */
+	public static String stripDotGit(String value) {
+		if (value.toLowerCase().endsWith(".git")) {
+			return value.substring(0, value.length() - 4);
+		}
+		return value;
+	}
+	
+	/**
+	 * Count the number of lines in a string.
+	 * 
+	 * @param value
+	 * @return the line count
+	 */
+	public static int countLines(String value) {
+		if (isEmpty(value)) {
+			return 0;
+		}
+		return value.split("\n").length;
+	}
+	
+	/**
+	 * Returns the file extension of a path.
+	 * 
+	 * @param path
+	 * @return a blank string or a file extension
+	 */
+	public static String getFileExtension(String path) {
+		int lastDot = path.lastIndexOf('.');
+		if (lastDot > -1) {
+			return path.substring(lastDot + 1);
+		}
+		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
+	 * @return a String with the replacements
+	 */
+	public static String replace(String inString, String oldPattern, String newPattern) {
+		StringBuilder sb = new StringBuilder();
+		int pos = 0; // our position in the old string
+		int index = inString.indexOf(oldPattern);
+		// the index of an occurrence we've found, or -1
+		int patLen = oldPattern.length();
+		while (index >= 0) {
+			sb.append(inString.substring(pos, index));
+			sb.append(newPattern);
+			pos = index + patLen;
+			index = inString.indexOf(oldPattern, pos);
+		}
+		sb.append(inString.substring(pos));
+		// 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
+	 */
+	public static String decodeString(byte [] content, String... charsets) {
+		Set<String> sets = new LinkedHashSet<String>();
+		if (!ArrayUtils.isEmpty(charsets)) {
+			sets.addAll(Arrays.asList(charsets));
+		}
+		String value = null;
+		sets.addAll(Arrays.asList("UTF-8", "ISO-8859-1", Charset.defaultCharset().name()));
+		for (String charset : sets) {
+			try {
+				Charset cs = Charset.forName(charset);
+				CharsetDecoder decoder = cs.newDecoder();
+				CharBuffer buffer = decoder.decode(ByteBuffer.wrap(content));
+				value = buffer.toString();
+				break;
+			} catch (CharacterCodingException e) {
+				// ignore and advance to the next charset
+			} catch (IllegalCharsetNameException e) {
+				// ignore illegal charset names
+			} catch (UnsupportedCharsetException e) {
+				// ignore unsupported charsets
+			}
+		}
+		if (value.startsWith("\uFEFF")) {
+			// strip UTF-8 BOM
+            return value.substring(1);
+        }
+		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
+	 */
+	public static String extractRepositoryPath(String url, String... urlpatterns) {
+		for (String urlPattern : urlpatterns) {
+			Pattern p = Pattern.compile(urlPattern);
+			Matcher m = p.matcher(url);
+			while (m.find()) {
+				String repositoryPath = m.group(1);
+				return repositoryPath;
+			}
+		}
+		// last resort
+		if (url.lastIndexOf('/') > -1) {
+			return url.substring(url.lastIndexOf('/') + 1);
+		}
+		return url;
+	}
+	
+	/**
+	 * Converts a string with \nnn sequences into a UTF-8 encoded string.
+	 * @param input
+	 * @return
+	 */
+	public static String convertOctal(String input) {
+		try {
+			ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+			Pattern p = Pattern.compile("(\\\\\\d{3})");
+			Matcher m = p.matcher(input);
+			int i = 0;
+			while (m.find()) {
+				bytes.write(input.substring(i, m.start()).getBytes("UTF-8"));
+				// replace octal encoded value
+				// strip leading \ character
+				String oct = m.group().substring(1);
+				bytes.write(Integer.parseInt(oct, 8));
+				i = m.end();			
+			}
+			if (bytes.size() == 0) {
+				// no octal matches
+				return input;
+			} else {
+				if (i < input.length()) {
+					// add remainder of string
+					bytes.write(input.substring(i).getBytes("UTF-8"));
+				}
+			}
+			return bytes.toString("UTF-8");
+		} catch (Exception e) {
+			e.printStackTrace();
+		}
+		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. 
+	 * 
+	 * @param path
+	 * @return the first element in the path
+	 */
+	public static String getFirstPathElement(String path) {
+		if (path.indexOf('/') > -1) {
+			return path.substring(0, path.indexOf('/')).trim();
+		}
+		return "";
+	}
+	
+	/**
+	 * Returns the last path element of a path string
+	 * 
+	 * @param path
+	 * @return the last element in the path
+	 */
+	public static String getLastPathElement(String path) {
+		if (path.indexOf('/') > -1) {
+			return path.substring(path.lastIndexOf('/') + 1);
+		}
+		return path;
+	}
+	
+	/**
+	 * Variation of String.matches() which disregards case issues.
+	 * 
+	 * @param regex
+	 * @param input
+	 * @return true if the pattern matches
+	 */
+	public static boolean matchesIgnoreCase(String input, String regex) {
+		Pattern p = Pattern.compile(regex, Pattern.CASE_INSENSITIVE);
+		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
+	 */
+	public static String removeNewlines(String input) {
+		if (input == null) {
+			return "";
+		}
+		return input.replace('\n',' ').replace('\r',  ' ').trim();
+	}
+}
\ No newline at end of file
diff --git a/src/com/gitblit/utils/SyndicationUtils.java b/src/main/java/com/gitblit/utils/SyndicationUtils.java
similarity index 100%
rename from src/com/gitblit/utils/SyndicationUtils.java
rename to src/main/java/com/gitblit/utils/SyndicationUtils.java
diff --git a/src/com/gitblit/utils/TicgitUtils.java b/src/main/java/com/gitblit/utils/TicgitUtils.java
similarity index 100%
rename from src/com/gitblit/utils/TicgitUtils.java
rename to src/main/java/com/gitblit/utils/TicgitUtils.java
diff --git a/src/main/java/com/gitblit/utils/TimeUtils.java b/src/main/java/com/gitblit/utils/TimeUtils.java
new file mode 100644
index 0000000..9b5927c
--- /dev/null
+++ b/src/main/java/com/gitblit/utils/TimeUtils.java
@@ -0,0 +1,353 @@
+/*
+ * Copyright 2011 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit.utils;
+
+import java.text.MessageFormat;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.ResourceBundle;
+import java.util.TimeZone;
+
+/**
+ * Utility class of time functions.
+ * 
+ * @author James Moger
+ * 
+ */
+public class TimeUtils {
+	public static final long MIN = 1000 * 60L;
+
+	public static final long HALFHOUR = MIN * 30L;
+
+	public static final long ONEHOUR = HALFHOUR * 2;
+
+	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;
+	}
+
+	/**
+	 * Returns true if date is today.
+	 * 
+	 * @param date
+	 * @return true if date is today
+	 */
+	public static boolean isToday(Date date, TimeZone timezone) {
+		Date now = new Date();
+		SimpleDateFormat df = new SimpleDateFormat("yyyyMMdd");
+		if (timezone != null) {
+			df.setTimeZone(timezone);
+		}
+		return df.format(now).equals(df.format(date));
+	}
+
+	/**
+	 * Returns true if date is yesterday.
+	 * 
+	 * @param date
+	 * @return true if date is yesterday
+	 */
+	public static boolean isYesterday(Date date, TimeZone timezone) {
+		Calendar cal = Calendar.getInstance();
+		cal.setTime(new Date());
+		cal.add(Calendar.DATE, -1);
+		SimpleDateFormat df = new SimpleDateFormat("yyyyMMdd");
+		if (timezone != null) {
+			df.setTimeZone(timezone);
+		}
+		return df.format(cal.getTime()).equals(df.format(date));
+	}
+
+	/**
+	 * 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
+	 */
+	public String duration(int days) {
+		if (days <= 60) {
+			return (days > 1 ? translate(days, "gb.duration.days", "{0} days") : translate("gb.duration.oneDay", "1 day"));
+		} else if (days < 365) {
+			int rem = days % 30;
+			return translate(((days / 30) + (rem >= 15 ? 1 : 0)), "gb.duration.months", "{0} months");
+		} else {
+			int years = days / 365;
+			int rem = days % 365;
+			String yearsString = (years > 1 ? translate(years, "gb.duration.years", "{0} years") : translate("gb.duration.oneYear", "1 year"));
+			if (rem < 30) {
+				if (rem == 0) {
+					return yearsString;
+				} else {
+					return yearsString + (rem >= 15 ? (", " + translate("gb.duration.oneMonth", "1 month")): "");
+				}
+			} else {
+				int months = rem / 30;
+				int remDays = rem % 30;
+				if (remDays >= 15) {
+					months++;
+				}
+				String monthsString = yearsString + ", "
+						+ (months > 1 ? translate(months, "gb.duration.months", "{0} months") : translate("gb.duration.oneMonth", "1 month"));
+				return monthsString;
+			}
+		}
+	}
+
+	/**
+	 * Returns the number of minutes ago between the start time and the end
+	 * time.
+	 * 
+	 * @param date
+	 * @param endTime
+	 * @param roundup
+	 * @return difference in minutes
+	 */
+	public static int minutesAgo(Date date, long endTime, boolean roundup) {
+		long diff = endTime - date.getTime();
+		int mins = (int) (diff / MIN);
+		if (roundup && (diff % MIN) >= 30) {
+			mins++;
+		}
+		return mins;
+	}
+
+	/**
+	 * Return the difference in minutes between now and the date.
+	 * 
+	 * @param date
+	 * @param roundup
+	 * @return minutes ago
+	 */
+	public static int minutesAgo(Date date, boolean roundup) {
+		return minutesAgo(date, System.currentTimeMillis(), roundup);
+	}
+
+	/**
+	 * Return the difference in hours between now and the date.
+	 * 
+	 * @param date
+	 * @param roundup
+	 * @return hours ago
+	 */
+	public static int hoursAgo(Date date, boolean roundup) {
+		long diff = System.currentTimeMillis() - date.getTime();
+		int hours = (int) (diff / ONEHOUR);
+		if (roundup && (diff % ONEHOUR) >= HALFHOUR) {
+			hours++;
+		}
+		return hours;
+	}
+
+	/**
+	 * Return the difference in days between now and the date.
+	 * 
+	 * @param date
+	 * @return days ago
+	 */
+	public static int daysAgo(Date date) {
+		long today = ONEDAY * (System.currentTimeMillis()/ONEDAY);
+		long day = ONEDAY * (date.getTime()/ONEDAY);
+		long diff = today - day;
+		int days = (int) (diff / ONEDAY);
+		return days;
+	}
+
+	public String today() {
+		return translate("gb.time.today", "today");
+	}
+
+	public String yesterday() {
+		return translate("gb.time.yesterday", "yesterday");
+	}
+
+	/**
+	 * Returns the string representation of the duration between now and the
+	 * date.
+	 * 
+	 * @param date
+	 * @return duration as a string
+	 */
+	public String timeAgo(Date date) {
+		return timeAgo(date, false);
+	}
+
+	/**
+	 * Returns the CSS class for the date based on its age from Now.
+	 * 
+	 * @param date
+	 * @return the css class
+	 */
+	public String timeAgoCss(Date date) {
+		return timeAgo(date, true);
+	}
+
+	/**
+	 * 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
+	 */
+	private String timeAgo(Date date, boolean css) {
+		if (isToday(date, timezone) || isYesterday(date, timezone)) {
+			int mins = minutesAgo(date, true);
+			if (mins >= 120) {
+				if (css) {
+					return "age1";
+				}
+				int hours = hoursAgo(date, true);
+				if (hours > 23) {
+					return yesterday();
+				} else {
+					return translate(hours, "gb.time.hoursAgo", "{0} hours ago");
+				}
+			}
+			if (css) {
+				return "age0";
+			}
+			if (mins > 2) {
+				return translate(mins, "gb.time.minsAgo", "{0} mins ago");
+			}
+			return translate("gb.time.justNow", "just now");
+		} else {
+			int days = daysAgo(date);
+			if (css) {
+				if (days <= 7) {
+					return "age2";
+				} if (days <= 30) {
+					return "age3";
+				} else {
+					return "age4";
+				}
+			}
+			if (days < 365) {
+				if (days <= 30) {
+					return translate(days, "gb.time.daysAgo", "{0} days ago");
+				} else if (days <= 90) {
+					int weeks = days / 7;
+					if (weeks == 12) {
+						return translate(3, "gb.time.monthsAgo", "{0} months ago");
+					} else {
+						return translate(weeks, "gb.time.weeksAgo", "{0} weeks ago");
+					}
+				}
+				int months = days / 30;
+				int weeks = (days % 30) / 7;
+				if (weeks >= 2) {
+					months++;
+				}
+				return translate(months, "gb.time.monthsAgo", "{0} months ago");
+			} else if (days == 365) {
+				return translate("gb.time.oneYearAgo", "1 year ago");
+			} else {
+				int yr = days / 365;
+				days = days % 365;
+				int months = (yr * 12) + (days / 30);
+				if (months > 23) {
+					return translate(yr, "gb.time.yearsAgo", "{0} years ago");
+				} else {
+					return translate(months, "gb.time.monthsAgo", "{0} months ago");
+				}
+			}
+		}
+	}
+	
+	public String inFuture(Date date) {
+		long diff = date.getTime() - System.currentTimeMillis();
+		if (diff > ONEDAY) {
+			double days = ((double) diff)/ONEDAY;
+			return translate((int) Math.round(days), "gb.time.inDays", "in {0} days");
+		} else {
+			double hours = ((double) diff)/ONEHOUR;
+			if (hours > 2) {
+				return translate((int) Math.round(hours), "gb.time.inHours", "in {0} hours");
+			} else {
+				int mins = (int) (diff/MIN);
+				return translate(mins, "gb.time.inMinutes", "in {0} minutes");
+			}
+		}
+	}
+	
+	private String translate(String key, String defaultValue) {
+		String value = defaultValue;
+		if (translation != null && translation.containsKey(key)) {
+			String aValue = translation.getString(key);
+			if (!StringUtils.isEmpty(aValue)) {
+				value = aValue;
+			}
+		}
+		return value;
+	}
+	
+	private String translate(int val, String key, String defaultPattern) {
+		String pattern = defaultPattern;
+		if (translation != null && translation.containsKey(key)) {
+			String aValue = translation.getString(key);
+			if (!StringUtils.isEmpty(aValue)) {
+				pattern = aValue;
+			}
+		}
+		return MessageFormat.format(pattern, val);
+	}
+
+	/**
+	 * Convert a frequency string into minutes.
+	 * 
+	 * @param frequency
+	 * @return minutes
+	 */
+	public static int convertFrequencyToMinutes(String frequency) {
+		// parse the frequency
+		frequency = frequency.toLowerCase();
+		int mins = 60;
+		if (!StringUtils.isEmpty(frequency)) {
+			try {
+				String str = frequency.trim();
+				if (frequency.indexOf(' ') > -1) {
+					str = str.substring(0, str.indexOf(' ')).trim();
+				}
+				mins = (int) Float.parseFloat(str);
+			} catch (NumberFormatException e) {
+			}
+			if (mins < 5) {
+				mins = 5;
+			}
+		}
+		if (frequency.indexOf("day") > -1) {
+			// convert to minutes
+			mins *= 1440;
+		} else if (frequency.indexOf("hour") > -1) {
+			// convert to minutes
+			mins *= 60;
+		}
+		return mins;
+	}
+}
diff --git a/src/com/gitblit/utils/X509Utils.java b/src/main/java/com/gitblit/utils/X509Utils.java
similarity index 100%
rename from src/com/gitblit/utils/X509Utils.java
rename to src/main/java/com/gitblit/utils/X509Utils.java
diff --git a/src/main/java/com/gitblit/wicket/AuthorizationStrategy.java b/src/main/java/com/gitblit/wicket/AuthorizationStrategy.java
new file mode 100644
index 0000000..e36a50e
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/AuthorizationStrategy.java
@@ -0,0 +1,85 @@
+/*
+ * 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.wicket;
+
+import org.apache.wicket.Component;
+import org.apache.wicket.RestartResponseException;
+import org.apache.wicket.authorization.IUnauthorizedComponentInstantiationListener;
+import org.apache.wicket.authorization.strategies.page.AbstractPageAuthorizationStrategy;
+
+import com.gitblit.GitBlit;
+import com.gitblit.Keys;
+import com.gitblit.models.UserModel;
+import com.gitblit.wicket.pages.BasePage;
+
+public class AuthorizationStrategy extends AbstractPageAuthorizationStrategy implements
+		IUnauthorizedComponentInstantiationListener {
+
+	public AuthorizationStrategy() {
+	}
+
+	@SuppressWarnings({ "unchecked", "rawtypes" })
+	@Override
+	protected boolean isPageAuthorized(Class pageClass) {
+		if (GitBlitWebApp.HOME_PAGE_CLASS.equals(pageClass)) {
+			// allow all requests to get to the HomePage with its inline
+			// authentication form
+			return true;
+		}
+
+		if (BasePage.class.isAssignableFrom(pageClass)) {
+			boolean authenticateView = GitBlit.getBoolean(Keys.web.authenticateViewPages, true);
+			boolean authenticateAdmin = GitBlit.getBoolean(Keys.web.authenticateAdminPages, true);
+			boolean allowAdmin = GitBlit.getBoolean(Keys.web.allowAdministration, true);
+
+			GitBlitWebSession session = GitBlitWebSession.get();
+			if (authenticateView && !session.isLoggedIn()) {
+				// authentication required
+				session.cacheRequest(pageClass);
+				return false;
+			}
+
+			UserModel user = session.getUser();
+			if (pageClass.isAnnotationPresent(RequiresAdminRole.class)) {
+				// admin page
+				if (allowAdmin) {
+					if (authenticateAdmin) {
+						// authenticate admin
+						if (user != null) {
+							return user.canAdmin();
+						}
+						return false;
+					} else {
+						// no admin authentication required
+						return true;
+					}
+				} else {
+					// admin prohibited
+					return false;
+				}
+			}
+		}
+		return true;
+	}
+
+	@Override
+	public void onUnauthorizedInstantiation(Component component) {
+		
+		if (component instanceof BasePage) {
+			throw new RestartResponseException(GitBlitWebApp.HOME_PAGE_CLASS);
+		}
+	}
+}
diff --git a/src/main/java/com/gitblit/wicket/CacheControl.java b/src/main/java/com/gitblit/wicket/CacheControl.java
new file mode 100644
index 0000000..f72fe3a
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/CacheControl.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2013 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit.wicket;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * 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;
+}
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/ExternalImage.java b/src/main/java/com/gitblit/wicket/ExternalImage.java
similarity index 100%
rename from src/com/gitblit/wicket/ExternalImage.java
rename to src/main/java/com/gitblit/wicket/ExternalImage.java
diff --git a/src/main/java/com/gitblit/wicket/GitBlitWebApp.java b/src/main/java/com/gitblit/wicket/GitBlitWebApp.java
new file mode 100644
index 0000000..74ccac7
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/GitBlitWebApp.java
@@ -0,0 +1,199 @@
+/*
+ * 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.wicket;
+
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+
+import org.apache.wicket.Application;
+import org.apache.wicket.Page;
+import org.apache.wicket.Request;
+import org.apache.wicket.Response;
+import org.apache.wicket.Session;
+import org.apache.wicket.markup.html.WebPage;
+import org.apache.wicket.protocol.http.WebApplication;
+
+import com.gitblit.GitBlit;
+import com.gitblit.Keys;
+import com.gitblit.utils.StringUtils;
+import com.gitblit.wicket.pages.ActivityPage;
+import com.gitblit.wicket.pages.BasePage;
+import com.gitblit.wicket.pages.BlamePage;
+import com.gitblit.wicket.pages.BlobDiffPage;
+import com.gitblit.wicket.pages.BlobPage;
+import com.gitblit.wicket.pages.BranchesPage;
+import com.gitblit.wicket.pages.CommitDiffPage;
+import com.gitblit.wicket.pages.CommitPage;
+import com.gitblit.wicket.pages.ComparePage;
+import com.gitblit.wicket.pages.DocsPage;
+import com.gitblit.wicket.pages.FederationRegistrationPage;
+import com.gitblit.wicket.pages.ForkPage;
+import com.gitblit.wicket.pages.ForksPage;
+import com.gitblit.wicket.pages.GitSearchPage;
+import com.gitblit.wicket.pages.GravatarProfilePage;
+import com.gitblit.wicket.pages.HistoryPage;
+import com.gitblit.wicket.pages.LogPage;
+import com.gitblit.wicket.pages.LogoutPage;
+import com.gitblit.wicket.pages.LuceneSearchPage;
+import com.gitblit.wicket.pages.MarkdownPage;
+import com.gitblit.wicket.pages.MetricsPage;
+import com.gitblit.wicket.pages.MyDashboardPage;
+import com.gitblit.wicket.pages.OverviewPage;
+import com.gitblit.wicket.pages.PatchPage;
+import com.gitblit.wicket.pages.ProjectPage;
+import com.gitblit.wicket.pages.ProjectsPage;
+import com.gitblit.wicket.pages.RawPage;
+import com.gitblit.wicket.pages.ReflogPage;
+import com.gitblit.wicket.pages.RepositoriesPage;
+import com.gitblit.wicket.pages.ReviewProposalPage;
+import com.gitblit.wicket.pages.SummaryPage;
+import com.gitblit.wicket.pages.TagPage;
+import com.gitblit.wicket.pages.TagsPage;
+import com.gitblit.wicket.pages.TicketPage;
+import com.gitblit.wicket.pages.TicketsPage;
+import com.gitblit.wicket.pages.TreePage;
+import com.gitblit.wicket.pages.UserPage;
+import com.gitblit.wicket.pages.UsersPage;
+
+public class GitBlitWebApp extends WebApplication {
+
+	public final static Class<? extends BasePage> HOME_PAGE_CLASS = MyDashboardPage.class;
+	
+	private final Map<String, CacheControl> cacheablePages = new HashMap<String, CacheControl>();
+	
+	@Override
+	public void init() {
+		super.init();
+
+		// Setup page authorization mechanism
+		boolean useAuthentication = GitBlit.getBoolean(Keys.web.authenticateViewPages, false)
+				|| GitBlit.getBoolean(Keys.web.authenticateAdminPages, false);
+		if (useAuthentication) {
+			AuthorizationStrategy authStrategy = new AuthorizationStrategy();
+			getSecuritySettings().setAuthorizationStrategy(authStrategy);
+			getSecuritySettings().setUnauthorizedComponentInstantiationListener(authStrategy);
+		}
+
+		// Grab Browser info (like timezone, etc)
+		if (GitBlit.getBoolean(Keys.web.useClientTimezone, false)) {
+			getRequestCycleSettings().setGatherExtendedBrowserInfo(true);
+		}
+
+		// configure the resource cache duration to 90 days for deployment
+		if (!GitBlit.isDebugMode()) {
+			getResourceSettings().setDefaultCacheDuration(90 * 86400);
+		}
+
+		// setup the standard gitweb-ish urls
+		mount("/repositories", RepositoriesPage.class);
+		mount("/overview", OverviewPage.class, "r", "h");
+		mount("/summary", SummaryPage.class, "r");
+		mount("/reflog", ReflogPage.class, "r", "h");
+		mount("/commits", LogPage.class, "r", "h");
+		mount("/log", LogPage.class, "r", "h");
+		mount("/tags", TagsPage.class, "r");
+		mount("/branches", BranchesPage.class, "r");
+		mount("/commit", CommitPage.class, "r", "h");
+		mount("/tag", TagPage.class, "r", "h");
+		mount("/tree", TreePage.class, "r", "h", "f");
+		mount("/blob", BlobPage.class, "r", "h", "f");
+		mount("/raw", RawPage.class, "r", "h", "f");
+		mount("/blobdiff", BlobDiffPage.class, "r", "h", "f");
+		mount("/commitdiff", CommitDiffPage.class, "r", "h");
+		mount("/compare", ComparePage.class, "r", "h");
+		mount("/patch", PatchPage.class, "r", "h", "f");
+		mount("/history", HistoryPage.class, "r", "h", "f");
+		mount("/search", GitSearchPage.class);
+		mount("/metrics", MetricsPage.class, "r");
+		mount("/blame", BlamePage.class, "r", "h", "f");
+		mount("/users", UsersPage.class);
+		mount("/logout", LogoutPage.class);
+
+		// setup ticket urls
+		mount("/tickets", TicketsPage.class, "r");
+		mount("/ticket", TicketPage.class, "r", "f");
+
+		// setup the markdown urls
+		mount("/docs", DocsPage.class, "r");
+		mount("/markdown", MarkdownPage.class, "r", "h", "f");
+
+		// federation urls
+		mount("/proposal", ReviewProposalPage.class, "t");
+		mount("/registration", FederationRegistrationPage.class, "u", "n");
+
+		mount("/activity", ActivityPage.class, "r", "h");
+		mount("/gravatar", GravatarProfilePage.class, "h");
+		mount("/lucene", LuceneSearchPage.class);
+		mount("/project", ProjectPage.class, "p");
+		mount("/projects", ProjectsPage.class);
+		mount("/user", UserPage.class, "user");
+		mount("/forks", ForksPage.class, "r");
+		mount("/fork", ForkPage.class, "r");
+	}
+
+	private void mount(String location, Class<? extends WebPage> clazz, String... parameters) {
+		if (parameters == null) {
+			parameters = new String[] {};
+		}
+		if (!GitBlit.getBoolean(Keys.web.mountParameters, true)) {
+			parameters = new String[] {};
+		}
+		mount(new GitblitParamUrlCodingStrategy(location, clazz, parameters));
+		
+		// map the mount point to the cache control definition 
+		if (clazz.isAnnotationPresent(CacheControl.class)) {
+			CacheControl cacheControl = clazz.getAnnotation(CacheControl.class);
+			cacheablePages.put(location.substring(1), cacheControl);
+		}
+	}
+
+	@Override
+	public Class<? extends Page> getHomePage() {
+		return HOME_PAGE_CLASS;
+	}
+	
+	public boolean isCacheablePage(String mountPoint) {
+		return cacheablePages.containsKey(mountPoint);
+	}
+
+	public CacheControl getCacheControl(String mountPoint) {
+		return cacheablePages.get(mountPoint);
+	}
+
+	@Override
+	public final Session newSession(Request request, Response response) {
+		GitBlitWebSession gitBlitWebSession = new GitBlitWebSession(request);
+
+		String forcedLocale = GitBlit.getString(Keys.web.forceDefaultLocale, null);
+		if (!StringUtils.isEmpty(forcedLocale)) {
+			gitBlitWebSession.setLocale(new Locale(forcedLocale));
+		}
+		return gitBlitWebSession;
+	}
+
+	@Override
+	public final String getConfigurationType() {
+		if (GitBlit.isDebugMode()) {
+			return Application.DEVELOPMENT;
+		}
+		return Application.DEPLOYMENT;
+	}
+
+	public static GitBlitWebApp get() {
+		return (GitBlitWebApp) WebApplication.get();
+	}
+}
diff --git a/src/main/java/com/gitblit/wicket/GitBlitWebApp.properties b/src/main/java/com/gitblit/wicket/GitBlitWebApp.properties
new file mode 100644
index 0000000..55d8987
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/GitBlitWebApp.properties
@@ -0,0 +1,504 @@
+gb.repository = repository
+gb.owner = owner
+gb.description = description
+gb.lastChange = last change
+gb.refs = refs
+gb.tag = tag
+gb.tags = tags
+gb.author = author
+gb.committer = committer
+gb.commit = commit
+gb.tree = tree
+gb.parent = parent
+gb.url = URL
+gb.history = history
+gb.raw = raw
+gb.object = object
+gb.ticketId = ticket id
+gb.ticketAssigned = assigned
+gb.ticketOpenDate = open date
+gb.ticketState = state
+gb.ticketComments = comments
+gb.view = view
+gb.local = local
+gb.remote = remote
+gb.branches = branches
+gb.patch = patch
+gb.diff = diff
+gb.log = log
+gb.moreLogs = more commits...
+gb.allTags = all tags...
+gb.allBranches = all branches...
+gb.summary = summary
+gb.ticket = ticket
+gb.newRepository = new repository
+gb.newUser = new user
+gb.commitdiff = commitdiff
+gb.tickets = tickets
+gb.pageFirst = first
+gb.pagePrevious = prev
+gb.pageNext = next
+gb.head = HEAD
+gb.blame = blame
+gb.login = login
+gb.logout = logout
+gb.username = username
+gb.password = password
+gb.tagger = tagger
+gb.moreHistory = more history...
+gb.difftocurrent = diff to current
+gb.search = search
+gb.searchForAuthor = Search for commits authored by
+gb.searchForCommitter = Search for commits committed by
+gb.addition = addition
+gb.modification = modification
+gb.deletion = deletion
+gb.rename = rename
+gb.metrics = metrics
+gb.stats = stats
+gb.markdown = markdown
+gb.changedFiles = changed files
+gb.filesAdded = {0} files added
+gb.filesModified = {0} files modified
+gb.filesDeleted = {0} files deleted
+gb.filesCopied = {0} files copied
+gb.filesRenamed = {0} files renamed
+gb.missingUsername = Missing Username
+gb.edit = edit
+gb.searchTypeTooltip = Select Search Type
+gb.searchTooltip = Search {0}
+gb.delete = delete
+gb.docs = docs
+gb.accessRestriction = access restriction
+gb.name = name
+gb.enableTickets = enable tickets
+gb.enableDocs = enable docs
+gb.save = save
+gb.showRemoteBranches = show remote branches
+gb.editUsers = edit users
+gb.confirmPassword = confirm password
+gb.restrictedRepositories = restricted repositories
+gb.canAdmin = can admin
+gb.notRestricted = anonymous view, clone, & push
+gb.pushRestricted = authenticated push
+gb.cloneRestricted = authenticated clone & push
+gb.viewRestricted = authenticated view, clone, & push
+gb.useTicketsDescription = readonly, distributed Ticgit issues
+gb.useDocsDescription = enumerates Markdown documentation in repository
+gb.showRemoteBranchesDescription = show remote branches
+gb.canAdminDescription = can administer Gitblit server
+gb.permittedUsers = permitted users
+gb.isFrozen = is frozen
+gb.isFrozenDescription = deny push operations
+gb.zip = zip
+gb.showReadme = show readme
+gb.showReadmeDescription = show a \"readme\" Markdown file on the summary page
+gb.nameDescription = use '/' to group repositories.  e.g. libraries/mycoollib.git
+gb.ownerDescription = the owner may edit repository settings
+gb.blob = blob
+gb.commitActivityTrend = commit activity trend
+gb.commitActivityDOW = commit activity by day of week
+gb.commitActivityAuthors = primary authors by commit activity
+gb.feed = feed
+gb.cancel = cancel
+gb.changePassword = change password
+gb.isFederated = is federated
+gb.federateThis = federate this repository
+gb.federateOrigin = federate the origin
+gb.excludeFromFederation = exclude from federation
+gb.excludeFromFederationDescription = block federated Gitblit instances from pulling this account
+gb.tokens = federation tokens
+gb.tokenAllDescription = all repositories, users, & settings
+gb.tokenUnrDescription = all repositories & users
+gb.tokenJurDescription = all repositories
+gb.federatedRepositoryDefinitions = repository definitions
+gb.federatedUserDefinitions = user definitions
+gb.federatedSettingDefinitions = setting definitions
+gb.proposals = federation proposals
+gb.received = received
+gb.type = type
+gb.token = token
+gb.repositories = repositories
+gb.proposal = proposal
+gb.frequency = frequency
+gb.folder = folder
+gb.lastPull = last pull
+gb.nextPull = next pull
+gb.inclusions = inclusions
+gb.exclusions = exclusions
+gb.registration = registration
+gb.registrations = federation registrations
+gb.sendProposal = propose
+gb.status = status
+gb.origin = origin
+gb.headRef = default branch (HEAD)
+gb.headRefDescription = change the ref that HEAD links to. e.g. refs/heads/master
+gb.federationStrategy = federation strategy
+gb.federationRegistration = federation registration
+gb.federationResults = federation pull results
+gb.federationSets = federation sets
+gb.message = message
+gb.myUrlDescription = the publicly accessible url for your Gitblit instance
+gb.destinationUrl = send to
+gb.destinationUrlDescription = the url of the Gitblit instance to send your proposal
+gb.users = users
+gb.federation = federation
+gb.error = error
+gb.refresh = refresh
+gb.browse = browse
+gb.clone = clone
+gb.filter = filter
+gb.create = create
+gb.servers = servers
+gb.recent = recent
+gb.available = available
+gb.selected = selected
+gb.size = size
+gb.downloading = downloading
+gb.loading = loading
+gb.starting = starting
+gb.general = general
+gb.settings = settings
+gb.manage = manage
+gb.lastLogin = last login
+gb.skipSizeCalculation = skip size calculation
+gb.skipSizeCalculationDescription = do not calculate the repository size (reduces page load time)
+gb.skipSummaryMetrics = skip summary metrics
+gb.skipSummaryMetricsDescription = do not calculate metrics on the summary page (reduces page load time)
+gb.accessLevel = access level
+gb.default = default
+gb.setDefault = set default
+gb.since = since
+gb.status = status
+gb.bootDate = boot date
+gb.servletContainer = servlet container
+gb.heapMaximum = maximum heap
+gb.heapAllocated = allocated heap
+gb.heapUsed = used heap
+gb.free = free
+gb.version = version
+gb.releaseDate = release date
+gb.date = date
+gb.activity = activity
+gb.subscribe = subscribe
+gb.branch = branch
+gb.maxHits = max hits
+gb.recentActivity = recent activity
+gb.recentActivityStats = last {0} days / {1} commits by {2} authors
+gb.recentActivityNone = last {0} days / none
+gb.dailyActivity = daily activity
+gb.activeRepositories = active repositories
+gb.activeAuthors = active authors
+gb.commits = commits
+gb.teams = teams
+gb.teamName = team name
+gb.teamMembers = team members
+gb.teamMemberships = team memberships
+gb.newTeam = new team
+gb.permittedTeams = permitted teams
+gb.emptyRepository = empty repository
+gb.repositoryUrl = repository url
+gb.mailingLists = mailing lists
+gb.preReceiveScripts = pre-receive scripts
+gb.postReceiveScripts = post-receive scripts
+gb.hookScripts = hook scripts
+gb.customFields = custom fields
+gb.customFieldsDescription = custom fields available to Groovy hooks
+gb.accessPermissions = access permissions
+gb.filters = filters
+gb.generalDescription = common settings
+gb.accessPermissionsDescription = restrict access by users and teams
+gb.accessPermissionsForUserDescription = set team memberships or grant access to specific restricted repositories
+gb.accessPermissionsForTeamDescription = set team members and grant access to specific restricted repositories
+gb.federationRepositoryDescription = share this repository with other Gitblit servers
+gb.hookScriptsDescription = run Groovy scripts on pushes to this Gitblit server
+gb.reset = reset
+gb.pages = pages
+gb.workingCopy = working copy
+gb.workingCopyWarning = this repository has a working copy and can not receive pushes
+gb.query = query
+gb.queryHelp = Standard query syntax is supported.<p/><p/>Please see <a target="_new" href="http://lucene.apache.org/core/old_versioned_docs/versions/3_5_0/queryparsersyntax.html">Lucene Query Parser Syntax</a> for details.
+gb.queryResults = results {0} - {1} ({2} hits)
+gb.noHits = no hits
+gb.authored = authored
+gb.committed = committed
+gb.indexedBranches = indexed branches
+gb.indexedBranchesDescription = select the branches to include in your Lucene index
+gb.noIndexedRepositoriesWarning = none of your repositories are configured for Lucene indexing
+gb.undefinedQueryWarning = query is undefined!
+gb.noSelectedRepositoriesWarning = please select one or more repositories!
+gb.luceneDisabled = Lucene indexing is disabled
+gb.failedtoRead = Failed to read
+gb.isNotValidFile = is not a valid file
+gb.failedToReadMessage = Failed to read default message from {0}!
+gb.passwordsDoNotMatch = Passwords do not match!
+gb.passwordTooShort = Password is too short. Minimum length is {0} characters.
+gb.passwordChanged = Password successfully changed.
+gb.passwordChangeAborted = Password change aborted.
+gb.pleaseSetRepositoryName = Please set repository name!
+gb.illegalLeadingSlash = Leading root folder references (/) are prohibited.
+gb.illegalRelativeSlash = Relative folder references (../) are prohibited.
+gb.illegalCharacterRepositoryName = Illegal character ''{0}'' in repository name!
+gb.selectAccessRestriction = Please select access restriction!
+gb.selectFederationStrategy = Please select federation strategy!
+gb.pleaseSetTeamName = Please enter a teamname!
+gb.teamNameUnavailable = Team name ''{0}'' is unavailable.
+gb.teamMustSpecifyRepository = A team must specify at least one repository.
+gb.teamCreated = New team ''{0}'' successfully created.
+gb.pleaseSetUsername = Please enter a username!
+gb.usernameUnavailable = Username ''{0}'' is unavailable.
+gb.combinedMd5Rename = Gitblit is configured for combined-md5 password hashing. You must enter a new password on account rename.
+gb.userCreated = New user ''{0}'' successfully created.
+gb.couldNotFindFederationRegistration = Could not find federation registration!
+gb.failedToFindGravatarProfile = Failed to find Gravatar profile for {0}
+gb.branchStats = {0} commits and {1} tags in {2}
+gb.repositoryNotSpecified = Repository not specified!
+gb.repositoryNotSpecifiedFor = Repository not specified for {0}!
+gb.canNotLoadRepository = Can not load repository
+gb.commitIsNull = Commit is null
+gb.unauthorizedAccessForRepository = Unauthorized access for repository
+gb.failedToFindCommit = Failed to find commit \"{0}\" in {1}!
+gb.couldNotFindFederationProposal = Could not find federation proposal!
+gb.invalidUsernameOrPassword = Invalid username or password!
+gb.OneProposalToReview = There is 1 federation proposal awaiting review.
+gb.nFederationProposalsToReview = There are {0} federation proposals awaiting review.
+gb.couldNotFindTag = Could not find tag {0}
+gb.couldNotCreateFederationProposal = Could not create federation proposal!
+gb.pleaseSetGitblitUrl = Please enter your Gitblit url!
+gb.pleaseSetDestinationUrl = Please enter a destination url for your proposal!
+gb.proposalReceived = Proposal successfully received by {0}.
+gb.noGitblitFound = Sorry, {0} could not find a Gitblit instance at {1}.
+gb.noProposals = Sorry, {0} is not accepting proposals at this time.
+gb.noFederation = Sorry, {0} is not configured to federate with any Gitblit instances.
+gb.proposalFailed = Sorry, {0} did not receive any proposal data!
+gb.proposalError = Sorry, {0} reports that an unexpected error occurred!
+gb.failedToSendProposal = Failed to send proposal!
+gb.userServiceDoesNotPermitAddUser = {0} does not permit adding a user account!
+gb.userServiceDoesNotPermitPasswordChanges = {0} does not permit password changes!
+gb.displayName = display name
+gb.emailAddress = email address
+gb.errorAdminLoginRequired = Administration requires a login
+gb.errorOnlyAdminMayCreateRepository = Only an administrator may create a repository
+gb.errorOnlyAdminOrOwnerMayEditRepository = Only an administrator or the owner may edit a repository
+gb.errorAdministrationDisabled = Administration is disabled
+gb.lastNDays = last {0} days
+gb.completeGravatarProfile = Complete profile on Gravatar.com
+gb.none = none
+gb.line = line
+gb.content = content
+gb.empty = empty
+gb.inherited = inherited
+gb.deleteRepository = Delete repository \"{0}\"?
+gb.repositoryDeleted = Repository ''{0}'' deleted.
+gb.repositoryDeleteFailed = Failed to delete repository ''{0}''!
+gb.deleteUser = Delete user \"{0}\"?
+gb.userDeleted = User ''{0}'' deleted.
+gb.userDeleteFailed = Failed to delete user ''{0}''!
+gb.time.justNow = just now
+gb.time.today = today
+gb.time.yesterday = yesterday
+gb.time.minsAgo = {0} mins ago
+gb.time.hoursAgo = {0} hours ago
+gb.time.daysAgo = {0} days ago
+gb.time.weeksAgo = {0} weeks ago
+gb.time.monthsAgo = {0} months ago
+gb.time.oneYearAgo = 1 year ago
+gb.time.yearsAgo = {0} years ago
+gb.duration.oneDay = 1 day
+gb.duration.days = {0} days
+gb.duration.oneMonth = 1 month
+gb.duration.months = {0} months
+gb.duration.oneYear = 1 year
+gb.duration.years = {0} years
+gb.authorizationControl = authorization control
+gb.allowAuthenticatedDescription = grant RW+ permission to all authenticated users
+gb.allowNamedDescription = grant fine-grained permissions to named users or teams
+gb.markdownFailure = Failed to parse Markdown content!
+gb.clearCache = clear cache
+gb.projects = projects
+gb.project = project
+gb.allProjects = all projects
+gb.copyToClipboard = copy to clipboard
+gb.fork = fork
+gb.forks = forks
+gb.forkRepository = fork {0}?
+gb.repositoryForked = {0} has been forked
+gb.repositoryForkFailed= fork has failed
+gb.personalRepositories = personal repositories
+gb.allowForks = allow forks
+gb.allowForksDescription = allow authorized users to fork this repository
+gb.forkedFrom = forked from
+gb.canFork = can fork
+gb.canForkDescription = can fork authorized repositories to personal repositories
+gb.myFork = view my fork
+gb.forksProhibited = forks prohibited
+gb.forksProhibitedWarning = this repository forbids forks
+gb.noForks = {0} has no forks
+gb.forkNotAuthorized = sorry, you are not authorized to fork {0}
+gb.forkInProgress = fork in progress
+gb.preparingFork = preparing your fork...
+gb.isFork = is fork
+gb.canCreate = can create
+gb.canCreateDescription = can create personal repositories
+gb.illegalPersonalRepositoryLocation = your personal repository must be located at \"{0}\"
+gb.verifyCommitter = verify committer
+gb.verifyCommitterDescription = require committer identity to match pushing Gitblt user account
+gb.verifyCommitterNote = all merges require "--no-ff" to enforce committer identity
+gb.repositoryPermissions = repository permissions
+gb.userPermissions = user permissions
+gb.teamPermissions = team permissions
+gb.add = add
+gb.noPermission = DELETE THIS PERMISSION
+gb.excludePermission = {0} (exclude)
+gb.viewPermission = {0} (view)
+gb.clonePermission = {0} (clone)
+gb.pushPermission = {0} (push)
+gb.createPermission = {0} (push, ref creation)
+gb.deletePermission = {0} (push, ref creation+deletion)
+gb.rewindPermission = {0} (push, ref creation+deletion+rewind)
+gb.permission = permission
+gb.regexPermission = this permission is set from regular expression \"{0}\"
+gb.accessDenied = access denied
+gb.busyCollectingGarbage = sorry, Gitblit is busy collecting garbage in {0}
+gb.gcPeriod = GC period
+gb.gcPeriodDescription = duration between garbage collections
+gb.gcThreshold = GC threshold
+gb.gcThresholdDescription = minimum total size of loose objects to trigger early garbage collection
+gb.ownerPermission = repository owner
+gb.administrator = admin
+gb.administratorPermission = Gitblit administrator
+gb.team = team
+gb.teamPermission = permission set by \"{0}\" team membership
+gb.missing = missing!
+gb.missingPermission = the repository for this permission is missing!
+gb.mutable = mutable
+gb.specified = specified
+gb.effective = effective
+gb.organizationalUnit = organizational unit
+gb.organization = organization
+gb.locality = locality
+gb.stateProvince = state or province
+gb.countryCode = country code
+gb.properties = properties
+gb.issued = issued
+gb.expires = expires
+gb.expired = expired
+gb.expiring = expiring
+gb.revoked = revoked
+gb.serialNumber = serial number
+gb.certificates = certificates
+gb.newCertificate = new certificate
+gb.revokeCertificate = revoke certificate
+gb.sendEmail = send email
+gb.passwordHint = password hint
+gb.ok = ok
+gb.invalidExpirationDate = invalid expiration date!
+gb.passwordHintRequired = password hint required!
+gb.viewCertificate = view certificate
+gb.subject = subject
+gb.issuer = issuer
+gb.validFrom = valid from
+gb.validUntil = valid until
+gb.publicKey = public key
+gb.signatureAlgorithm = signature algorithm
+gb.sha1FingerPrint = SHA-1 Fingerprint
+gb.md5FingerPrint = MD5 Fingerprint
+gb.reason = reason
+gb.revokeCertificateReason = Please select a reason for certificate revocation
+gb.unspecified = unspecified
+gb.keyCompromise = key compromise
+gb.caCompromise = CA compromise
+gb.affiliationChanged = affiliation changed
+gb.superseded = superseded
+gb.cessationOfOperation = cessation of operation
+gb.privilegeWithdrawn = privilege withdrawn
+gb.time.inMinutes = in {0} mins
+gb.time.inHours = in {0} hours
+gb.time.inDays = in {0} days
+gb.hostname = hostname
+gb.hostnameRequired = Please enter a hostname
+gb.newSSLCertificate = new server SSL certificate
+gb.newCertificateDefaults = new certificate defaults
+gb.duration = duration
+gb.certificateRevoked = Certificate {0,number,0} has been revoked
+gb.clientCertificateGenerated = Successfully generated new client certificate for {0}
+gb.sslCertificateGenerated = Successfully generated new server SSL certificate for {0}
+gb.newClientCertificateMessage = NOTE:\nThe 'password' is not the user's password, it is the password to protect the user's keystore.  This password is not saved so you must also enter a 'hint' which will be included in the user's README instructions.
+gb.certificate = certificate
+gb.emailCertificateBundle = email client certificate bundle
+gb.pleaseGenerateClientCertificate = Please generate a client certificate for {0}
+gb.clientCertificateBundleSent = Client certificate bundle for {0} sent
+gb.enterKeystorePassword = Please enter the Gitblit keystore password
+gb.warning = warning
+gb.jceWarning = Your Java Runtime Environment does not have the \"JCE Unlimited Strength Jurisdiction Policy\" files.\nThis will limit the length of passwords you may use to encrypt your keystores to 7 characters.\nThese policy files are an optional download from Oracle.\n\nWould you like to continue and generate the certificate infrastructure anyway?\n\nAnswering No will direct your browser to Oracle's download page so that you may download the policy files.
+gb.maxActivityCommits = max activity commits
+gb.maxActivityCommitsDescription = maximum number of commits to contribute to the Activity page
+gb.noMaximum = no maximum
+gb.attributes = attributes
+gb.serveCertificate = serve https with this certificate
+gb.sslCertificateGeneratedRestart = Successfully generated new server SSL certificate for {0}.\nYou must restart Gitblit to use the new certificate.\n\nIf you are launching with the '--alias' parameter you will have to set that to ''--alias {0}''.
+gb.validity = validity
+gb.siteName = site name
+gb.siteNameDescription = short, descriptive name of your server
+gb.excludeFromActivity = exclude from activity page
+gb.isSparkleshared = repository is Sparkleshared
+gb.owners = owners
+gb.sessionEnded = Session has been closed
+gb.closeBrowser = Please close the browser to properly end the session.
+gb.doesNotExistInTree = {0} does not exist in tree {1}
+gb.enableIncrementalPushTags = enable incremental push tags
+gb.useIncrementalPushTagsDescription = on push, automatically tag each branch tip with an incremental revision number
+gb.incrementalPushTagMessage = Auto-tagged [{0}] branch on push
+gb.externalPermissions = {0} access permissions are externally maintained
+gb.viewAccess = You do not have Gitblit read or write access
+gb.overview = overview
+gb.dashboard = dashboard
+gb.monthlyActivity = monthly activity
+gb.myProfile = my profile
+gb.compare = compare
+gb.manual = manual
+gb.from = from
+gb.to = to
+gb.at = at
+gb.of = of
+gb.in = in
+gb.moreChanges = all changes...
+gb.pushedNCommitsTo = pushed {0} commits to
+gb.pushedOneCommitTo = pushed 1 commit to
+gb.commitsTo = {0} commits to
+gb.oneCommitTo = 1 commit to
+gb.byNAuthors = by {0} authors
+gb.byOneAuthor = by {0}
+gb.viewComparison = view comparison of these {0} commits \u00bb
+gb.nMoreCommits = {0} more commits \u00bb
+gb.oneMoreCommit = 1 more commit \u00bb
+gb.pushedNewTag = pushed new tag
+gb.createdNewTag = created new tag
+gb.deletedTag = deleted tag
+gb.pushedNewBranch = pushed new branch
+gb.createdNewBranch = created new branch
+gb.deletedBranch = deleted branch
+gb.createdNewPullRequest = created pull request
+gb.mergedPullRequest = merged pull request
+gb.rewind = REWIND
+gb.star = star
+gb.unstar = unstar
+gb.stargazers = stargazers
+gb.starredRepositories = starred repositories
+gb.failedToUpdateUser = Failed to update user account!
+gb.myRepositories = my repositories
+gb.noActivity = there has been no activity in the last {0} days
+gb.findSomeRepositories = find some repositories
+gb.metricAuthorExclusions = author metric exclusions
+gb.myDashboard = my dashboard
+gb.failedToFindAccount = failed to find user account ''{0}''
+gb.reflog = reflog
+gb.active = active
+gb.starred = starred
+gb.owned = owned
+gb.starredAndOwned = starred & owned
+gb.reviewPatchset = review {0} patchset {1}
+gb.todaysActivityStats = today / {1} commits by {2} authors
+gb.todaysActivityNone = today / none
+gb.noActivityToday = there has been no activity today
+gb.anonymousUser= anonymous
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/GitBlitWebApp_es.properties b/src/main/java/com/gitblit/wicket/GitBlitWebApp_es.properties
new file mode 100644
index 0000000..07beda5
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/GitBlitWebApp_es.properties
@@ -0,0 +1,504 @@
+gb.repository = Repositorio
+gb.owner = Propietario
+gb.description = Descripci\u00F3n
+gb.lastChange = Actualizado
+gb.refs = Refs
+gb.tag = Etiqueta
+gb.tags = Etiquetas
+gb.author = Autor
+gb.committer = Consignador
+gb.commit = Consigna
+gb.tree = \u00C1rbol
+gb.parent = Antecesor
+gb.url = URL
+gb.history = Hist\u00F3rico
+gb.raw = Bruto
+gb.object = Objeto
+gb.ticketId = Id Ticket
+gb.ticketAssigned = Asignado
+gb.ticketOpenDate = Fecha de apertura
+gb.ticketState = Estado
+gb.ticketComments = Comentarios
+gb.view = Ver
+gb.local = Local
+gb.remote = Remoto
+gb.branches = Ramas
+gb.patch = Parche
+gb.diff = Dif.
+gb.log = Reg.
+gb.moreLogs = M\u00E1s Consignas...
+gb.allTags = Todas las Etiquetas...
+gb.allBranches = Todas las Ramas...
+gb.summary = Resumen
+gb.ticket = Ticket
+gb.newRepository = Nuevo Repositorio
+gb.newUser = Nuevo usuario
+gb.commitdiff = Dif. Consigna
+gb.tickets = Tickets
+gb.pageFirst = Primera
+gb.pagePrevious = Anterior
+gb.pageNext = Siguiente
+gb.head = HEAD
+gb.blame = Acuse
+gb.login = Idenfiticarse
+gb.logout = Salir
+gb.username = Usuario
+gb.password = Contrase\u00F1a
+gb.tagger = Etiquetador
+gb.moreHistory = M\u00E1s hist\u00F3ricos...
+gb.difftocurrent = Dif. con actual
+gb.search = Buscar
+gb.searchForAuthor = Buscar consignas por autor
+gb.searchForCommitter = Buscar consignas enviadas por
+gb.addition = Adici\u00F3n
+gb.modification = Modificaci\u00F3n
+gb.deletion = Eliminado
+gb.rename = Renombrar
+gb.metrics = Movimientos
+gb.stats = Estad&iacute;sticas
+gb.markdown = Markdown
+gb.changedFiles = Archivos cambiados 
+gb.filesAdded = {0} Archivos a\u00F1adidos
+gb.filesModified = {0} Archivos modificados 
+gb.filesDeleted = {0} Archivos eliminados 
+gb.filesCopied = {0} Archivos copiados 
+gb.filesRenamed = {0} Archivos renombrados 
+gb.missingUsername = Usuario omitido
+gb.edit = Editar
+gb.searchTypeTooltip = Seleccionar tipo de b\u00FAsqueda
+gb.searchTooltip = Buscar {0}
+gb.delete = Eliminar
+gb.docs = Docs.
+gb.accessRestriction = Restricci\u00F3n de acceso
+gb.name = Nombre
+gb.enableTickets = Habilitar tickets
+gb.enableDocs = Habilitar Docs.
+gb.save = Guardar
+gb.showRemoteBranches = Mostrar ramas remotas
+gb.editUsers = Editar usuarios
+gb.confirmPassword = Confirmar contrase\u00F1a
+gb.restrictedRepositories = Repositorios restringidos
+gb.canAdmin = Puede Administrar
+gb.notRestricted = An\u00F3nimos pueden Ver, clonar y empujar
+gb.pushRestricted = Autentificados pueden empujar
+gb.cloneRestricted = Autentificados pueden clonar y empujar
+gb.viewRestricted = Autentificados pueden Ver, clonar y empujar
+gb.useTicketsDescription = Distribuir problemas mediante Ticgit (s\u00F3lo lecura)
+gb.useDocsDescription = Enumerar documentaci\u00F3n Markdown en el Repositorio.
+gb.showRemoteBranchesDescription = Mostrar Ramas Remotas
+gb.canAdminDescription = Puede administrar el Servidor GitBlit
+gb.permittedUsers = Usuarios permitidos
+gb.isFrozen = Est\u00E1 congelado
+gb.isFrozenDescription = No se le puede empujar
+gb.zip = Zip
+gb.showReadme = Ver l\u00E9eme
+gb.showReadmeDescription = Mostrar el archivo \"l\u00E9eme\" de Markdown en la p\u00E1gina resumen
+gb.nameDescription = Usa '/' para agrupar repositorios. ej. librerias/mylibreria.git
+gb.ownerDescription = El propietario puede editar la configuraci\u00F3n del repositorio
+gb.blob = Objeto
+gb.commitActivityTrend = Tendencia de actividad del repositorio
+gb.commitActivityDOW = Actividad de consignas por d\u00EDa de la semana
+gb.commitActivityAuthors = Principales autores por actividad de consignas
+gb.feed = Sindicaci\u00F3n
+gb.cancel = Cancelar
+gb.changePassword = Cambiar contrase\u00F1a
+gb.isFederated = Est\u00E1 federado
+gb.federateThis = Federar este repositorio
+gb.federateOrigin = Federar desde el origen
+gb.excludeFromFederation = Excluir de la federaci\u00F3n
+gb.excludeFromFederationDescription = Bloquear a esta cuenta el recibir de instancias federadas de GitBlit
+gb.tokens = Tarjetas de federaci\u00F3n
+gb.tokenAllDescription = Todos los repositorios, usuarios y configuraciones
+gb.tokenUnrDescription = Todos los repositorios y usuarios
+gb.tokenJurDescription = Todos los repositorios
+gb.federatedRepositoryDefinitions = Definiciones del repositorio
+gb.federatedUserDefinitions = Definiciones del usuario
+gb.federatedSettingDefinitions = Definiciones de configuraci\u00F3n
+gb.proposals = Propuestas de federaci\u00F3n
+gb.received = Recibida
+gb.type = Tipo
+gb.token = Tarjeta
+gb.repositories = Repositorios
+gb.proposal = Propuesta
+gb.frequency = Frecuencia
+gb.folder = Carpeta
+gb.lastPull = \u00DAltimo recibido
+gb.nextPull = Siguiente recibido
+gb.inclusions = Inclusiones
+gb.exclusions = Exclusiones
+gb.registration = Registro
+gb.registrations = Registros de federaci\u00F3n
+gb.sendProposal = Proponer
+gb.status = Estado
+gb.origin = Origen
+gb.headRef = Rama por defecto (HEAD)
+gb.headRefDescription = Cambiar la Ref. a la que apunta HEAD ej. refs/heads/master
+gb.federationStrategy = Estrategia de federaci\u00F3n
+gb.federationRegistration = Registro de federaci\u00F3n
+gb.federationResults = Resultados de empujes federados
+gb.federationSets = Grupos de federaci\u00F3n
+gb.message = Mensaje
+gb.myUrlDescription = La URL p\u00FAblica y accesible de tu instancia de GitBlit
+gb.destinationUrl = Enviar a
+gb.destinationUrlDescription = La URL de la instancia de GitBlit a la que env\u00EDas tu propuesta
+gb.users = Usuarios
+gb.federation = Federaci\u00F3n
+gb.error = Error
+gb.refresh = Actualizar
+gb.browse = Buscar
+gb.clone = Clonar
+gb.filter = Filtrar
+gb.create = Crear
+gb.servers = Servidores
+gb.recent = Recientes
+gb.available = Disponible
+gb.selected = Seleccionados
+gb.size = Tama\u00F1o
+gb.downloading = Descargando
+gb.loading = Cargando
+gb.starting = Iniciando
+gb.general = General
+gb.settings = Configuraci\u00F3n
+gb.manage = Administrar
+gb.lastLogin = \u00DAltimo acceso
+gb.skipSizeCalculation = Saltar comprobaciones de tama\u00F1o
+gb.skipSizeCalculationDescription = No calcular el tama\u00F1o del repositorio (Reduce tiempo de carga de la p\u00E1gina)
+gb.skipSummaryMetrics = Saltar resumen de estad\u00EDsticas
+gb.skipSummaryMetricsDescription = No calcular estad\u00EDsticas (Reduce tiempo de carga de la p\u00E1gina)
+gb.accessLevel = Nivel de acceso
+gb.default = Predeterminado
+gb.setDefault = Poner predeterminado
+gb.since = Desde
+gb.status = Estado
+gb.bootDate = Fecha de inicio
+gb.servletContainer = Contenedor ServLet
+gb.heapMaximum = Pila m\u00E1xima
+gb.heapAllocated = Pila asignada
+gb.heapUsed = Pila usada
+gb.free = Libre
+gb.version = Versi\u00F3n
+gb.releaseDate = Fecha de lanzamiento
+gb.date = Fecha
+gb.activity = Actividad
+gb.subscribe = Suscribir
+gb.branch = Rama
+gb.maxHits = Coincidencias m\u00E1ximas
+gb.recentActivity = Actividad reciente
+gb.recentActivityStats = \u00DAltimo(s) {0} d\u00EDa(s) / {1} Consigna(s) de {2} Autor(es)
+gb.recentActivityNone = \u00DAltimo(s) {0} d\u00EDa(s) / Ninguna
+gb.dailyActivity = Actividad diaria
+gb.activeRepositories = Repositorios activos
+gb.activeAuthors = Autores activos
+gb.commits = Consignas
+gb.teams = Equipos
+gb.teamName = Nombre del Equipo
+gb.teamMembers = Suscriptores
+gb.teamMemberships = Inscripciones
+gb.newTeam = Nuevo Equipo
+gb.permittedTeams = Equipos permitidos
+gb.emptyRepository = Repositorio vac\u00EDo
+gb.repositoryUrl = URL del Repositorio
+gb.mailingLists = Listas de correo
+gb.preReceiveScripts = Scripts para pre-recibo
+gb.postReceiveScripts = Scripts para post-recibo
+gb.hookScripts = Scripts enganchados
+gb.customFields = Campos Propios
+gb.customFieldsDescription = Campos Propios disponibles para los engachados Groovy
+gb.accessPermissions = Permisos de acceso
+gb.filters = Filtros
+gb.generalDescription = Configuraciones comunes
+gb.accessPermissionsDescription = Restringir acceso a usuarios y Equipos
+gb.accessPermissionsForUserDescription = Modificar inscripciones y especificar acceso a repositorios o restringirlos
+gb.accessPermissionsForTeamDescription = A\u00F1ada miembros al Equipo y conceda o restrinja acceso a repositorios.
+gb.federationRepositoryDescription = Compartir este repositorio con otros servidores GitBlit
+gb.hookScriptsDescription = Scripts Groovy ha ejecutar cuando empujen a este servidor.
+gb.reset = Reinicializa
+gb.pages = P\u00E1ginas
+gb.workingCopy = Copia de trabajo
+gb.workingCopyWarning = Este repositorio tiene una copia de trabajo y no se le puede empujar
+gb.query = Consulta
+gb.queryHelp = Se admite la sintaxis de consulta est\u00E1ndar.<p/>Por favor, lee el: <a target="_new" href="http://lucene.apache.org/core/old_versioned_docs/versions/3_5_0/queryparsersyntax.html">Analizador sint\u00E1ctico de consultas de Lucene</a> para m\u00E1s detalles.
+gb.queryResults = Resultados {0} - {1} ({2} coincidencias)
+gb.noHits = Sin coincidencias
+gb.authored = Autor
+gb.committed = Consignado
+gb.indexedBranches = Ramas indexadas
+gb.indexedBranchesDescription = Selecciona las Ramas a incluir en tu \u00EDndice de Lucene
+gb.noIndexedRepositoriesWarning = Ninguno de tus repositorios est\u00E1 configurado para el indexado de Lucene
+gb.undefinedQueryWarning = \u00A1Consulta indefinida!
+gb.noSelectedRepositoriesWarning = \u00A1Por favor selecciona uno o m\u00E1s repositorios!
+gb.luceneDisabled = Indexado de Lucene deshabilitado
+gb.failedtoRead = Fallo de lectura
+gb.isNotValidFile = No es un archivo v\u00E1lido
+gb.failedToReadMessage = \u00A1Fallo al leer el mensaje por defecto de {0}!
+gb.passwordsDoNotMatch = \u00A1Las contrase\u00F1as no coinciden!
+gb.passwordTooShort = La contrase\u00F1a es muy corta. La longitud m\u00EDnima es de {0} caracteres.
+gb.passwordChanged = Contrase\u00F1a cambiada satisfactoriamente.
+gb.passwordChangeAborted = Cambio de contrase\u00F1a abortado.
+gb.pleaseSetRepositoryName = \u00A1Por favor introduce un nombre para el repositorio!
+gb.illegalLeadingSlash = Referencias a la carpeta ra\i00EDz (/) estu00E1n prohibidas.
+gb.illegalRelativeSlash = Referencias relativas a la carpeta (../) est\u00E1n prohibidas.
+gb.illegalCharacterRepositoryName = \u00A1Caracter ilegal ''{0}'' en el nombre del repositorio!
+gb.selectAccessRestriction = \u00A1Por favor selecciona la restricci\u00F3n de acceso!
+gb.selectFederationStrategy = \u00A1Por favor, selecciona la estrategia de federaci\u00F3n!
+gb.pleaseSetTeamName = \u00A1Por favor, introduce un nombre para el Equipo!
+gb.teamNameUnavailable = El nombre de Equipo ''{0}'' no est\u00E1 disponible.
+gb.teamMustSpecifyRepository = Debe especificar al menos un repositorio para el Equipo.
+gb.teamCreated = Nuevo Equipo ''{0}'' creado satisfactoriamente.
+gb.pleaseSetUsername = \u00A1Por favor, introduce un usuario!
+gb.usernameUnavailable = El usuario ''{0}'' no est\u00E1 disponible.
+gb.combinedMd5Rename = GitBlit est\u00E1 configurado para Hashes combinados md5. Debes introducir una nueva contrase\u00F1a para renombrar la cuenta.
+gb.userCreated = Nuevo usuario ''{0}'' creado satisfactoriamente.
+gb.couldNotFindFederationRegistration = \u00A1No se pudo encontrar el registro de federaci\u00F3n!
+gb.failedToFindGravatarProfile = Fallo al buscar el perfil Gravatar de {0}
+gb.branchStats = {0} consigna(s) y {1} etiqueta(s) en {2}
+gb.repositoryNotSpecified = /u00A1Repositorio no especificado!
+gb.repositoryNotSpecifiedFor = /u00A1Repositorio no especificado para {0}!
+gb.canNotLoadRepository = No se puede cargar el repositorio
+gb.commitIsNull = La consigna es nula
+gb.unauthorizedAccessForRepository = Acceso no autorizado al repositorio
+gb.failedToFindCommit = \u00A1Fallo al buscar la consigna \"{0}\" en {1} de {2} p\u00E1ginas!
+gb.couldNotFindFederationProposal = \u00A1No se puede encontrar una propuesta de federaci\u00F3n!
+gb.invalidUsernameOrPassword = \u00A1Usuario o contrase\u00F1a inv\u00E1lidos!
+gb.OneProposalToReview = Hay 1 petici\u00F3n de federaci\u00F3n esperando revisi\u00F3n.
+gb.nFederationProposalsToReview = Hay {0} peticiones de federaci\u00F3n esperando revisi\u00F3n.
+gb.couldNotFindTag = No se puede encontrar la etiqueta {0}
+gb.couldNotCreateFederationProposal = \u00A1No se puede crear una propuesta de federaci\u00F3n!
+gb.pleaseSetGitblitUrl = \u00A1Por favor, introduce la URL de tu GitBlit!
+gb.pleaseSetDestinationUrl = \u00A1Por favor, introduce la URL de destino para tu propuesta!
+gb.proposalReceived = Propuesta recibida satisfactoriamente por {0}.
+gb.noGitblitFound = Lo siento, {0} no puede encontrar una instancia de GitBlit en {1}.
+gb.noProposals = Lo siento, {0} no acepta propuestas en este momento.
+gb.noFederation = Lo siento, {0} no est\u00E1 configurado para Federar con ninguna instancia de GitBlit.
+gb.proposalFailed = /u00A1Lo siento, {0} no ha recibido ning\u00FAn dato de propuesta!
+gb.proposalError = /u00A1Lo siento, {0} informa de que ha ocurrido un error inesperado!
+gb.failedToSendProposal = /u00A1Fallo al enviar la propuesta!
+gb.userServiceDoesNotPermitAddUser = \u00A1{0} no permite a\u00F1adir una cuenta de usuario!
+gb.userServiceDoesNotPermitPasswordChanges = \u00A1{0} no permite cambio de contrase\u00F1a!
+gb.displayName = Nombre
+gb.emailAddress = Direcci\u00F3n de correo
+gb.errorAdminLoginRequired = La administraci&oacute;n requiere identificarse
+gb.errorOnlyAdminMayCreateRepository = S&oacute;lo un administrador puede crear un repositorio
+gb.errorOnlyAdminOrOwnerMayEditRepository = S&oacute;lo un administrador o el propietario puede editar un repositorio
+gb.errorAdministrationDisabled = La administraci&oacute;n est&aacute; desactivada
+gb.lastNDays = \u00FAltimos {0} d\u00EDas
+gb.completeGravatarProfile = Perfil completo en Gravatar.com
+gb.none = nadie
+gb.line = L\u00EDenea
+gb.content = Contenido
+gb.empty = vac\u00EDo
+gb.inherited = heredado
+gb.deleteRepository = \u00BFBorrar el repositorio \"{0}\"?
+gb.repositoryDeleted = Repositorio ''{0}'' borrado.
+gb.repositoryDeleteFailed = \u00A1Fallo al borrar el repositorio ''{0}''!
+gb.deleteUser = \u00BFEliminar usuario\"{0}\"?
+gb.userDeleted = Usuario ''{0}'' eliminado.
+gb.userDeleteFailed = \u00A1Fallo al eliminar usuario ''{0}''!
+gb.time.justNow = hace poco
+gb.time.today = hoy
+gb.time.yesterday = ayer
+gb.time.minsAgo = hace {0} min
+gb.time.hoursAgo = hace {0} horas
+gb.time.daysAgo = hace {0} d\u00EDas
+gb.time.weeksAgo = hace {0} semanas
+gb.time.monthsAgo = hace {0} meses
+gb.time.oneYearAgo = hace 1 a\u00F1o
+gb.time.yearsAgo = hace {0} a\u00F1os
+gb.duration.oneDay = 1 d\u00EDa
+gb.duration.days = {0} d\u00EDas
+gb.duration.oneMonth = 1 mes
+gb.duration.months = {0} meses
+gb.duration.oneYear = 1 a\u00F1o
+gb.duration.years = {0} a\u00F1os
+gb.authorizationControl = Control de autorizaciones
+gb.allowAuthenticatedDescription = Permitir acceso a todos los usuarios registrados
+gb.allowNamedDescription = Permitir acceso a usuarios inscritos \u00F3 equipos
+gb.markdownFailure = \u00A1Fallo al analizar el contenido Markdown!
+gb.clearCache = Limpiar cache
+gb.projects = Proyectos
+gb.project = Proyecto
+gb.allProjects = Todos los proyectos
+gb.copyToClipboard = Copiar al portapapeles
+gb.fork = Bifurcar
+gb.forks = Bifurcados
+gb.forkRepository = \u00BFBifurcar {0}?
+gb.repositoryForked = {0} se ha bifurcado
+gb.repositoryForkFailed= Bifurcaci\u00F3n fallida
+gb.personalRepositories = Repositorios personales
+gb.allowForks = Permitir bifurcados
+gb.allowForksDescription = Permitir a usuarios autorizados bifurcar este repositorio
+gb.forkedFrom = Bifurcado de
+gb.canFork = Puede bifurcar
+gb.canForkDescription = Puede bifurcar repositorios permitidos ha sus repositorios personales
+gb.myFork = Ver mi bifurcado
+gb.forksProhibited = Prohibido bifurcar
+gb.forksProhibitedWarning = Este repositorio proh\u00EDbe bifurcarse
+gb.noForks = {0} no tiene bifurcados
+gb.forkNotAuthorized = Perdona, no est\u00E1s autorizado a bifurcar {0}
+gb.forkInProgress = Bifurcar en curso
+gb.preparingFork = Preparando tu bifurcaci\u00F3n...
+gb.isFork = Es bifurcado
+gb.canCreate = Puede crear
+gb.canCreateDescription = Puede crear repositorios personales
+gb.illegalPersonalRepositoryLocation = Tu repositorio personal debe estar ubicado en \"{0}\"
+gb.verifyCommitter = Consignador acreditado
+gb.verifyCommitterDescription = Require que la acreditaci\u00F3n del consignador coincida con la de la cuenta del usuario en Gitblt
+gb.verifyCommitterNote = es obligatorio "--no-ff" al empujar para que el consignador se acredite
+gb.repositoryPermissions = Permisos del repositorio
+gb.userPermissions = Permisos de usuarios
+gb.teamPermissions = Permisos de equipos
+gb.add = A\u00F1adir
+gb.noPermission = BORRAR ESTE PERMISO
+gb.excludePermission = {0} (excluir)
+gb.viewPermission = {0} (ver)
+gb.clonePermission = {0} (clonar)
+gb.pushPermission = {0} (empujar)
+gb.createPermission = {0} (empujar, ref creaci\u00F3n)
+gb.deletePermission = {0} (empujar, ref creaci\u00F3n+borrado)
+gb.rewindPermission = {0} (empujar, ref creaci\u00F3n+borrado+supresi\u00F3n)
+gb.permission = Permisos
+gb.regexPermission = Estos permisos se ajustan desde la expresi\u00F3n regulare \"{0}\"
+gb.accessDenied = Acceso denegado
+gb.busyCollectingGarbage = Perd\u00F3n, Gitblit est\u00E1 ocupado quitando basura de {0}
+gb.gcPeriod = Periodo para GC
+gb.gcPeriodDescription = Duraci\u00F3n entre periodos de limpieza
+gb.gcThreshold = L\u00EDmites para GC
+gb.gcThresholdDescription = Tama\u00F1o m\u00EDnimo total de objetos sueltos para activar la recolecci\u00F3n inmediata de basura
+gb.ownerPermission = Propietario del repositorio
+gb.administrator = Admin
+gb.administratorPermission = Administrador de Gitblit
+gb.team = Equipo
+gb.teamPermission = Permisos ajustados para \"{0}\" mienbros de equipo
+gb.missing = \u00A1Omitido!
+gb.missingPermission = \u00A1Falta el repositorio de este permiso!
+gb.mutable = Alterables
+gb.specified = Espec\u00EDficos
+gb.effective = Efectivos
+gb.organizationalUnit = Unidad de organizaci\u00F3n
+gb.organization = Organizaci\u00F3n
+gb.locality = Localidad
+gb.stateProvince = Estado o provincia
+gb.countryCode = C\u00F3digo postal
+gb.properties = Propiedades
+gb.issued = Publicado
+gb.expires = Expira
+gb.expired = Expirado
+gb.expiring = Concluido
+gb.revoked = Revocado
+gb.serialNumber = N\u00FAmero de serie
+gb.certificates = Certificados
+gb.newCertificate = Nuevo certificado
+gb.revokeCertificate = Revocar certificado
+gb.sendEmail = Enviar correo
+gb.passwordHint = Recordatorio de contrase\u00F1a
+gb.ok = ok
+gb.invalidExpirationDate = \u00A1La fecha de expiraci\u00F3n no es v\u00E1lida!
+gb.passwordHintRequired = \u00A1Se requiere una pista para la contrase\u00F1a!
+gb.viewCertificate = Ver certificado
+gb.subject = Asunto
+gb.issuer = Emisor
+gb.validFrom = V\u00E1lido desde
+gb.validUntil = V\u00E1lido hasta
+gb.publicKey = Clave p\u00FAblica
+gb.signatureAlgorithm = Algoritmo de firma
+gb.sha1FingerPrint = Huella digital SHA-1
+gb.md5FingerPrint = Huella digital MD5
+gb.reason = Motivo
+gb.revokeCertificateReason = Por favor, selecciona un motivo por el que revocas el certificado
+gb.unspecified = Sin especificar
+gb.keyCompromise = Clave de compromiso
+gb.caCompromise = Compromiso CA
+gb.affiliationChanged = Afiliaci\u00F3n cambiada
+gb.superseded = Sustituida
+gb.cessationOfOperation = Cese de operaci\u00F3n 
+gb.privilegeWithdrawn = Privilegios retirados
+gb.time.inMinutes = en {0} mints
+gb.time.inHours = en {0} horas
+gb.time.inDays = en {0} d\u00EDas
+gb.hostname = Nombre de host
+gb.hostnameRequired = Por favor, introduzca un nombre de host
+gb.newSSLCertificate = Nuevo certificado SSL del servidor
+gb.newCertificateDefaults = Nuevo certificado predeterminado
+gb.duration = Duraci\u00F3n 
+gb.certificateRevoked = El cretificado {0,n\u00FAmero,0} ha sido revocado
+gb.clientCertificateGenerated = Nuevo certificado de cliente generado correctamente para {0}
+gb.sslCertificateGenerated = Nuevo certificado de SSL generado correctamente para {0}
+gb.newClientCertificateMessage = AVISO:\nLa 'contrase\u00F1a' no es la contrase\u00F1a del usuario, es la contrase\u00F1a para proteger el almac\u00E9n de claves del usuario. Esta contrase\u00F1a no se guarda por lo que tambi\u00E9n debe introducirse una "pista" que ser\u00E1 incluida en las instrucciones LEEME del usuario.
+gb.certificate = Certificado
+gb.emailCertificateBundle = Correo del cliente para el paquete del certificado
+gb.pleaseGenerateClientCertificate = Por favor, genera un certificado de cliente para {0}
+gb.clientCertificateBundleSent = Paquete de certificado de cliente {0} enviado
+gb.enterKeystorePassword =  Por favor, introduzca la contrase\u00F1a del almac\u00E9n de claves de Gitblit
+gb.warning = Advertencia
+gb.jceWarning = Tu entorno de trabajo JAVA no contiene los archivos \"JCE Unlimited Strength Jurisdiction Policy\".\nEsto limita la longitud de la contrase\u00F1a que puedes usuar para cifrar el almac\u00E9n de claves a 7 caracteres.\nEstos archivos opcionales puedes descargarlos desde Oracle.\n\n\u00BFQuieres continuar y generar la infraestructura de certificados de todos modos?\n\nSi respondes No tu navegador te dirigir\u00E1 a la p\u00E1gina de descarga de Oracle para que pueda descargar dichos archivos.
+gb.maxActivityCommits = Actividad m\u00E1xima de consignas
+gb.maxActivityCommitsDescription = N\u00FAmero m\u00E1ximo de consignas a incluir en la p\u00E1gina de actividad
+gb.noMaximum = Sin m\u00E1ximos
+gb.attributes = Atributos
+gb.serveCertificate = Servidor https con este certificado
+gb.sslCertificateGeneratedRestart = Certificado SSL generado correctamente para  {0}.\nDebes reiniciar Gitblit para usar el nuevo certificado.\n\nSi lo has iniciado con la opci\u00F3n  '--alias' deber\u00E1s ajustar dicha opci\u00F3n a ''--alias {0}''.
+gb.validity = Vigencia
+gb.siteName = Nombre del sitio
+gb.siteNameDescription = Nombre corto y descriptivo de tu servidor 
+gb.excludeFromActivity = Excluir de la p\u00E1gina de actividad
+gb.isSparkleshared = compartido mediante Sparkleshared
+gb.owners = propiet\u00E1rios
+gb.sessionEnded = La sesi\u00F3n ha sido cerrada
+gb.closeBrowser = Porfavor cierre el navegador para terminar correctamente la sesi\u00F3n.
+b.doesNotExistInTree = {0} no existe en el \u00E1rbol {1}
+gb.enableIncrementalPushTags = habilitar etiquetas de empuje incrementales
+gb.useIncrementalPushTagsDescription = al empujar, etiquetar autom\u00E1ticamente cada punta de la rama con un n\u00FAmero incremental de revisi\u00F3n 
+gb.incrementalPushTagMessage = Auto-etiquetado [{0}] rama al empujar
+gb.externalPermissions = {0} los permisos de acceso son mantenidos externamente
+gb.viewAccess = No tienes Gitblit acceso de lectura o escritura
+gb.overview = descripci\u00F3n
+gb.dashboard = Tablero de mandos
+gb.monthlyActivity = actividad mensual
+gb.myProfile = mi perfil
+gb.compare = comparar
+gb.manual = manual
+gb.from = desde
+gb.to = hasta
+gb.at = para
+gb.of = de
+gb.in = en
+gb.moreChanges = todos los cambios...
+gb.pushedNCommitsTo = empujadas {0} consignas hasta
+gb.pushedOneCommitTo = empujada 1 consigna hasta
+gb.commitsTo = {0} consignas hasta
+gb.oneCommitTo = 1 consigna hasta
+gb.byNAuthors = de {0} autores
+gb.byOneAuthor = de {0}
+gb.viewComparison = ver comparativo de estas {0} consignas \u00bb
+gb.nMoreCommits = {0} consignas m\u00E1s \u00bb
+gb.oneMoreCommit = 1 consigna m\u00E1 \u00bb
+gb.pushedNewTag = nueva etiqueta empujada
+gb.createdNewTag = nueva etiqueta creada
+gb.deletedTag = etiqueta borrada
+gb.pushedNewBranch = nueva rama empujada
+gb.createdNewBranch = nueva rama creada
+gb.deletedBranch = rama borrada
+gb.createdNewPullRequest = crear petici\u00F3n de empuje
+gb.mergedPullRequest = petici\u00F3n de empuje combinada
+gb.rewind = REBOBINAR
+gb.star = marcar
+gb.unstar = desmarcar
+gb.stargazers = observados
+gb.starredRepositories = repositorios marcados
+gb.failedToUpdateUser = \u00A1Error al actualizar la cuenta de usuario!
+gb.myRepositories = mis repositorios
+gb.noActivity = no ha habido ninguna actividad en los \u00FAltimos {0} d\u00EDas
+gb.findSomeRepositories = encontrar algunos repositorios
+gb.metricAuthorExclusions = excluir estad\u00EDsticas autor
+gb.myDashboard = m\u00ED tablero de mandos
+gb.failedToFindAccount = no pudo encontrar la cuenta del usuario ''{0}''
+gb.reflog = reflotar
+gb.active = activo
+gb.starred = marcadores
+gb.owned = propiet\u00E1rio
+gb.starredAndOwned = marcadores & propiet\u00E1rio
+gb.reviewPatchset = revisados {0} conjuntos de parches {1}
+gb.todaysActivityStats = hoy / {1} consignas de {2} autores
+gb.todaysActivityNone = hoy / nada
+gb.noActivityToday = hoy no ha habido actividad
+gb.anonymousUser= an\u00F3nimo
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/GitBlitWebApp_ja.properties b/src/main/java/com/gitblit/wicket/GitBlitWebApp_ja.properties
new file mode 100644
index 0000000..c8a2449
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/GitBlitWebApp_ja.properties
@@ -0,0 +1,451 @@
+gb.repository = \u30ea\u30dd\u30b8\u30c8\u30ea
+gb.owner = \u6240\u6709\u8005
+gb.description = \u8aac\u660e
+gb.lastChange = \u6700\u5f8c\u306e\u5909\u66f4
+gb.refs = refs
+gb.tag = \u30bf\u30b0
+gb.tags = \u30bf\u30b0
+gb.author = \u4f5c\u8005
+gb.committer = \u30b3\u30df\u30c3\u30bf\u30fc
+gb.commit = \u30b3\u30df\u30c3\u30c8
+gb.tree = tree
+gb.parent = \u89aa
+gb.url = URL
+gb.history = \u5c65\u6b74
+gb.raw = raw
+gb.object = object
+gb.ticketId = \u30c1\u30b1\u30c3\u30c8ID
+gb.ticketAssigned = \u5272\u308a\u5f53\u3066\u6e08\u307f
+gb.ticketOpenDate = \u30aa\u30fc\u30d7\u30f3\u65e5
+gb.ticketState = \u72b6\u614b
+gb.ticketComments = \u30b3\u30e1\u30f3\u30c8
+gb.view = \u898b\u308b
+gb.local = \u30ed\u30fc\u30ab\u30eb
+gb.remote = \u30ea\u30e2\u30fc\u30c8
+gb.branches = \u30d6\u30e9\u30f3\u30c1
+gb.patch = \u30d1\u30c3\u30c1
+gb.diff = diff
+gb.log = \u30ed\u30b0
+gb.moreLogs = more commits...
+gb.allTags = all tags...
+gb.allBranches = all branches...
+gb.summary = \u6982\u8981
+gb.ticket = \u30c1\u30b1\u30c3\u30c8
+gb.newRepository = \u30ea\u30dd\u30b8\u30c8\u30ea\u4f5c\u6210
+gb.newUser = \u30e6\u30fc\u30b6\u30fc\u4f5c\u6210
+gb.commitdiff = commitdiff
+gb.tickets = \u30c1\u30b1\u30c3\u30c8
+gb.pageFirst = first
+gb.pagePrevious = prev
+gb.pageNext = next
+gb.head = HEAD
+gb.blame = \u6ce8\u91c8\u5c65\u6b74
+gb.login = \u30ed\u30b0\u30a4\u30f3
+gb.logout = \u30ed\u30b0\u30a2\u30a6\u30c8
+gb.username = \u30e6\u30fc\u30b6\u30fc\u540d
+gb.password = \u30d1\u30b9\u30ef\u30fc\u30c9
+gb.tagger = tagger
+gb.moreHistory = more history...
+gb.difftocurrent = diff to current
+gb.search = \u691c\u7d22
+gb.searchForAuthor = Search for commits authored by
+gb.searchForCommitter = Search for commits committed by
+gb.addition = \u8ffd\u52a0
+gb.modification = \u4fee\u6b63
+gb.deletion = \u524a\u9664
+gb.rename = \u30ea\u30cd\u30fc\u30e0
+gb.metrics = \u6307\u6a19
+gb.stats = \u7d71\u8a08
+gb.markdown = markdown
+gb.changedFiles = \u5909\u66f4\u3055\u308c\u305f\u30d5\u30a1\u30a4\u30eb 
+gb.filesAdded = {0} \u30d5\u30a1\u30a4\u30eb\u304c\u8ffd\u52a0
+gb.filesModified = {0} \u30d5\u30a1\u30a4\u30eb\u304c\u5909\u66f4
+gb.filesDeleted = {0} \u30d5\u30a1\u30a4\u30eb\u304c\u524a\u9664
+gb.filesCopied = {0} \u30d5\u30a1\u30a4\u30eb\u304c\u30b3\u30d4\u30fc
+gb.filesRenamed = {0} \u30d5\u30a1\u30a4\u30eb\u304c\u30ea\u30cd\u30fc\u30e0
+gb.missingUsername = \u30e6\u30fc\u30b6\u30fc\u540d\u304c\u3042\u308a\u307e\u305b\u3093
+gb.edit = \u7de8\u96c6
+gb.searchTypeTooltip = Select Search Type
+gb.searchTooltip = {0}\u304b\u3089\u691c\u7d22
+gb.delete = \u524a\u9664
+gb.docs = docs
+gb.accessRestriction = \u30a2\u30af\u30bb\u30b9\u5236\u9650
+gb.name = \u540d\u524d
+gb.enableTickets = \u30c1\u30b1\u30c3\u30c8\u3092\u6709\u52b9\u5316
+gb.enableDocs = \u30c9\u30ad\u30e5\u30e1\u30f3\u30c8\u3092\u6709\u52b9\u5316
+gb.save = \u4fdd\u5b58
+gb.showRemoteBranches = \u30ea\u30e2\u30fc\u30c8\u30d6\u30e9\u30f3\u30c1\u3092\u8868\u793a
+gb.editUsers = \u30e6\u30fc\u30b6\u30fc\u7de8\u96c6
+gb.confirmPassword = \u30d1\u30b9\u30ef\u30fc\u30c9(\u78ba\u8a8d)
+gb.restrictedRepositories = \u5236\u9650\u30ea\u30dd\u30b8\u30c8\u30ea
+gb.canAdmin = \u7ba1\u7406\u8005
+gb.notRestricted = \u533f\u540d view, clone, & push
+gb.pushRestricted = \u8a8d\u8a3c push
+gb.cloneRestricted = \u8a8d\u8a3c clone & push
+gb.viewRestricted = \u8a8d\u8a3c view, clone, & push
+gb.useTicketsDescription = \u5206\u6563\u30a4\u30b7\u30e5\u30fc\u7ba1\u7406\u30b7\u30b9\u30c6\u30e0 Ticgit \u3092\u5229\u7528\u3059\u308b
+gb.useDocsDescription = \u30ea\u30dd\u30b8\u30c8\u30ea\u5185\u306e Markdown \u30c9\u30ad\u30e5\u30e1\u30f3\u30c8\u3092\u5217\u6319\u3059\u308b
+gb.showRemoteBranchesDescription = \u30ea\u30e2\u30fc\u30c8\u30d6\u30e9\u30f3\u30c1\u3092\u8868\u793a\u3059\u308b
+gb.canAdminDescription = Gitblit\u30b5\u30fc\u30d0\u30fc\u306e\u7ba1\u7406\u8005
+gb.permittedUsers = \u8a31\u53ef\u3055\u308c\u305f\u30e6\u30fc\u30b6\u30fc
+gb.isFrozen = \u51cd\u7d50
+gb.isFrozenDescription = push\u64cd\u4f5c\u3092\u62d2\u5426\u3059\u308b
+gb.zip = zip
+gb.showReadme = readme\u8868\u793a
+gb.showReadmeDescription = \"readme\" Markdown\u30d5\u30a1\u30a4\u30eb\u3092\u6982\u8981\u30da\u30fc\u30b8\u306b\u8868\u793a\u3059\u308b
+gb.nameDescription = \u30ea\u30dd\u30b8\u30c8\u30ea\u3092\u30b0\u30eb\u30fc\u30d7\u5316\u3059\u308b\u306b\u306f '/' \u3092\u4f7f\u3046\u3002 e.g. libraries/mycoollib.git
+gb.ownerDescription = \u6240\u6709\u8005\u306f\u30ea\u30dd\u30b8\u30c8\u30ea\u306e\u8a2d\u5b9a\u3092\u5909\u66f4\u3067\u304d\u308b
+gb.blob = blob
+gb.commitActivityTrend = commit activity trend
+gb.commitActivityDOW = commit activity by day of week
+gb.commitActivityAuthors = primary authors by commit activity
+gb.feed = \u30d5\u30a3\u30fc\u30c9
+gb.cancel = \u30ad\u30e3\u30f3\u30bb\u30eb
+gb.changePassword = \u30d1\u30b9\u30ef\u30fc\u30c9\u5909\u66f4
+gb.isFederated = is federated
+gb.federateThis = federate this repository
+gb.federateOrigin = federate the origin
+gb.excludeFromFederation = \u30d5\u30a7\u30c7\u30ec\u30fc\u30b7\u30e7\u30f3\u304b\u3089\u9664\u5916\u3059\u308b
+gb.excludeFromFederationDescription = block federated Gitblit instances from pulling this account
+gb.tokens = federation tokens
+gb.tokenAllDescription = all repositories, users, & settings
+gb.tokenUnrDescription = all repositories & users
+gb.tokenJurDescription = all repositories
+gb.federatedRepositoryDefinitions = repository definitions
+gb.federatedUserDefinitions = user definitions
+gb.federatedSettingDefinitions = setting definitions
+gb.proposals = federation proposals
+gb.received = received
+gb.type = type
+gb.token = token
+gb.repositories = \u30ea\u30dd\u30b8\u30c8\u30ea
+gb.proposal = proposal
+gb.frequency = frequency
+gb.folder = \u30d5\u30a9\u30eb\u30c0\u30fc
+gb.lastPull = last pull
+gb.nextPull = next pull
+gb.inclusions = inclusions
+gb.exclusions = exclusions
+gb.registration = registration
+gb.registrations = federation registrations
+gb.sendProposal = propose
+gb.status = status
+gb.origin = origin
+gb.headRef = \u30c7\u30d5\u30a9\u30eb\u30c8\u30d6\u30e9\u30f3\u30c1 (HEAD) 
+gb.headRefDescription = HEAD \u306e\u30ea\u30f3\u30af\u5148 ref \u3092\u5909\u66f4\u3059\u308b e.g. refs/heads/master
+gb.federationStrategy = \u30d5\u30a7\u30c7\u30ec\u30fc\u30b7\u30e7\u30f3\u6226\u7565
+gb.federationRegistration = federation registration
+gb.federationResults = federation pull results
+gb.federationSets = federation sets
+gb.message = \u30e1\u30c3\u30bb\u30fc\u30b8
+gb.myUrlDescription = the publicly accessible url for your Gitblit instance
+gb.destinationUrl = send to
+gb.destinationUrlDescription = the url of the Gitblit instance to send your proposal
+gb.users = \u30e6\u30fc\u30b6\u30fc
+gb.federation = \u30d5\u30a7\u30c7\u30ec\u30fc\u30b7\u30e7\u30f3
+gb.error = \u30a8\u30e9\u30fc
+gb.refresh = \u66f4\u65b0
+gb.browse = \u95b2\u89a7
+gb.clone = clone
+gb.filter = \u30d5\u30a3\u30eb\u30bf\u30fc
+gb.create = \u4f5c\u6210
+gb.servers = \u30b5\u30fc\u30d0\u30fc
+gb.recent = \u6700\u8fd1\u306e\u30a2\u30af\u30bb\u30b9
+gb.available = available
+gb.selected = selected
+gb.size = \u30b5\u30a4\u30ba
+gb.downloading = \u30c0\u30a6\u30f3\u30ed\u30fc\u30c9\u4e2d
+gb.loading = \u30ed\u30fc\u30c9\u4e2d
+gb.starting = \u8d77\u52d5\u4e2d
+gb.general = \u4e00\u822c
+gb.settings = \u8a2d\u5b9a
+gb.manage = \u7ba1\u7406
+gb.lastLogin = \u6700\u7d42\u30ed\u30b0\u30a4\u30f3
+gb.skipSizeCalculation = \u30b5\u30a4\u30ba\u8a08\u7b97\u3092\u30b9\u30ad\u30c3\u30d7
+gb.skipSizeCalculationDescription = \u30ea\u30dd\u30b8\u30c8\u30ea\u306e\u30b5\u30a4\u30ba\u3092\u8a08\u7b97\u3057\u306a\u3044 (\u30da\u30fc\u30b8\u306e\u30ed\u30fc\u30c9\u6642\u9593\u3092\u524a\u6e1b)
+gb.skipSummaryMetrics = \u6982\u8981\u3067\u306e\u6307\u6a19\u3092\u30b9\u30ad\u30c3\u30d7
+gb.skipSummaryMetricsDescription = \u6982\u8981\u30da\u30fc\u30b8\u3067\u6307\u6a19\u3092\u8a08\u7b97\u3057\u306a\u3044 (\u30da\u30fc\u30b8\u306e\u30ed\u30fc\u30c9\u6642\u9593\u3092\u524a\u6e1b)
+gb.accessLevel = \u30a2\u30af\u30bb\u30b9\u30ec\u30d9\u30eb 
+gb.default = \u30c7\u30d5\u30a9\u30eb\u30c8
+gb.setDefault = \u30c7\u30d5\u30a9\u30eb\u30c8\u306b\u8a2d\u5b9a
+gb.since = since
+gb.status = status
+gb.bootDate = boot date
+gb.servletContainer = \u30b5\u30fc\u30d6\u30ec\u30c3\u30c8\u30b3\u30f3\u30c6\u30ca
+gb.heapMaximum = \u6700\u5927\u30d2\u30fc\u30d7
+gb.heapAllocated = \u78ba\u4fdd\u6e08\u307f\u30d2\u30fc\u30d7
+gb.heapUsed = \u4f7f\u7528\u30d2\u30fc\u30d7
+gb.free = free
+gb.version = \u30d0\u30fc\u30b8\u30e7\u30f3
+gb.releaseDate = \u30ea\u30ea\u30fc\u30b9\u65e5
+gb.date = \u65e5\u6642
+gb.activity = \u6d3b\u52d5
+gb.subscribe = \u8cfc\u8aad
+gb.branch = \u30d6\u30e9\u30f3\u30c1
+gb.maxHits = \u6700\u5927\u30d2\u30c3\u30c8\u6570
+gb.recentActivity = \u6700\u8fd1\u306e\u6d3b\u52d5
+gb.recentActivityStats = \u3053\u3053{0}\u65e5\u9593 / {2}\u4eba\u306e\u4f5c\u8005\u304b\u3089 {1}\u30b3\u30df\u30c3\u30c8
+gb.recentActivityNone = \u3053\u3053{0}\u65e5\u9593 / \u306a\u3057
+gb.dailyActivity = \u6bce\u65e5\u306e\u6d3b\u52d5
+gb.activeRepositories = \u6d3b\u767a\u306a\u30ea\u30dd\u30b8\u30c8\u30ea
+gb.activeAuthors = \u6d3b\u767a\u306a\u4f5c\u8005
+gb.commits = \u30b3\u30df\u30c3\u30c8
+gb.teams = \u30c1\u30fc\u30e0
+gb.teamName = \u30c1\u30fc\u30e0\u540d
+gb.teamMembers = \u30c1\u30fc\u30e0\u30e1\u30f3\u30d0\u30fc
+gb.teamMemberships = \u30c1\u30fc\u30e0
+gb.newTeam = \u30c1\u30fc\u30e0\u4f5c\u6210
+gb.permittedTeams = \u8a31\u53ef\u3055\u308c\u305f\u30c1\u30fc\u30e0
+gb.emptyRepository = \u7a7a\u306e\u30ea\u30dd\u30b8\u30c8\u30ea
+gb.repositoryUrl = \u30ea\u30dd\u30b8\u30c8\u30ea\u306eURL
+gb.mailingLists = \u30e1\u30fc\u30ea\u30f3\u30b0\u30ea\u30b9\u30c8
+gb.preReceiveScripts = pre-receive \u30b9\u30af\u30ea\u30d7\u30c8
+gb.postReceiveScripts = post-receive \u30b9\u30af\u30ea\u30d7\u30c8
+gb.hookScripts = \u30d5\u30c3\u30af\u30b9\u30af\u30ea\u30d7\u30c8
+gb.customFields = custom fields
+gb.customFieldsDescription = custom fields available to Groovy hooks
+gb.accessPermissions = \u30a2\u30af\u30bb\u30b9\u6a29\u9650
+gb.filters = \u30d5\u30a3\u30eb\u30bf\u30fc
+gb.generalDescription = \u4e00\u822c\u7684\u306a\u8a2d\u5b9a
+gb.accessPermissionsDescription = \u30e6\u30fc\u30b6\u30fc\u3068\u30c1\u30fc\u30e0\u3067\u30a2\u30af\u30bb\u30b9\u3092\u5236\u9650\u3059\u308b
+gb.accessPermissionsForUserDescription = \u30c1\u30fc\u30e0\u3092\u8a2d\u5b9a\u3059\u308b\u3001\u7279\u5b9a\u306e\u5236\u9650\u30ea\u30dd\u30b8\u30c8\u30ea\u3078\u306e\u30a2\u30af\u30bb\u30b9\u3092\u8a31\u53ef\u3059\u308b
+gb.accessPermissionsForTeamDescription = \u30c1\u30fc\u30e0\u30e1\u30f3\u30d0\u30fc\u3092\u8a2d\u5b9a\u3059\u308b\u3001\u7279\u5b9a\u306e\u5236\u9650\u30ea\u30dd\u30b8\u30c8\u30ea\u3078\u306e\u30a2\u30af\u30bb\u30b9\u3092\u8a31\u53ef\u3059\u308b
+gb.federationRepositoryDescription = \u3053\u306e\u30ea\u30dd\u30b8\u30c8\u30ea\u3092\u4ed6\u306e Gitblit \u30b5\u30fc\u30d0\u30fc\u3068\u5171\u6709\u3059\u308b
+gb.hookScriptsDescription = \u3053\u306e Gitblit \u30b5\u30fc\u30d0\u30fc\u306b push \u3055\u308c\u305f\u6642\u306b Groovy \u30b9\u30af\u30ea\u30d7\u30c8\u3092\u5b9f\u884c\u3059\u308b
+gb.reset = \u30ea\u30bb\u30c3\u30c8
+gb.pages = \u30da\u30fc\u30b8
+gb.workingCopy = \u4f5c\u696d\u30b3\u30d4\u30fc
+gb.workingCopyWarning = \u3053\u306e\u30ea\u30dd\u30b8\u30c8\u30ea\u306b\u306f\u4f5c\u696d\u30b3\u30d4\u30fc\u304c\u3042\u308b\u305f\u3081 push \u3067\u304d\u307e\u305b\u3093
+gb.query = \u30af\u30a8\u30ea\u30fc
+gb.queryHelp = \u6a19\u6e96\u7684\u306a\u30af\u30a8\u30ea\u30fc\u66f8\u5f0f\u3092\u30b5\u30dd\u30fc\u30c8\u3057\u3066\u3044\u307e\u3059\u3002<p/><p/>\u8a73\u7d30\u306f <a target="_new" href="http://lucene.apache.org/core/old_versioned_docs/versions/3_5_0/queryparsersyntax.html">Lucene Query Parser Syntax</a> \u3092\u53c2\u7167\u3057\u3066\u4e0b\u3055\u3044\u3002
+gb.queryResults = \u691c\u7d22\u7d50\u679c {0} - {1} ({2} \u30d2\u30c3\u30c8)
+gb.noHits = \u898b\u3064\u304b\u308a\u307e\u305b\u3093\u3067\u3057\u305f\u3002
+gb.authored = authored
+gb.committed = committed
+gb.indexedBranches = \u30a4\u30f3\u30c7\u30c3\u30af\u30b9\u3059\u308b\u30d6\u30e9\u30f3\u30c1
+gb.indexedBranchesDescription = Lucene \u3067\u30a4\u30f3\u30c7\u30c3\u30af\u30b9\u3059\u308b\u30d6\u30e9\u30f3\u30c1\u3092\u9078\u629e
+gb.noIndexedRepositoriesWarning = Lucene \u3067\u30a4\u30f3\u30c7\u30c3\u30af\u30b9\u3059\u308b\u3088\u3046\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u308b\u30ea\u30dd\u30b8\u30c8\u30ea\u304c\u3042\u308a\u307e\u305b\u3093
+gb.undefinedQueryWarning = \u30af\u30a8\u30ea\u30fc\u304c\u672a\u5b9a\u7fa9\u3067\u3059!
+gb.noSelectedRepositoriesWarning = \u3072\u3068\u3064\u4ee5\u4e0a\u306e\u30ea\u30dd\u30b8\u30c8\u30ea\u3092\u9078\u629e\u3057\u3066\u4e0b\u3055\u3044!
+gb.luceneDisabled = Lucene \u30a4\u30f3\u30c7\u30c3\u30af\u30b9\u306f\u7121\u52b9\u5316\u3055\u308c\u3066\u3044\u307e\u3059
+gb.failedtoRead = \u8aad\u307f\u8fbc\u307f\u5931\u6557
+gb.isNotValidFile = is not a valid file
+gb.failedToReadMessage = {0}\u304b\u3089\u306e\u30c7\u30d5\u30a9\u30eb\u30c8\u30e1\u30c3\u30bb\u30fc\u30b8\u306e\u8aad\u307f\u8fbc\u307f\u306b\u5931\u6557\u3057\u307e\u3057\u305f!
+gb.passwordsDoNotMatch = \u30d1\u30b9\u30ef\u30fc\u30c9\u304c\u4e00\u81f4\u3057\u307e\u305b\u3093!
+gb.passwordTooShort = \u30d1\u30b9\u30ef\u30fc\u30c9\u304c\u77ed\u3059\u304e\u307e\u3059\u3002\u6700\u4f4e\u3067{0}\u6587\u5b57\u5fc5\u8981\u3067\u3059\u3002
+gb.passwordChanged = \u30d1\u30b9\u30ef\u30fc\u30c9\u3092\u5909\u66f4\u3057\u307e\u3057\u305f\u3002
+gb.passwordChangeAborted = \u30d1\u30b9\u30ef\u30fc\u30c9\u306e\u5909\u66f4\u3092\u4e2d\u6b62\u3057\u307e\u3057\u305f\u3002
+gb.pleaseSetRepositoryName = \u30ea\u30dd\u30b8\u30c8\u30ea\u540d\u3092\u8a2d\u5b9a\u3057\u3066\u4e0b\u3055\u3044\u3002
+gb.illegalLeadingSlash = \u5148\u982d\u306e\u30eb\u30fc\u30c8\u30d5\u30a9\u30eb\u30c0\u53c2\u7167(/)\u306f\u7981\u6b62\u3067\u3059\u3002
+gb.illegalRelativeSlash = \u76f8\u5bfe\u30d5\u30a9\u30eb\u30c0\u53c2\u7167(../)\u306f\u7981\u6b62\u3067\u3059\u3002
+gb.illegalCharacterRepositoryName = \u30ea\u30dd\u30b8\u30c8\u30ea\u540d\u306b''{0}''\u4e0d\u6b63\u306a\u6587\u5b57\u304c\u542b\u307e\u308c\u3066\u3044\u307e\u3059!
+gb.selectAccessRestriction = \u30a2\u30af\u30bb\u30b9\u5236\u9650\u3092\u9078\u629e\u3057\u3066\u4e0b\u3055\u3044!
+gb.selectFederationStrategy = \u30d5\u30a7\u30c7\u30ec\u30fc\u30b7\u30e7\u30f3\u6226\u7565\u3092\u9078\u629e\u3057\u3066\u4e0b\u3055\u3044!
+gb.pleaseSetTeamName = \u30c1\u30fc\u30e0\u540d\u3092\u5165\u529b\u3057\u3066\u4e0b\u3055\u3044!
+gb.teamNameUnavailable = \u30c1\u30fc\u30e0\u540d''{0}''\u306f\u5229\u7528\u3067\u304d\u307e\u305b\u3093\u3002.
+gb.teamMustSpecifyRepository = \u6700\u4f4e\u3067\u3082\u30ea\u30dd\u30b8\u30c8\u30ea\u3092\u30c1\u30fc\u30e0\u306b\u4e00\u3064\u6307\u5b9a\u3057\u3066\u4e0b\u3055\u3044\u3002
+gb.teamCreated = \u65b0\u3057\u3044\u30c1\u30fc\u30e0''{0}''\u3092\u4f5c\u6210\u3057\u307e\u3057\u305f\u3002
+gb.pleaseSetUsername = \u30e6\u30fc\u30b6\u30fc\u540d\u3092\u5165\u529b\u3057\u3066\u4e0b\u3055\u3044!
+gb.usernameUnavailable = \u30e6\u30fc\u30b6\u30fc\u540d''{0}''\u306f\u5229\u7528\u3067\u304d\u307e\u305b\u3093\u3002
+gb.combinedMd5Rename = Gitblit\u306fcombined-md5\u30d1\u30b9\u30ef\u30fc\u30c9\u30cf\u30c3\u30b7\u30e5\u304c\u6709\u52b9\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059\u3002\u30a2\u30ab\u30a6\u30f3\u30c8\u540d\u306e\u5909\u66f4\u3067\u306f\u65b0\u3057\u3044\u30d1\u30b9\u30ef\u30fc\u30c9\u3092\u5165\u529b\u3057\u3066\u4e0b\u3055\u3044\u3002
+gb.userCreated = \u65b0\u3057\u3044\u30e6\u30fc\u30b6\u30fc''{0}''\u3092\u4f5c\u6210\u3057\u307e\u3057\u305f\u3002
+gb.couldNotFindFederationRegistration = \u30d5\u30a7\u30c7\u30ec\u30fc\u30b7\u30e7\u30f3\u767b\u9332\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093\u3067\u3057\u305f!
+gb.failedToFindGravatarProfile = {0} \u306eGravatar\u30d7\u30ed\u30d5\u30a1\u30a4\u30eb\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093\u3067\u3057\u305f\u3002
+gb.branchStats = {2}\u3067{0}\u30b3\u30df\u30c3\u30c8\u3001{1}\u30bf\u30b0
+gb.repositoryNotSpecified = \u30ea\u30dd\u30b8\u30c8\u30ea\u304c\u6307\u5b9a\u3055\u308c\u3066\u3044\u307e\u305b\u3093\u3002
+gb.repositoryNotSpecifiedFor = {0}\u306b\u30ea\u30dd\u30b8\u30c8\u30ea\u304c\u6307\u5b9a\u3055\u308c\u3066\u3044\u307e\u305b\u3093!
+gb.canNotLoadRepository = \u30ea\u30dd\u30b8\u30c8\u30ea\u3092\u30ed\u30fc\u30c9\u3067\u304d\u307e\u305b\u3093
+gb.commitIsNull = \u30b3\u30df\u30c3\u30c8\u304c\u7a7a\u3067\u3059
+gb.unauthorizedAccessForRepository = \u30ea\u30dd\u30b8\u30c8\u30ea\u3078\u306e\u30a2\u30af\u30bb\u30b9\u6a29\u304c\u3042\u308a\u307e\u305b\u3093
+gb.failedToFindCommit = Failed to find commit \"{0}\" in {1} for {2} page!
+gb.couldNotFindFederationProposal = Could not find federation proposal!
+gb.invalidUsernameOrPassword = \u30e6\u30fc\u30b6\u30fc\u540d\u307e\u305f\u306f\u30d1\u30b9\u30ef\u30fc\u30c9\u304c\u7121\u52b9\u3067\u3059!
+gb.OneProposalToReview = There is 1 federation proposal awaiting review. 
+gb.nFederationProposalsToReview = There are {0} federation proposals awaiting review.
+gb.couldNotFindTag = {0} \u30bf\u30b0\u3092\u898b\u3064\u304b\u308a\u307e\u305b\u3093\u3067\u3057\u305f
+gb.couldNotCreateFederationProposal = Could not create federation proposal!
+gb.pleaseSetGitblitUrl = Please enter your Gitblit url!
+gb.pleaseSetDestinationUrl = Please enter a destination url for your proposal!
+gb.proposalReceived = Proposal successfully received by {0}.
+gb.noGitblitFound = Sorry, {0} could not find a Gitblit instance at {1}.
+gb.noProposals = Sorry, {0} is not accepting proposals at this time.
+gb.noFederation = Sorry, {0} is not configured to federate with any Gitblit instances.
+gb.proposalFailed = Sorry, {0} did not receive any proposal data!
+gb.proposalError = Sorry, {0} reports that an unexpected error occurred!
+gb.failedToSendProposal = Failed to send proposal!
+gb.userServiceDoesNotPermitAddUser = {0} does not permit adding a user account!
+gb.userServiceDoesNotPermitPasswordChanges = {0} does not permit password changes!
+gb.displayName = \u8868\u793a\u540d
+gb.emailAddress = \u30e1\u30fc\u30eb\u30a2\u30c9\u30ec\u30b9
+gb.errorAdminLoginRequired = Administration requires a login
+gb.errorOnlyAdminMayCreateRepository = Only an administrator may create a repository
+gb.errorOnlyAdminOrOwnerMayEditRepository = Only an administrator or the owner may edit a repository
+gb.errorAdministrationDisabled = Administration is disabled
+gb.lastNDays = \u3053\u3053{0}\u65e5\u9593
+gb.completeGravatarProfile = Complete profile on Gravatar.com
+gb.none = none
+gb.line = line
+gb.content = content
+gb.empty = empty
+gb.inherited = inherited
+gb.deleteRepository = \u30ea\u30dd\u30b8\u30c8\u30ea\"{0}\"\u3092\u524a\u9664\u3057\u307e\u3059\u304b?
+gb.repositoryDeleted = \u30ea\u30dd\u30b8\u30c8\u30ea''{0}''\u3092\u524a\u9664\u3057\u307e\u3057\u305f\u3002
+gb.repositoryDeleteFailed = \u30ea\u30dd\u30b8\u30c8\u30ea''{0}''\u306e\u524a\u9664\u306b\u5931\u6557\u3057\u307e\u3057\u305f!
+gb.deleteUser = \u30e6\u30fc\u30b6\u30fc\"{0}\"\u3092\u524a\u9664\u3057\u307e\u3059\u304b?
+gb.userDeleted = \u30e6\u30fc\u30b6\u30fc''{0}''\u3092\u524a\u9664\u3057\u307e\u3057\u305f\u3002
+gb.userDeleteFailed = \u30e6\u30fc\u30b6\u30fc''{0}''\u306e\u524a\u9664\u306b\u5931\u6557\u3057\u307e\u3057\u305f!
+gb.time.justNow = \u305f\u3063\u305f\u4eca
+gb.time.today = \u4eca\u65e5
+gb.time.yesterday = \u6628\u65e5
+gb.time.minsAgo = {0}\u5206\u524d
+gb.time.hoursAgo = {0}\u6642\u9593\u524d
+gb.time.daysAgo = {0}\u65e5\u524d
+gb.time.weeksAgo = {0}\u9031\u524d
+gb.time.monthsAgo = {0}\u30f6\u6708\u524d
+gb.time.oneYearAgo = 1\u5e74\u524d
+gb.time.yearsAgo = {0}\u5e74\u524d
+gb.duration.oneDay = 1\u65e5
+gb.duration.days = {0}\u65e5
+gb.duration.oneMonth = 1\u30f6\u6708
+gb.duration.months = {0}\u30f6\u6708
+gb.duration.oneYear = 1\u5e74
+gb.duration.years = {0}\u5e74
+gb.authorizationControl = \u6a29\u9650\u5236\u5fa1
+gb.allowAuthenticatedDescription = \u5168\u3066\u306e\u8a8d\u8a3c\u6e08\u307f\u30e6\u30fc\u30b6\u30fc\u3078\u30a2\u30af\u30bb\u30b9\u3092\u8a31\u53ef\u3059\u308b
+gb.allowNamedDescription = \u6307\u5b9a\u3057\u305f\u540d\u524d\u306e\u30e6\u30fc\u30b6\u30fc/\u30c1\u30fc\u30e0\u3078\u30a2\u30af\u30bb\u30b9\u3092\u8a31\u53ef\u3059\u308b
+gb.markdownFailure = Markdown \u306e\u30d1\u30fc\u30b9\u306b\u5931\u6557\u3057\u307e\u3057\u305f!
+gb.clearCache = \u30ad\u30e3\u30c3\u30b7\u30e5\u3092\u30af\u30ea\u30a2
+gb.projects = \u30d7\u30ed\u30b8\u30a7\u30af\u30c8
+gb.project = \u30d7\u30ed\u30b8\u30a7\u30af\u30c8
+gb.allProjects = \u30b9\u307a\u624b\u306e\u30d7\u30ed\u30b8\u30a7\u30af\u30c8
+gb.copyToClipboard = \u30af\u30ea\u30c3\u30d7\u30dc\u30fc\u30c9\u306b\u30b3\u30d4\u30fc
+gb.fork = fork
+gb.forks = forks
+gb.forkRepository = fork {0}?
+gb.repositoryForked = {0} \u3092\u30d5\u30a9\u30fc\u30af\u3057\u307e\u3057\u305f
+gb.repositoryForkFailed= \u30d5\u30a9\u30fc\u30af\u306b\u5931\u6557\u3057\u307e\u3057\u305f
+gb.personalRepositories = \u500b\u4eba\u30ea\u30dd\u30b8\u30c8\u30ea
+gb.allowForks = \u5168\u3066\u306e\u30d5\u30a9\u30fc\u30af
+gb.allowForksDescription = \u8a8d\u8a3c\u6e08\u307f\u30e6\u30fc\u30b6\u30fc\u306b\u3053\u306e\u30ea\u30dd\u30b8\u30c8\u30ea\u306e\u30d5\u30a9\u30fc\u30af\u3092\u8a31\u53ef\u3059\u308b
+gb.forkedFrom = forked from
+gb.canFork = \u30d5\u30a9\u30fc\u30af\u53ef\u80fd
+gb.canForkDescription = \u6a29\u9650\u3092\u6301\u3064\u30ea\u30dd\u30b8\u30c8\u30ea\u304b\u3089\u500b\u4eba\u30ea\u30dd\u30b8\u30c8\u30ea\u3078\u306e\u30d5\u30a9\u30fc\u30af\u3067\u304d\u308b
+gb.myFork = \u81ea\u5206\u306e\u30d5\u30a9\u30fc\u30af\u3092\u898b\u308b
+gb.forksProhibited = \u30d5\u30a9\u30fc\u30af\u7981\u6b62
+gb.forksProhibitedWarning = \u3053\u306e\u30ea\u30dd\u30b8\u30c8\u30ea\u306f\u30d5\u30a9\u30fc\u30af\u3067\u304d\u307e\u305b\u3093
+gb.noForks = {0}\u306b\u306f\u30d5\u30a9\u30fc\u30af\u304c\u3042\u308a\u307e\u305b\u3093
+gb.forkNotAuthorized = \u3059\u307f\u307e\u305b\u3093\u3001\u3042\u306a\u305f\u306f{0}\u3092\u30d5\u30a9\u30fc\u30af\u3067\u304d\u307e\u305b\u3093
+gb.forkInProgress = \u30d5\u30a9\u30fc\u30af\u4e2d\u3067\u3059
+gb.preparingFork = \u30d5\u30a9\u30fc\u30af\u306e\u6e96\u5099\u4e2d...
+gb.isFork = is fork
+gb.canCreate = \u4f5c\u6210\u53ef\u80fd
+gb.canCreateDescription = \u500b\u4eba\u30ea\u30dd\u30b8\u30c8\u30ea\u3092\u4f5c\u6210\u3067\u304d\u308b
+gb.illegalPersonalRepositoryLocation = \u3042\u306a\u305f\u306e\u500b\u4eba\u30ea\u30dd\u30b8\u30c8\u30ea\u306f\"{0}\"\u306b\u5b58\u5728\u3057\u306a\u3051\u308c\u3070\u306a\u308a\u307e\u305b\u3093
+gb.verifyCommitter = verify committer
+gb.verifyCommitterDescription = require committer identity to match pushing Gitblt user account
+gb.verifyCommitterNote = all merges require "--no-ff" to enforce committer identity
+gb.repositoryPermissions = repository permissions
+gb.userPermissions = \u30e6\u30fc\u30b6\u30fc\u30d1\u30fc\u30df\u30c3\u30b7\u30e7\u30f3
+gb.teamPermissions = \u30c1\u30fc\u30e0\u30d1\u30fc\u30df\u30c3\u30b7\u30e7\u30f3
+gb.add = \u8ffd\u52a0
+gb.noPermission = DELETE THIS PERMISSION
+gb.excludePermission = {0} (exclude)
+gb.viewPermission = {0} (view)
+gb.clonePermission = {0} (clone)
+gb.pushPermission = {0} (push)
+gb.createPermission = {0} (push, ref creation)
+gb.deletePermission = {0} (push, ref creation+deletion)
+gb.rewindPermission = {0} (push, ref creation+deletion+rewind)
+gb.permission = \u30d1\u30fc\u30df\u30c3\u30b7\u30e7\u30f3
+gb.regexPermission = this permission is set from regular expression \"{0}\"
+gb.accessDenied = \u30a2\u30af\u30bb\u30b9\u304c\u62d2\u5426\u3055\u308c\u307e\u3057\u305f
+gb.busyCollectingGarbage = sorry, Gitblit is busy collecting garbage in {0}
+gb.gcPeriod = GC\u5468\u671f
+gb.gcPeriodDescription = GC\u306e\u5b9f\u884c\u9593\u9694
+gb.gcThreshold = GC\u95be\u5024
+gb.gcThresholdDescription = \u7de9\u3044\u30aa\u30d6\u30b8\u30a7\u30af\u30c8\u306e\u5408\u8a08\u30b5\u30a4\u30ba\u306e\u6700\u5c0f\u5024
+gb.ownerPermission = \u30ea\u30dd\u30b8\u30c8\u30ea\u306e\u6240\u6709\u8005
+gb.administrator = \u7ba1\u7406\u8005
+gb.administratorPermission = Gitblit\u7ba1\u7406\u8005
+gb.team = \u30c1\u30fc\u30e0
+gb.teamPermission = permission set by \"{0}\" team membership
+gb.missing = missing!
+gb.missingPermission = the repository for this permission is missing!
+gb.mutable = \u5909\u66f4\u53ef\u80fd
+gb.specified = \u6307\u5b9a
+gb.effective = \u6709\u52b9
+gb.organizationalUnit = organizational unit
+gb.organization = organization
+gb.locality = locality
+gb.stateProvince = state or province
+gb.countryCode = country code
+gb.properties = properties
+gb.issued = issued
+gb.expires = expires
+gb.expired = expired
+gb.expiring = expiring
+gb.revoked = revoked
+gb.serialNumber = serial number
+gb.certificates = certificates
+gb.newCertificate = new certificate
+gb.revokeCertificate = revoke certificate
+gb.sendEmail = send email
+gb.passwordHint = password hint
+gb.ok = ok
+gb.invalidExpirationDate = invalid expiration date!
+gb.passwordHintRequired = password hint required!
+gb.viewCertificate = view certificate
+gb.subject = subject
+gb.issuer = issuer
+gb.validFrom = valid from
+gb.validUntil = valid until
+gb.publicKey = public key
+gb.signatureAlgorithm = signature algorithm
+gb.sha1FingerPrint = SHA-1 Fingerprint
+gb.md5FingerPrint = MD5 Fingerprint
+gb.reason = reason
+gb.revokeCertificateReason = Please select a reason for certificate revocation
+gb.unspecified = unspecified
+gb.keyCompromise = key compromise
+gb.caCompromise = CA compromise
+gb.affiliationChanged = affiliation changed
+gb.superseded = superseded
+gb.cessationOfOperation = cessation of operation
+gb.privilegeWithdrawn = privilege withdrawn
+gb.time.inMinutes = in {0} mins
+gb.time.inHours = in {0} hours
+gb.time.inDays = in {0} days
+gb.hostname = hostname
+gb.hostnameRequired = Please enter a hostname
+gb.newSSLCertificate = new server SSL certificate
+gb.newCertificateDefaults = new certificate defaults
+gb.duration = duration
+gb.certificateRevoked = Certificate {0,number,0} has been revoked
+gb.clientCertificateGenerated = Successfully generated new client certificate for {0}
+gb.sslCertificateGenerated = Successfully generated new server SSL certificate for {0}
+gb.newClientCertificateMessage = NOTE:\nThe 'password' is not the user's password, it is the password to protect the user's keystore.  This password is not saved so you must also enter a 'hint' which will be included in the user's README instructions.
+gb.certificate = certificate
+gb.emailCertificateBundle = email client certificate bundle
+gb.pleaseGenerateClientCertificate = Please generate a client certificate for {0}
+gb.clientCertificateBundleSent = Client certificate bundle for {0} sent
+gb.enterKeystorePassword = Please enter the Gitblit keystore password
+gb.warning = warning
+gb.jceWarning = Your Java Runtime Environment does not have the \"JCE Unlimited Strength Jurisdiction Policy\" files.\nThis will limit the length of passwords you may use to encrypt your keystores to 7 characters.\nThese policy files are an optional download from Oracle.\n\nWould you like to continue and generate the certificate infrastructure anyway?\n\nAnswering No will direct your browser to Oracle's download page so that you may download the policy files.
+gb.maxActivityCommits = max activity commits
+gb.maxActivityCommitsDescription = \u6d3b\u52d5\u30da\u30fc\u30b8\u306b\u5bc4\u4e0e\u3059\u308b\u30b3\u30df\u30c3\u30c8\u6570\u306e\u4e0a\u9650
+gb.noMaximum = \u4e0a\u9650\u306a\u3057
+gb.attributes = \u5c5e\u6027
+gb.serveCertificate = serve https with this certificate
+gb.sslCertificateGeneratedRestart = Successfully generated new server SSL certificate for {0}.\nYou must restart Gitblit to use the new certificate.\n\nIf you are launching with the '--alias' parameter you will have to set that to ''--alias {0}''.
+gb.validity = validity
+gb.siteName = site name
+gb.siteNameDescription = short, descriptive name of your server 
+gb.excludeFromActivity = \u6d3b\u52d5\u30da\u30fc\u30b8\u304b\u3089\u9664\u5916\u3059\u308b
+gb.isSparkleshared = repository is Sparkleshared
+gb.owners = owners
+gb.sessionEnded = Session has been closed
+gb.closeBrowser = Please close the browser to properly end the session.
+gb.doesNotExistInTree = {0} does not exist in tree {1}
+gb.enableIncrementalPushTags = enable incremental push tags
+gb.useIncrementalPushTagsDescription = on push, automatically tag each branch tip with an incremental revision number
+gb.incrementalPushTagMessage = Auto-tagged [{0}] branch on push
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/GitBlitWebApp_ko.properties b/src/main/java/com/gitblit/wicket/GitBlitWebApp_ko.properties
new file mode 100644
index 0000000..db31078
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/GitBlitWebApp_ko.properties
@@ -0,0 +1,504 @@
+gb.repository = \uC800\uC7A5\uC18C
+gb.owner = \uC18C\uC720\uC790
+gb.description = \uC124\uBA85
+gb.lastChange = \uCD5C\uADFC \uBCC0\uACBD
+gb.refs = refs
+gb.tag = \uD0DC\uADF8
+gb.tags = \uD0DC\uADF8
+gb.author = \uC791\uC131\uC790
+gb.committer = \uCEE4\uBBF8\uD130
+gb.commit = \uCEE4\uBC0B
+gb.tree = \uD2B8\uB9AC
+gb.parent = \uBD80\uBAA8
+gb.url = URL
+gb.history = \uD788\uC2A4\uD1A0\uB9AC
+gb.raw = raw
+gb.object = object
+gb.ticketId = \uD2F0\uCF13 id
+gb.ticketAssigned = \uD560\uB2F9
+gb.ticketOpenDate = \uC5F4\uB9B0 \uB0A0\uC790
+gb.ticketState = \uC0C1\uD0DC
+gb.ticketComments = \uCF54\uBA58\uD2B8
+gb.view = \uBCF4\uAE30
+gb.local = \uB85C\uCEEC
+gb.remote = \uB9AC\uBAA8\uD2B8
+gb.branches = \uBE0C\uB79C\uCE58
+gb.patch = \uD328\uCE58
+gb.diff = \uCC28\uC774
+gb.log = \uB85C\uADF8
+gb.moreLogs = \uCEE4\uBC0B \uB354 \uBCF4\uAE30...
+gb.allTags = \uBAA8\uB4E0 \uD0DC\uADF8...
+gb.allBranches = \uBAA8\uB4E0 \uBE0C\uB79C\uCE58...
+gb.summary = \uC694\uC57D
+gb.ticket = \uD2F0\uCF13
+gb.newRepository = \uC0C8 \uC800\uC7A5\uC18C
+gb.newUser = \uB0B4 \uC0AC\uC6A9\uC790
+gb.commitdiff = \uCEE4\uBC0B\uBE44\uAD50
+gb.tickets = \uD2F0\uCF13
+gb.pageFirst = \uCCAB
+gb.pagePrevious = \uC774\uC804
+gb.pageNext = \uB2E4\uC74C
+gb.head = HEAD
+gb.blame = blame
+gb.login = \uB85C\uADF8\uC778
+gb.logout = \uB85C\uADF8\uC544\uC6C3
+gb.username = \uC720\uC800\uB124\uC784
+gb.password = \uD328\uC2A4\uC6CC\uB4DC
+gb.tagger = \uD0DC\uAC70
+gb.moreHistory = \uD788\uC2A4\uD1A0\uB9AC \uB354 \uBCF4\uAE30...
+gb.difftocurrent = \uD604\uC7AC\uC640 \uBE44\uAD50
+gb.search = \uAC80\uC0C9
+gb.searchForAuthor = \uCEE4\uBC0B\uC744 \uC791\uC131\uC790\uB85C \uAC80\uC0C9
+gb.searchForCommitter = \uCEE4\uBC0B\uC744 \uCEE4\uBC0B\uD130\uB85C \uAC80\uC0C9
+gb.addition = \uCD94\uAC00
+gb.modification = \uBCC0\uACBD
+gb.deletion = \uC0AD\uC81C
+gb.rename = \uC774\uB984\uBCC0\uACBD
+gb.metrics = \uBA54\uD2B8\uB9AD
+gb.stats = \uC0C1\uD0DC
+gb.markdown = \uB9C8\uD06C\uB2E4\uC6B4
+gb.changedFiles = \uD30C\uC77C \uBCC0\uACBD\uB428 
+gb.filesAdded = {0} \uD30C\uC77C \uCD94\uAC00\uB428
+gb.filesModified = {0} \uD30C\uC77C \uBCC0\uACBD\uB428
+gb.filesDeleted = {0} \uD30C\uC77C \uC0AD\uC81C\uB428
+gb.filesCopied = {0} \uD30C\uC77C \uBCF5\uC0AC\uB428
+gb.filesRenamed = {0} \uD30C\uC77C \uC774\uB984 \uBCC0\uACBD\uB428
+gb.missingUsername = \uC720\uC800\uB124\uC784 \uB204\uB77D
+gb.edit = \uC218\uC815
+gb.searchTypeTooltip = \uAC80\uC0C9 \uD0C0\uC785 \uC120\uD0DD
+gb.searchTooltip = {0} \uAC80\uC0C9
+gb.delete = \uC0AD\uC81C
+gb.docs = \uBB38\uC11C
+gb.accessRestriction = \uC811\uC18D \uC81C\uD55C
+gb.name = \uC774\uB984
+gb.enableTickets = \uD2F0\uCF13 \uC0AC\uC6A9
+gb.enableDocs = \uBB38\uC11C \uC0AC\uC6A9
+gb.save = \uC800\uC7A5
+gb.showRemoteBranches = \uB9AC\uBAA8\uD2B8 \uBE0C\uB79C\uCE58 \uBCF4\uAE30
+gb.editUsers = \uC720\uC800 \uC218\uC815
+gb.confirmPassword = \uD328\uC2A4\uC6CC\uB4DC \uD655\uC778
+gb.restrictedRepositories = \uC81C\uD55C\uB41C \uC800\uC7A5\uC18C
+gb.canAdmin = \uAD00\uB9AC \uAC00\uB2A5
+gb.notRestricted = \uC775\uBA85 view, clone, & push
+gb.pushRestricted = \uD5C8\uC6A9\uB41C \uC720\uC800\uB9CC push
+gb.cloneRestricted = \uD5C8\uC6A9\uB41C \uC720\uC800\uB9CC clone & push
+gb.viewRestricted = \uD5C8\uC6A9\uB41C \uC720\uC800\uB9CC view, clone, & push
+gb.useTicketsDescription = Ticgit(\uBD84\uC0B0 \uD2F0\uCF13 \uC2DC\uC2A4\uD15C) \uC774\uC288 \uC0AC\uC6A9
+gb.useDocsDescription = \uC800\uC7A5\uC18C \uC788\uB294 \uB9C8\uD06C\uB2E4\uC6B4 \uBB38\uC11C \uC0AC\uC6A9
+gb.showRemoteBranchesDescription = \uB9AC\uBAA8\uD2B8 \uBE0C\uB79C\uCE58 \uBCF4\uAE30
+gb.canAdminDescription = Gitblit \uAD00\uB9AC \uAD8C\uD55C \uBD80\uC5EC
+gb.permittedUsers = \uD5C8\uC6A9\uB41C \uC0AC\uC6A9\uC790
+gb.isFrozen = \uD504\uB9AC\uC9D5\uB428
+gb.isFrozenDescription = \uD478\uC2DC \uCC28\uB2E8
+gb.zip = zip
+gb.showReadme = \uB9AC\uB4DC\uBBF8(readme) \uBCF4\uAE30
+gb.showReadmeDescription = \uC694\uC57D\uD398\uC774\uC9C0\uC5D0\uC11C \"readme\" \uB9C8\uD06C\uB2E4\uC6B4 \uD30C\uC77C \uBCF4\uAE30
+gb.nameDescription = \uC800\uC7A5\uC18C\uB97C \uADF8\uB8F9\uC73C\uB85C \uBB36\uC73C\uB824\uBA74 '/' \uB97C \uC0AC\uC6A9. \uC608) libraries/reponame.git
+gb.ownerDescription = \uC18C\uC720\uC790\uB294 \uC800\uC7A5\uC18C \uC124\uC815\uC744 \uBCC0\uACBD\uD560 \uC218 \uC788\uC74C
+gb.blob = blob
+gb.commitActivityTrend = \uCEE4\uBC0B \uD65C\uB3D9 \uD2B8\uB79C\uB4DC
+gb.commitActivityDOW = 1\uC8FC\uC77C\uC758 \uC77C\uB2E8\uC704 \uCEE4\uBC0B \uD65C\uB3D9
+gb.commitActivityAuthors = \uCEE4\uBC0B \uD65C\uB3D9\uC758 \uC8FC \uC791\uC131\uC790
+gb.feed = \uD53C\uB4DC
+gb.cancel = \uCDE8\uC18C
+gb.changePassword = \uD328\uC2A4\uC6CC\uB4DC \uBCC0\uACBD
+gb.isFederated = \uD398\uB354\uB808\uC774\uC158\uB428
+gb.federateThis = \uC774 \uC800\uC7A5\uC18C\uB97C \uD398\uB354\uB808\uC774\uC158\uD568
+gb.federateOrigin = origin \uC5D0 \uD398\uB354\uB808\uC774\uC158
+gb.excludeFromFederation = \uD398\uB354\uB808\uC774\uC158 \uC81C\uC678
+gb.excludeFromFederationDescription = \uC774 \uACC4\uC815\uC73C\uB85C \uD480\uB9C1\uB418\uB294 \uD398\uB7EC\uB808\uC774\uC158 \uB41C Gitblit \uC778\uC2A4\uD134\uC2A4 \uCC28\uB2E8
+gb.tokens = \uD398\uB354\uB808\uC774\uC158 \uD1A0\uD070
+gb.tokenAllDescription = \uBAA8\uB4E0 \uC800\uC7A5\uC18C, \uD1A0\uD070, \uC0AC\uC6A9\uC790 & \uC124\uC815
+gb.tokenUnrDescription = \uBAA8\uB4E0 \uC800\uC7A5\uC18C & \uC0AC\uC6A9\uC790
+gb.tokenJurDescription = \uBAA8\uB4E0 \uC800\uC7A5\uC18C
+gb.federatedRepositoryDefinitions = \uC800\uC7A5\uC18C \uC815\uC758
+gb.federatedUserDefinitions = \uC0AC\uC6A9\uC790 \uC815\uC758
+gb.federatedSettingDefinitions = \uC124\uC815 \uC815\uC758
+gb.proposals = \uD398\uB354\uB808\uC774\uC158 \uC81C\uC548
+gb.received = \uC218\uC2E0\uD568
+gb.type = \uD0C0\uC785
+gb.token = \uD1A0\uD070
+gb.repositories = \uC800\uC7A5\uC18C
+gb.proposal = \uC81C\uC548
+gb.frequency = \uBE48\uB3C4
+gb.folder = \uD3F4\uB354
+gb.lastPull = \uB9C8\uC9C0\uB9C9 \uD480
+gb.nextPull = \uB2E4\uC74C \uD480
+gb.inclusions = \uD3EC\uD568
+gb.exclusions = \uC81C\uC678
+gb.registration = \uB4F1\uB85D
+gb.registrations = \uD398\uB354\uB808\uC774\uC158 \uB4F1\uB85D
+gb.sendProposal = \uC81C\uC548\uD558\uAE30
+gb.status = \uC0C1\uD0DC
+gb.origin = origin
+gb.headRef = \uB514\uD3F4\uD2B8 \uBE0C\uB79C\uCE58(HEAD)
+gb.headRefDescription = \uB514\uD3F4\uD2B8 \uBE0C\uB79C\uCE58\uB97C \uC785\uB825. \uC608) refs/heads/master
+gb.federationStrategy = \uD398\uB354\uB808\uC774\uC158 \uC815\uCC45
+gb.federationRegistration = \uD398\uB354\uB808\uC774\uC158 \uB4F1\uB85D
+gb.federationResults = \uD398\uB354\uB808\uC774\uC158 \uD480 \uACB0\uACFC
+gb.federationSets = \uD398\uB354\uB808\uC774\uC158 \uC14B
+gb.message = \uBA54\uC2DC\uC9C0
+gb.myUrlDescription = \uACF5\uAC1C\uB418\uC5B4 \uC811\uC18D\uD560 \uC218 \uC788\uB294 Gitblit \uC778\uC2A4\uD134\uC2A4 url
+gb.destinationUrl = \uB85C \uBCF4\uB0C4
+gb.destinationUrlDescription = \uC81C\uC548\uC744 \uC804\uC1A1\uD560 \uB300\uC0C1 Gitblit \uC778\uC2A4\uD134\uC2A4\uC758 url
+gb.users = \uC720\uC800
+gb.federation = \uD398\uB354\uB808\uC774\uC158
+gb.error = \uC5D0\uB7EC
+gb.refresh = \uC0C8\uB85C\uACE0\uCE68
+gb.browse = \uBE0C\uB77C\uC6B0\uC988
+gb.clone = clone
+gb.filter = \uD544\uD130
+gb.create = \uC0DD\uC131
+gb.servers = \uC11C\uBC84
+gb.recent = recent
+gb.available = \uAC00\uB2A5\uD55C
+gb.selected = \uC120\uD0DD\uB41C
+gb.size = \uD06C\uAE30
+gb.downloading = \uB2E4\uC6B4\uB85C\uB4DC\uC911
+gb.loading = \uB85C\uB529\uC911
+gb.starting = \uC2DC\uC791\uC911
+gb.general = \uC77C\uBC18
+gb.settings = \uC138\uD305
+gb.manage = \uAD00\uB9AC
+gb.lastLogin = \uB9C8\uC9C0\uB9C9 \uB85C\uADF8\uC778
+gb.skipSizeCalculation = \uD06C\uAE30 \uACC4\uC0B0 \uBB34\uC2DC
+gb.skipSizeCalculationDescription = \uC800\uC7A5\uC18C \uD06C\uAE30 \uACC4\uC0B0\uD558\uC9C0 \uC54A\uC74C (\uD398\uC774\uC9C0 \uB85C\uB529 \uC2DC\uAC04 \uB2E8\uCD95\uB428)
+gb.skipSummaryMetrics = \uBA54\uD2B8\uB9AD \uC694\uC57D \uBB34\uC2DC
+gb.skipSummaryMetricsDescription = \uC694\uC57D \uD398\uC9C0\uC774\uC5D0\uC11C \uBA54\uD2B8\uB9AD \uACC4\uC0B0\uD558\uC9C0 \uC54A\uC74C (\uD398\uC774\uC9C0 \uB85C\uB529 \uC2DC\uAC04 \uB2E8\uCD95\uB428)
+gb.accessLevel = \uC811\uC18D \uB808\uBCA8
+gb.default = \uB514\uD3F4\uD2B8
+gb.setDefault = \uB514\uD3F4\uD2B8 \uC124\uC815
+gb.since = since
+gb.status = \uC0C1\uD0DC
+gb.bootDate = \uBD80\uD305 \uC77C\uC790
+gb.servletContainer = \uC11C\uBE14\uB9BF \uCEE8\uD14C\uC774\uB108
+gb.heapMaximum = \uB9E5\uC2DC\uBA48 \uD799
+gb.heapAllocated = \uD560\uB2F9\uB41C \uD799
+gb.heapUsed = \uC0AC\uC6A9\uB41C \uD799
+gb.free = \uD504\uB9AC
+gb.version = \uBC84\uC804
+gb.releaseDate = \uB9B4\uB9AC\uC988 \uB0A0\uC9DC
+gb.date = date
+gb.activity = \uC561\uD2F0\uBE44\uD2F0
+gb.subscribe = \uAD6C\uB3C5
+gb.branch = \uBE0C\uB79C\uCE58
+gb.maxHits = \uB9E5\uC2A4\uD788\uD2B8
+gb.recentActivity = \uCD5C\uADFC \uC561\uD2F0\uBE44\uD2F0
+gb.recentActivityStats = \uC9C0\uB09C {0} \uC77C / {2} \uC5D0 \uC758\uD574 {1} \uAC1C \uCEE4\uBC0B \uB428
+gb.recentActivityNone = \uC9C0\uB09C {0} \uC77C / \uC5C6\uC74C
+gb.dailyActivity = \uC77C\uC77C \uC561\uD2F0\uBE44\uD2F0
+gb.activeRepositories = \uC0AC\uC6A9\uC911\uC778 \uC800\uC7A5\uC18C
+gb.activeAuthors = \uC0AC\uC6A9\uC911\uC778 \uC791\uC131\uC790
+gb.commits = \uCEE4\uBC0B
+gb.teams = \uD300
+gb.teamName = \uD300 \uC774\uB984
+gb.teamMembers = \uD300 \uBA64\uBC84
+gb.teamMemberships = \uD300 \uB9F4\uBC84\uC27D
+gb.newTeam = \uC0C8\uB85C\uC6B4 \uD300
+gb.permittedTeams = \uD5C8\uC6A9\uB41C \uD300
+gb.emptyRepository = \uBE48 \uC800\uC7A5\uC18C
+gb.repositoryUrl = \uC800\uC7A5\uC18C url
+gb.mailingLists = \uBA54\uC77C\uB9C1 \uB9AC\uC2A4\uD2B8
+gb.preReceiveScripts = pre-receive \uC2A4\uD06C\uB9BD\uD2B8
+gb.postReceiveScripts = post-receive \uC2A4\uD06C\uB9BD\uD2B8
+gb.hookScripts = \uD6C4\uD06C \uC2A4\uD06C\uB9BD\uD2B8
+gb.customFields = \uC0AC\uC6A9\uC790 \uD544\uB4DC
+gb.customFieldsDescription = \uADF8\uB8E8\uBE44 \uD6C5\uC5D0 \uC0AC\uC6A9\uC790 \uD544\uB4DC \uC0AC\uC6A9 \uAC00\uB2A5
+gb.accessPermissions = \uC811\uC18D \uAD8C\uD55C
+gb.filters = \uD544\uD130
+gb.generalDescription = \uC77C\uBC18 \uC124\uC815
+gb.accessPermissionsDescription = \uC720\uC800\uC640 \uD300\uC73C\uB85C \uC811\uC18D\uAD8C\uD55C \uBD80\uC5EC
+gb.accessPermissionsForUserDescription = \uD300\uC744 \uC9C0\uC815\uD558\uAC70\uB098 \uC811\uC18D \uAD8C\uD55C\uC744 \uC9C0\uC815\uD560 \uC800\uC7A5\uC18C \uC120\uD0DD
+gb.accessPermissionsForTeamDescription = \uD300 \uB9F4\uBC84\uB97C \uC120\uD0DD\uD558\uACE0, \uC811\uC18D \uAD8C\uD55C\uC744 \uC9C0\uC815\uD560 \uC800\uC7A5\uC18C \uC120\uD0DD
+gb.federationRepositoryDescription = \uC774 \uC800\uC7A5\uC18C\uB97C \uB2E4\uB978 Gitblit \uC11C\uBC84\uC640 \uACF5\uC720
+gb.hookScriptsDescription = \uC774 Gitblit \uC11C\uBC84\uC5D0 \uD478\uC2DC\uB418\uBA74 \uADF8\uB8E8\uBE44(Groovy) \uC2A4\uD06C\uB9BD\uD2B8\uB97C \uC2E4\uD589
+gb.reset = \uB9AC\uC14B
+gb.pages = \uD398\uC774\uC9C0
+gb.workingCopy = \uC6CC\uD0B9 \uCE74\uD53C
+gb.workingCopyWarning = \uC774 \uC800\uC7A5\uC18C\uB294 \uC6CC\uD0B9\uCE74\uD53C\uB97C \uAC00\uC9C0\uACE0 \uC788\uACE0 \uD478\uC2DC\uB97C \uBC1B\uC744 \uC218 \uC5C6\uC74C
+gb.query = \uCFFC\uB9AC
+gb.queryHelp = \uD45C\uC900 \uCFFC\uB9AC \uBB38\uBC95\uC744 \uC9C0\uC6D0.<p/><p/>\uC790\uC138\uD55C \uAC83\uC744 \uC6D0\uD55C\uB2E4\uBA74 <a target="_new" href="http://lucene.apache.org/core/old_versioned_docs/versions/3_5_0/queryparsersyntax.html">Lucene Query Parser Syntax</a> \uC744 \uBC29\uBB38\uD574 \uC8FC\uC138\uC694.
+gb.queryResults = \uAC80\uC0C9\uACB0\uACFC {0} - {1} ({2}\uAC1C \uAC80\uC0C9\uB428)
+gb.noHits = \uAC80\uC0C9 \uACB0\uACFC \uC5C6\uC74C
+gb.authored = \uAC00 \uC791\uC131\uD568.
+gb.committed = \uCEE4\uBC0B\uB428
+gb.indexedBranches = \uC778\uB371\uC2F1\uD560 \uBE0C\uB79C\uCE58
+gb.indexedBranchesDescription = \uB8E8\uC2E0 \uC778\uB371\uC2A4\uC5D0 \uD3EC\uD568\uD560 \uBE0C\uB79C\uCE58 \uC120\uD0DD
+gb.noIndexedRepositoriesWarning = \uC800\uC7A5\uC18C\uAC00 \uB8E8\uC2E0 \uC778\uB371\uC2F1\uC5D0 \uC124\uC815\uB418\uC9C0 \uC54A\uC74C
+gb.undefinedQueryWarning = \uCFFC\uB9AC \uC9C0\uC815\uB418\uC9C0 \uC54A\uC74C!
+gb.noSelectedRepositoriesWarning = \uD558\uB098 \uB610\uB294 \uADF8 \uC774\uC0C1\uC758 \uC800\uC7A5\uC18C\uB97C \uC120\uD0DD\uD558\uC138\uC694!
+gb.luceneDisabled = \uB8E8\uC2E0 \uC778\uB371\uC2F1 \uC911\uC9C0\uB428
+gb.failedtoRead = \uC77C\uAE30 \uC2E4\uD328
+gb.isNotValidFile = \uC720\uD6A8\uD55C \uD30C\uC77C\uC774 \uC544\uB2D8
+gb.failedToReadMessage = {0}\uC5D0\uC11C \uB514\uD3F4\uD2B8 \uBA54\uC2DC\uC9C0 \uC77C\uAE30 \uC2E4\uD328!
+gb.passwordsDoNotMatch = \uD328\uC2A4\uC6CC\uB4DC\uAC00 \uC77C\uCE58\uD558\uC9C0 \uC54A\uC544\uC694!
+gb.passwordTooShort = \uD328\uC2A4\uC6CC\uB4DC\uAC00 \uB108\uBB34 \uC9E7\uC544\uC694. \uC801\uC5B4\uB3C4 {0} \uAC1C \uBB38\uC790\uC5EC\uC57C \uD569\uB2C8\uB2E4.
+gb.passwordChanged = \uD328\uC2A4\uC6CC\uB4DC\uAC00 \uBCC0\uACBD \uC131\uACF5.
+gb.passwordChangeAborted = \uD328\uC2A4\uC6CC\uB4DC \uBCC0\uACBD \uCDE8\uC18C\uB428.
+gb.pleaseSetRepositoryName = \uC800\uC7A5\uC18C \uC774\uB984\uC744 \uC785\uB825\uD558\uC138\uC694!
+gb.illegalLeadingSlash = \uC800\uC7A5\uC18C \uC774\uB984 \uB610\uB294 \uD3F4\uB354\uB294 (/) \uB85C \uC2DC\uC791\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.
+gb.illegalRelativeSlash = \uC0C1\uB300 \uACBD\uB85C \uC9C0\uC815 (../) \uC740 \uC0AC\uC6A9\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4..
+gb.illegalCharacterRepositoryName = \uBB38\uC790 ''{0}'' \uC800\uC7A5\uC18C \uC774\uB984\uC5D0 \uC0AC\uC6A9\uD560 \uC218 \uC5C6\uC5B4\uC694!
+gb.selectAccessRestriction = \uC811\uC18D \uAD8C\uD55C\uC744 \uC120\uD0DD\uD558\uC138\uC694!
+gb.selectFederationStrategy = \uD398\uB354\uB808\uC774\uC158 \uC815\uCC45\uC744 \uC120\uD0DD\uD558\uC138\uC694!
+gb.pleaseSetTeamName = \uD300\uC774\uB984\uC744 \uC785\uB825\uD558\uC138\uC694!
+gb.teamNameUnavailable = ''{0}'' \uD300\uC740 \uC0AC\uC6A9\uD560 \uC218 \uC5C6\uC5B4\uC694.
+gb.teamMustSpecifyRepository = \uD300\uC740 \uC801\uC5B4\uB3C4 \uD558\uB098\uC758 \uC800\uC7A5\uC18C\uB97C \uC9C0\uC815\uD574\uC57C \uD569\uB2C8\uB2E4.
+gb.teamCreated = \uC0C8\uB85C\uC6B4 \uD300 ''{0}'' \uC0DD\uC131 \uC644\uB8CC.
+gb.pleaseSetUsername = \uC720\uC800\uB124\uC784\uC744 \uC785\uB825\uD558\uC138\uC694!
+gb.usernameUnavailable = ''{0}'' \uC720\uC800\uB124\uC784\uC740 \uC0AC\uC6A9\uD560 \uC218 \uC5C6\uC5B4\uC694.
+gb.combinedMd5Rename = Gitblit \uC740 combined-md5 \uD574\uC2F1 \uD328\uC2A4\uC6CC\uB4DC\uB85C \uC124\uC815\uB418\uC5C8\uC2B5\uB2C8\uB2E4. \uACC4\uC815 \uC774\uB984 \uBCC0\uACBD \uC2DC \uC0C8 \uD328\uC2A4\uC6CC\uB4DC\uB97C \uC785\uB825\uD574\uC57C \uD569\uB2C8\uB2E4.
+gb.userCreated = \uC0C8\uB85C\uC6B4 \uC720\uC800 ''{0}'' \uC0DD\uC131 \uC644\uB8CC.
+gb.couldNotFindFederationRegistration = \uD398\uB354\uB808\uC774\uC158 \uB4F1\uB85D\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4!
+gb.failedToFindGravatarProfile = {0} \uC758 Gravatar \uD504\uB85C\uD30C\uC77C \uCC3E\uAE30 \uC2E4\uD328
+gb.branchStats = {2} \uC548\uC5D0 {0} \uCEE4\uBC0B {1} \uD0DC\uADF8
+gb.repositoryNotSpecified = \uC800\uC7A5\uC18C\uAC00 \uC9C0\uC815\uB418\uC9C0 \uC54A\uC74C!
+gb.repositoryNotSpecifiedFor = {0} \uB97C \uC704\uD55C \uC800\uC7A5\uC18C\uAC00 \uC9C0\uC815\uB418\uC9C0 \uC54A\uC74C!
+gb.canNotLoadRepository = \uC800\uC7A5\uC18C\uB97C \uBD88\uB7EC\uC62C \uC218 \uC5C6\uC74C
+gb.commitIsNull = \uB110 \uCEE4\uBC0B
+gb.unauthorizedAccessForRepository = \uC800\uC7A5\uC18C\uC5D0 \uC811\uADFC \uD5C8\uC6A9\uB418\uC9C0 \uC54A\uC74C
+gb.failedToFindCommit = \uCEE4\uBC0B\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC74C \"{0}\" in {1} for {2} \uD398\uC774\uC9C0!
+gb.couldNotFindFederationProposal = \uD398\uB354\uB808\uC774\uC158 \uC81C\uC548\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4!
+gb.invalidUsernameOrPassword = \uC798\uBABB\uB41C \uC720\uC800\uB124\uC784 \uB610\uB294 \uD328\uC2A4\uC6CC\uB4DC!
+gb.OneProposalToReview = \uB9AC\uBDF0\uB97C \uAE30\uB2E4\uB9AC\uACE0 \uC788\uB294 1\uAC1C\uC758 \uD398\uB354\uB808\uC774\uC158 \uC81C\uC548\uC774 \uC788\uC2B5\uB2C8\uB2E4.
+gb.nFederationProposalsToReview = \uB9AC\uBDF0\uB97C \uAE30\uB2E4\uB9AC\uACE0 \uC788\uB294 {0} \uAC1C\uC758 \uD398\uB354\uB808\uC774\uC158 \uC81C\uC548\uC774 \uC788\uC2B5\uB2C8\uB2E4.
+gb.couldNotFindTag = \uD0DC\uADF8 {0} \uB97C(\uC744) \uCC3E\uC744 \uC218 \uC5C6\uC74C
+gb.couldNotCreateFederationProposal = \uD398\uB354\uB808\uC774\uC158 \uC81C\uC548 \uC0DD\uC131 \uC2E4\uD328!
+gb.pleaseSetGitblitUrl = Gitblit url \uC744 \uC785\uB825\uD558\uC138\uC694!
+gb.pleaseSetDestinationUrl = \uB2F9\uC2E0\uC758 \uC81C\uC548\uC5D0 \uB300\uD55C \uB300\uC0C1 url \uC744 \uC785\uB825\uD558\uC138\uC694!
+gb.proposalReceived = {0} \uC758 \uC81C\uC548 \uC131\uACF5\uC801 \uC218\uC2E0
+gb.noGitblitFound = \uC8C4\uC1A1\uD569\uB2C8\uB2E4, Gitblit \uC778\uC2A4\uD134\uC2A4 {1} \uC5D0\uC11C {0} \uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.
+gb.noProposals = \uC8C4\uC1A1\uD569\uB2C8\uB2E4, \uC774\uBC88\uC5D0\uB294 {0} \uC758 \uC81C\uC548\uC744 \uC218\uC6A9\uD558\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.
+gb.noFederation = \uC8C4\uC1A1\uD569\uB2C8\uB2E4, {0} \uC5D0\uB294 \uD398\uB354\uB808\uC774\uC158 \uC124\uC815\uB41C Gitblit \uC778\uC2A4\uD134\uC2A4\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.
+gb.proposalFailed = \uC8C4\uC1A1\uD569\uB2C8\uB2E4, {0} \uC5D0\uB294 \uC81C\uC548 \uB370\uC774\uD130\uB97C \uBC1B\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.
+gb.proposalError = \uC8C4\uC1A1\uD569\uB2C8\uB2E4, {0} \uC5D0 \uB300\uD55C \uC624\uB958 \uBC1C\uC0DD \uBCF4\uACE0
+gb.failedToSendProposal = \uC81C\uC548 \uBCF4\uB0B4\uAE30 \uC2E4\uD328!
+gb.userServiceDoesNotPermitAddUser = {0} \uC0C8\uB85C\uC6B4 \uC720\uC800\uB97C \uCD94\uAC00\uD560 \uC218 \uC5C6\uC74C!
+gb.userServiceDoesNotPermitPasswordChanges = {0} \uD328\uC2A4\uC6CC\uB4DC\uB97C \uBCC0\uACBD\uD560 \uC218 \uC5C6\uC74C!
+gb.displayName = \uD45C\uC2DC\uB418\uB294 \uC774\uB984
+gb.emailAddress = \uC774\uBA54\uC77C \uC8FC\uC18C
+gb.errorAdminLoginRequired = \uAD00\uB9AC\uB97C \uC704\uD574\uC11C\uB294 \uB85C\uADF8\uC778\uC774 \uD544\uC694
+gb.errorOnlyAdminMayCreateRepository = \uAD00\uB9AC\uC790\uB9CC \uC800\uC7A5\uC18C\uB97C \uB9CC\uB4E4\uC218 \uC788\uC74C
+gb.errorOnlyAdminOrOwnerMayEditRepository = \uAD00\uB9AC\uC790\uC640 \uC18C\uC720\uC790\uB9CC \uC800\uC7A5\uC18C\uB97C \uC218\uC815\uD560 \uC218 \uC788\uC74C
+gb.errorAdministrationDisabled = \uAD00\uB9AC\uAE30\uB2A5 \uBE44\uD65C\uC131\uD654\uB428
+gb.lastNDays = {0} \uC77C\uC804
+gb.completeGravatarProfile = Gravatar.com \uC5D0 \uD504\uB85C\uD30C\uC77C \uC0DD\uC131\uB428
+gb.none = none
+gb.line = \uB77C\uC778
+gb.content = \uB0B4\uC6A9
+gb.empty = empty
+gb.inherited = \uC0C1\uC18D
+gb.deleteRepository = \"{0}\" \uC800\uC7A5\uC18C\uB97C \uC0AD\uC81C\uD560\uAE4C\uC694?
+gb.repositoryDeleted = ''{0}'' \uC800\uC7A5\uC18C \uC0AD\uC81C\uB428.
+gb.repositoryDeleteFailed = ''{0}'' \uC800\uC7A5\uC18C \uC0AD\uC81C \uC2E4\uD328!
+gb.deleteUser = \"{0}\" \uC0AC\uC6A9\uC790 \uC0AD\uC81C?
+gb.userDeleted = ''{0}'' \uC0AC\uC6A9\uC790 \uC0AD\uC81C\uB428.
+gb.userDeleteFailed = ''{0}'' \uC0AC\uC6A9\uC790 \uC0AD\uC81C \uC2E4\uD328!
+gb.time.justNow = \uC9C0\uAE08
+gb.time.today = \uC624\uB298
+gb.time.yesterday = \uC5B4\uC81C
+gb.time.minsAgo = {0} \uBD84 \uC804
+gb.time.hoursAgo = {0} \uC2DC\uAC04 \uC804
+gb.time.daysAgo = {0} \uC77C \uC804
+gb.time.weeksAgo = {0} \uC8FC \uC804
+gb.time.monthsAgo = {0} \uB2EC \uC804
+gb.time.oneYearAgo = 1 \uB144 \uC804
+gb.time.yearsAgo = {0} \uB144 \uC804
+gb.duration.oneDay = 1 \uC77C
+gb.duration.days = {0} \uC77C
+gb.duration.oneMonth = 1 \uAC1C\uC6D4
+gb.duration.months = {0} \uAC1C\uC6D4
+gb.duration.oneYear = 1 \uB144
+gb.duration.years = {0} \uB144
+gb.authorizationControl = \uC778\uC99D \uC81C\uC5B4
+gb.allowAuthenticatedDescription = \uBAA8\uB4E0 \uC778\uC99D\uB41C \uC720\uC800\uC5D0\uAC8C \uAD8C\uD55C \uBD80\uC5EC
+gb.allowNamedDescription = \uC774\uB984\uC73C\uB85C \uC720\uC800\uB098 \uD300\uC5D0\uAC8C \uAD8C\uD55C \uBD80\uC5EC
+gb.markdownFailure = \uB9C8\uD06C\uB2E4\uC6B4 \uCEE8\uD150\uD2B8 \uD30C\uC2F1 \uC624\uB958!
+gb.clearCache = \uCE90\uC2DC \uC9C0\uC6B0\uAE30
+gb.projects = \uD504\uB85C\uC81D\uD2B8\uB4E4
+gb.project = \uD504\uB85C\uC81D\uD2B8
+gb.allProjects = \uBAA8\uB4E0 \uD504\uB85C\uC81D\uD2B8
+gb.copyToClipboard = \uD074\uB9BD\uBCF4\uB4DC\uC5D0 \uBCF5\uC0AC
+gb.fork = \uD3EC\uD06C
+gb.forks = \uD3EC\uD06C
+gb.forkRepository = fork {0}?
+gb.repositoryForked = {0} \uD3EC\uD06C\uB428
+gb.repositoryForkFailed= \uD3EC\uD06C\uC2E4\uD328
+gb.personalRepositories = \uAC1C\uC778 \uC800\uC7A5\uC18C
+gb.allowForks = \uD3EC\uD06C \uD5C8\uC6A9
+gb.allowForksDescription = \uC774 \uC800\uC7A5\uC18C\uB97C \uC778\uC99D\uB41C \uC720\uC800\uC5D0\uAC70 \uD3EC\uD06C \uD5C8\uC6A9
+gb.forkedFrom = forked from
+gb.canFork = \uD3EC\uD06C \uAC00\uB2A5
+gb.canForkDescription = \uD5C8\uC6A9\uB41C \uC800\uC7A5\uC18C\uB97C \uAC1C\uC778 \uC800\uC7A5\uC18C\uC5D0 \uD3EC\uD06C\uD560 \uC218 \uC788\uC74C
+gb.myFork = \uB0B4 \uD3EC\uD06C \uBCF4\uAE30
+gb.forksProhibited = \uD3EC\uD06C \uCC28\uB2E8\uB428
+gb.forksProhibitedWarning = \uC774 \uC800\uC7A5\uC18C\uB294 \uD3EC\uD06C \uCC28\uB2E8\uB418\uC5B4 \uC788\uC74C
+gb.noForks = {0} \uB294 \uD3EC\uD06C \uC5C6\uC74C
+gb.forkNotAuthorized = \uC8C4\uC1A1\uD569\uB2C8\uB2E4. {0} \uD3EC\uD06C\uC5D0 \uC811\uC18D \uC778\uC99D\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.
+gb.forkInProgress = \uD504\uD06C \uC9C4\uD589 \uC911
+gb.preparingFork = \uD3EC\uD06C \uC900\uBE44 \uC911...
+gb.isFork = \uD3EC\uD06C\uD55C
+gb.canCreate = \uC0DD\uC131 \uAC00\uB2A5
+gb.canCreateDescription = \uAC1C\uC778 \uC800\uC7A5\uC18C\uB97C \uB9CC\uB4E4 \uC218 \uC788\uC74C
+gb.illegalPersonalRepositoryLocation = \uAC1C\uC778 \uC800\uC7A5\uC18C\uB294 \uBC18\uB4DC\uC2DC \"{0}\" \uC5D0 \uC704\uCE58\uD574\uC57C \uD569\uB2C8\uB2E4.
+gb.verifyCommitter = \uCEE4\uBBF8\uD130 \uD655\uC778
+gb.verifyCommitterDescription = \uCEE4\uBBF8\uD130 ID \uB294 Gitblit ID \uC640 \uB9E4\uCE58\uB418\uC5B4\uC57C \uD568
+gb.verifyCommitterNote = \uBAA8\uB4E0 \uBA38\uC9C0\uB294 \uCEE4\uBBF8\uD130 ID \uB97C \uC801\uC6A9\uD558\uAE30 \uC704\uD574 "--no-ff" \uC635\uC158 \uD544\uC694
+gb.repositoryPermissions = \uC800\uC7A5\uC18C \uAD8C\uD55C
+gb.userPermissions = \uC720\uC800 \uAD8C\uD55C
+gb.teamPermissions = \uD300 \uAD8C\uD55C
+gb.add = \uCD94\uAC00
+gb.noPermission = \uC774 \uAD8C\uD55C \uC0AD\uC81C
+gb.excludePermission = {0} (\uC81C\uC678)
+gb.viewPermission = {0} (\uBCF4\uAE30)
+gb.clonePermission = {0} (\uD074\uB860)
+gb.pushPermission = {0} (\uD478\uC2DC)
+gb.createPermission = {0} (\uD478\uC2DC, ref \uC0DD\uC131)
+gb.deletePermission = {0} (\uD478\uC2DC, ref \uC0DD\uC131+\uC0AD\uC81C)
+gb.rewindPermission = {0} (\uD478\uC2DC, ref \uC0DD\uC131+\uC0AD\uC81C+\uB418\uB3CC\uB9AC\uAE30)
+gb.permission = \uAD8C\uD55C
+gb.regexPermission = \uC774 \uAD8C\uD55C\uC740 \uC815\uADDC\uC2DD \"{0}\" \uB85C\uBD80\uD130 \uC124\uC815\uB428
+gb.accessDenied = \uC811\uC18D \uAC70\uBD80
+gb.busyCollectingGarbage = \uC8C4\uC1A1\uD569\uB2C8\uB2E4. Gitblit \uC740 \uAC00\uBE44\uC9C0 \uCEEC\uB809\uC158 \uC911\uC785\uB2C8\uB2E4. {0}
+gb.gcPeriod = GC \uC8FC\uAE30
+gb.gcPeriodDescription = \uAC00\uBE44\uC9C0 \uD074\uB809\uC158\uAC04\uC758 \uC2DC\uAC04 \uAC04\uACA9
+gb.gcThreshold = GC \uAE30\uC900\uC810
+gb.gcThresholdDescription = \uC870\uAE30 \uAC00\uBE44\uC9C0 \uCEEC\uB809\uC158\uC744 \uBC1C\uC0DD\uC2DC\uD0A4\uAE30 \uC704\uD55C \uC624\uBE0C\uC81D\uD2B8\uB4E4\uC758 \uCD5C\uC18C \uC804\uCCB4 \uD06C\uAE30
+gb.ownerPermission = \uC800\uC7A5\uC18C \uC624\uB108
+gb.administrator = \uAD00\uB9AC\uC790
+gb.administratorPermission = Gitblit \uAD00\uB9AC\uC790
+gb.team = \uD300
+gb.teamPermission = \"{0}\" \uD300 \uBA64\uBC84\uC5D0 \uAD8C\uD55C \uC124\uC815\uB428
+gb.missing = \uB204\uB77D!
+gb.missingPermission = \uC774 \uAD8C\uD55C\uC744 \uC704\uD55C \uC800\uC7A5\uC18C \uB204\uB77D!
+gb.mutable = \uAC00\uBCC0
+gb.specified = \uC9C0\uC815\uB41C
+gb.effective = \uD6A8\uACFC\uC801
+gb.organizationalUnit = \uC870\uC9C1
+gb.organization = \uAE30\uAD00
+gb.locality = \uC704\uCE58
+gb.stateProvince = \uB3C4 \uB610\uB294 \uC8FC
+gb.countryCode = \uAD6D\uAC00\uCF54\uB4DC
+gb.properties = \uC18D\uC131
+gb.issued = \uBC1C\uAE09\uB428
+gb.expires = \uB9CC\uB8CC
+gb.expired = \uB9CC\uB8CC\uB428
+gb.expiring = \uB9CC\uB8CC\uC911
+gb.revoked = \uD3D0\uAE30\uB428
+gb.serialNumber = \uC77C\uB828\uBC88\uD638
+gb.certificates = \uC778\uC99D\uC11C
+gb.newCertificate = \uC0C8 \uC778\uC99D\uC11C
+gb.revokeCertificate = \uC778\uC99D\uC11C \uD3D0\uAE30
+gb.sendEmail = \uBA54\uC77C \uBCF4\uB0B4\uAE30
+gb.passwordHint = \uD328\uC2A4\uC6CC\uB4DC \uD78C\uD2B8
+gb.ok = ok
+gb.invalidExpirationDate = \uB9D0\uB8CC\uC77C\uC790 \uC624\uB958!
+gb.passwordHintRequired = \uD328\uC2A4\uC6CC\uB4DC \uD78C\uD2B8 \uD544\uC218!
+gb.viewCertificate = \uC778\uC99D\uC11C \uBCF4\uAE30
+gb.subject = \uC774\uB984
+gb.issuer = \uBC1C\uAE09\uC790
+gb.validFrom = \uC720\uD6A8\uAE30\uAC04 (\uC2DC\uC791)
+gb.validUntil = \uC720\uD6A8\uAE30\uAC04 (\uB05D)
+gb.publicKey = \uACF5\uAC1C\uD0A4
+gb.signatureAlgorithm = \uC11C\uBA85 \uC54C\uACE0\uB9AC\uC998
+gb.sha1FingerPrint = SHA-1 \uC9C0\uBB38 \uC54C\uACE0\uB9AC\uC998
+gb.md5FingerPrint = MD5 \uC9C0\uBB38 \uC54C\uACE0\uB9AC\uC998
+gb.reason = \uC774\uC720
+gb.revokeCertificateReason = \uC778\uC99D\uC11C \uD574\uC9C0\uC774\uC720\uB97C \uC120\uD0DD\uD558\uC138\uC694
+gb.unspecified = \uD45C\uC2DC\uD558\uC9C0 \uC54A\uC74C
+gb.keyCompromise = \uD0A4 \uC190\uC0C1
+gb.caCompromise = CA \uC190\uC0C1
+gb.affiliationChanged = \uAD00\uACC4 \uBCC0\uACBD\uB428
+gb.superseded = \uB300\uCCB4\uB428
+gb.cessationOfOperation = \uC6B4\uC601 \uC911\uC9C0
+gb.privilegeWithdrawn = \uAD8C\uD55C \uCCA0\uD68C\uB428
+gb.time.inMinutes = {0} \uBD84
+gb.time.inHours = {0} \uC2DC\uAC04
+gb.time.inDays = {0} \uC77C
+gb.hostname = \uD638\uC2A4\uD2B8\uBA85
+gb.hostnameRequired = \uD638\uC2A4\uD2B8\uBA85\uC744 \uC785\uB825\uD558\uC138\uC694
+gb.newSSLCertificate = \uC0C8 \uC11C\uBC84 SSL \uC778\uC99D\uC11C
+gb.newCertificateDefaults = \uC0C8 \uC778\uC99D\uC11C \uAE30\uBCF8
+gb.duration = \uAE30\uAC04
+gb.certificateRevoked = \uC778\uC99D\uC11C {0,number,0} \uB294 \uD3D0\uAE30\uB418\uC5C8\uC2B5\uB2C8\uB2E4
+gb.clientCertificateGenerated = {0} \uC744(\uB97C) \uC704\uD55C \uC0C8\uB85C\uC6B4 \uD074\uB77C\uC774\uC5B8\uD2B8 \uC778\uC99D\uC11C \uC0DD\uC131 \uC131\uACF5
+gb.sslCertificateGenerated = {0} \uC744(\uB97C) \uC704\uD55C \uC0C8\uB85C\uC6B4 \uC11C\uBC84 SSL \uC778\uC99D\uC11C \uC0DD\uC131 \uC131\uACF5
+gb.newClientCertificateMessage = \uB178\uD2B8:\n'\uD328\uC2A4\uC6CC\uB4DC' \uB294 \uC720\uC800\uC758 \uD328\uC2A4\uC6CC\uB4DC\uAC00 \uC544\uB2C8\uB77C \uC720\uC800\uC758 \uD0A4\uC2A4\uD1A0\uC5B4 \uB97C \uBCF4\uD638\uD558\uAE30 \uC704\uD55C \uAC83\uC785\uB2C8\uB2E4. \uC774 \uD328\uC2A4\uC6CC\uB4DC\uB294 \uC800\uC7A5\uB418\uC9C0 \uC54A\uC73C\uBBC0\uB85C \uC0AC\uC6A9\uC790 README \uC9C0\uCE68\uC5D0 \uD3EC\uD568\uB420 '\uD78C\uD2B8' \uB97C \uBC18\uB4DC\uC2DC \uC785\uB825\uD574\uC57C \uD569\uB2C8\uB2E4.
+gb.certificate = \uC778\uC99D\uC11C
+gb.emailCertificateBundle = \uC774\uBA54\uC77C \uD074\uB77C\uC774\uC5B8\uD2B8 \uC778\uC99D\uC11C \uBC88\uB4E4
+gb.pleaseGenerateClientCertificate = {0} \uC744(\uB97C) \uC704\uD55C \uD074\uB77C\uC774\uC5B8\uD2B8 \uC778\uC99D\uC11C\uB97C \uC0DD\uC131\uD558\uC138\uC694
+gb.clientCertificateBundleSent = {0} \uC744(\uB97C) \uC704\uD55C \uD074\uB77C\uC774\uC5B8\uD2B8 \uC778\uC99D\uC11C \uBC88\uB4E4 \uBC1C\uC1A1\uB428
+gb.enterKeystorePassword = Gitblit \uD0A4\uC2A4\uD1A0\uC5B4 \uD328\uC2A4\uC6CC\uB4DC\uB97C \uC785\uB825\uD558\uC138\uC694
+gb.warning = \uACBD\uACE0
+gb.jceWarning = \uC790\uBC14 \uC2E4\uD589\uD658\uACBD\uC5D0 \"JCE Unlimited Strength Jurisdiction Policy\" \uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.\n\uC774\uAC83\uC740 \uD0A4\uC800\uC7A5\uC18C \uC554\uD638\uD654\uC5D0 \uC0AC\uC6A9\uB418\uB294 \uD328\uC2A4\uC6CC\uB4DC\uC758 \uAE38\uC774\uB294 7\uC790\uB85C \uC81C\uD55C\uD569\uB2C8\uB2E4.\n\uC774 \uC815\uCC45 \uD30C\uC77C\uC740 Oracle \uC5D0\uC11C \uC120\uD0DD\uC801\uC73C\uB85C \uB2E4\uC6B4\uB85C\uB4DC\uD574\uC57C \uD569\uB2C8\uB2E4.\n\n\uBB34\uC2DC\uD558\uACE0 \uC778\uC99D\uC11C \uC778\uD504\uB77C\uB97C \uC0DD\uC131\uD558\uC2DC\uACA0\uC2B5\uB2C8\uAE4C?\n\n\uC544\uB2C8\uC624(No) \uB77C\uACE0 \uB2F5\uD558\uBA74 \uC815\uCC45\uD30C\uC77C\uC744 \uB2E4\uC6B4\uBC1B\uC744 \uC218 \uC788\uB294 Oracle \uB2E4\uC6B4\uB85C\uB4DC \uD398\uC774\uC9C0\uB97C \uBE0C\uB77C\uC6B0\uC800\uB85C \uC548\uB0B4\uD560 \uAC83\uC785\uB2C8\uB2E4.
+gb.maxActivityCommits = \uCD5C\uB300 \uC561\uD2F0\uBE44\uD2F0 \uCEE4\uBC0B
+gb.maxActivityCommitsDescription = \uC561\uD2F0\uBE44\uD2F0 \uD398\uC774\uC9C0\uC5D0 \uD45C\uC2DC\uD560 \uCD5C\uB300 \uCEE4\uBC0B \uC218
+gb.noMaximum = \uBB34\uC81C\uD55C
+gb.attributes = \uC18D\uC131
+gb.serveCertificate = \uC774 \uC778\uC99D\uC11C\uB85C https \uC81C\uACF5
+gb.sslCertificateGeneratedRestart = {0} \uC744(\uB97C) \uC704\uD55C \uC0C8 \uC11C\uBC84 SSL \uC778\uC99D\uC11C\uB97C \uC131\uACF5\uC801\uC73C\uB85C \uC0DD\uC131\uD558\uC600\uC2B5\uB2C8\uB2E4. \n\uC0C8 \uC778\uC99D\uC11C\uB97C \uC0AC\uC6A9\uD558\uB824\uBA74 Gitblit \uC744 \uC7AC\uC2DC\uC791 \uD574\uC57C \uD569\uB2C8\uB2E4.\n\n'--alias' \uD30C\uB77C\uBBF8\uD130\uB85C \uC2E4\uD589\uD55C\uB2E4\uBA74 ''--alias {0}'' \uB85C \uC124\uC815\uD574\uC57C \uD569\uB2C8\uB2E4.
+gb.validity = \uC720\uD6A8\uC131
+gb.siteName = \uC0AC\uC774\uD2B8 \uC774\uB984
+gb.siteNameDescription = \uC11C\uBC84\uC758 \uC9E6\uC740 \uC124\uBA85\uC774 \uD3EC\uD568\uB41C \uC774\uB984
+gb.excludeFromActivity = \uC561\uD2F0\uBE44\uD2F0 \uD398\uC774\uC9C0\uC5D0\uC11C \uC81C\uC678
+gb.isSparkleshared = \uC800\uC7A5\uC18C\uB294 Sparkleshare \uB428
+gb.owners = \uC624\uB108
+gb.sessionEnded = \uC138\uC158\uC774 \uC885\uB8CC\uB428
+gb.closeBrowser = \uC815\uD655\uD788 \uC138\uC158\uC744 \uC885\uB8CC\uD558\uB824\uBA74 \uBE0C\uB77C\uC6B0\uC800\uB97C \uB2EB\uC544 \uC8FC\uC138\uC694.
+gb.doesNotExistInTree = {1} \uD2B8\uB9AC\uC5D0 {0} \uAC00 \uC5C6\uC74C
+gb.enableIncrementalPushTags = \uC99D\uAC00\uD558\uB294 \uD478\uC2DC \uD0DC\uADF8 \uAC00\uB2A5
+gb.useIncrementalPushTagsDescription = \uD478\uC2DC\uD560 \uB54C, \uC99D\uAC00\uD558\uB294 \uB9AC\uBE44\uC804 \uBC88\uD638\uAC00 \uC790\uB3D9\uC73C\uB85C \uD0DC\uADF8\uB428
+gb.incrementalPushTagMessage = \uD478\uC2DC\uD560 \uB54C [{0}] \uBE0C\uB79C\uCE58\uC5D0 \uC790\uB3D9\uC73C\uB85C \uD0DC\uADF8\uB428
+gb.externalPermissions = {0} \uC811\uC18D \uAD8C\uD55C\uC740 \uC678\uBD80\uC5D0\uC11C \uAD00\uB9AC\uB428
+gb.viewAccess = Gitblit \uC5D0 \uC77D\uAE30 \uB610\uB294 \uC4F0\uAE30 \uAD8C\uD55C\uC774 \uC5C6\uC74C
+gb.overview = \uAC1C\uC694
+gb.dashboard = \uB300\uC2DC\uBCF4\uB4DC
+gb.monthlyActivity = \uC6D4\uBCC4 \uC561\uD2F0\uBE44
+gb.myProfile = \uB0B4 \uD504\uB85C\uD544
+gb.compare = \uBE44\uAD50
+gb.manual = \uC124\uBA85\uC11C
+gb.from = from
+gb.to = to
+gb.at = at
+gb.of = of
+gb.in = in
+gb.moreChanges = \uBAA8\uB4E0 \uBCC0\uACBD...
+gb.pushedNCommitsTo = {0} \uAC1C \uCEE4\uBC0B\uC774 \uD478\uC2DC\uB428
+gb.pushedOneCommitTo = 1 \uAC1C \uCEE4\uBC0B\uC774 \uD478\uC2DC\uB428
+gb.commitsTo = {0} \uAC1C \uCEE4\uBC0B
+gb.oneCommitTo = 1 \uAC1C \uCEE4\uBC0B
+gb.byNAuthors = {0} \uBA85\uC758 \uC791\uC131\uC790
+gb.byOneAuthor = {0} \uC5D0 \uC758\uD574 
+gb.viewComparison = {0} \uCEE4\uBC0B\uC758 \uBE44\uAD50 \uBCF4\uAE30 \u00bb
+gb.nMoreCommits = {0} \uAC1C \uB354 \u00bb
+gb.oneMoreCommit = 1 \uAC1C \uB354 \u00bb
+gb.pushedNewTag = \uC0C8 \uD0DC\uADF8\uAC00 \uD478\uC2DC\uB428
+gb.createdNewTag = \uC0C8 \uD0DC\uADF8\uAC00 \uC0DD\uC131\uB428
+gb.deletedTag = \uD0DC\uADF8\uAC00 \uC0AD\uC81C\uB428
+gb.pushedNewBranch = \uC0C8 \uBE0C\uB79C\uCE58\uAC00 \uD478\uC2DC\uB428
+gb.createdNewBranch = \uC0C8 \uBE0C\uB79C\uCE58\uAC00 \uC0DD\uC131\uB428
+gb.deletedBranch = \uBE0C\uB79C\uCE58\uAC00 \uC0AD\uC81C\uB428
+gb.createdNewPullRequest = \uD480 \uB9AC\uD018\uC2A4\uD2B8\uAC00 \uC0DD\uC131\uB428
+gb.mergedPullRequest = \uD480 \uB9AC\uD018\uC2A4\uD2B8\uAC00 \uBA38\uC9C0\uB428
+gb.rewind = REWIND
+gb.star = \uBCC4
+gb.unstar = \uBCC4\uC81C\uAC70
+gb.stargazers = \uAD00\uCE21\uC790
+gb.starredRepositories = \uBCC4 \uD45C\uC2DC\uB41C \uC800\uC7A5\uC18C
+gb.failedToUpdateUser = \uACC4\uC815 \uC5C5\uB370\uC774\uD2B8 \uC2E4\uD328!
+gb.myRepositories = \uB0B4 \uC800\uC7A5\uC18C
+gb.noActivity = \uC9C0\uB09C {0} \uC77C\uAC04 \uC561\uD2F0\uBE44\uD2F0 \uC5C6\uC74C
+gb.findSomeRepositories = \uC800\uC7A5\uC18C \uCC3E\uAE30
+gb.metricAuthorExclusions = \uC791\uC131\uC790 \uBA54\uD2B8\uB9AD \uC81C\uC678
+gb.myDashboard = \uB0B4 \uB300\uC2DC\uBCF4\uB4DC
+gb.failedToFindAccount = ''{0}'' \uACC4\uC815 \uCC3E\uAE30 \uC2E4\uD328
+gb.reflog = reflog
+gb.active = \uD65C\uB3D9\uC911
+gb.starred = \uBCC4\uD45C\uC2DC\uB428
+gb.owned = \uC18C\uC720\uD55C
+gb.starredAndOwned = \uBCC4\uD45C\uC2DC\uB428 & \uC18C\uC720\uD55C
+gb.reviewPatchset = {1} \uD328\uCE58\uC14B {0} \uB9AC\uBDF0
+gb.todaysActivityStats = \uC624\uB298 / {2} \uC791\uC131\uC790\uC5D0 \uC758\uD574 {1} \uCEE4\uBC0B
+gb.todaysActivityNone = \uC624\uB298 / \uC5C6\uC74C
+gb.noActivityToday = \uC624\uB298\uC740 \uC561\uD2F0\uBE44\uD2F0\uAC00 \uC5C6\uC74C
+gb.anonymousUser= \uC775\uBA85
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/GitBlitWebApp_nl.properties b/src/main/java/com/gitblit/wicket/GitBlitWebApp_nl.properties
new file mode 100644
index 0000000..dac85a7
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/GitBlitWebApp_nl.properties
@@ -0,0 +1,504 @@
+gb.repository = repositorie
+gb.owner = eigenaar
+gb.description = omschrijving
+gb.lastChange = laatste wijziging
+gb.refs = refs
+gb.tag = tag
+gb.tags = tags
+gb.author = auteur
+gb.committer = committer
+gb.commit = commit
+gb.tree = tree
+gb.parent = parent
+gb.url = URL
+gb.history = historie
+gb.raw = raw
+gb.object = object
+gb.ticketId = ticket id
+gb.ticketAssigned = toegewezen
+gb.ticketOpenDate = open datum
+gb.ticketState = status
+gb.ticketComments = commentaar
+gb.view = view
+gb.local = local
+gb.remote = remote
+gb.branches = branches
+gb.patch = patch
+gb.diff = diff
+gb.log = log
+gb.moreLogs = meer commits...
+gb.allTags = alle tags...
+gb.allBranches = alle branches...
+gb.summary = samenvatting
+gb.ticket = ticket
+gb.newRepository = nieuwe repositorie
+gb.newUser = nieuwe gebruiker
+gb.commitdiff = commitdiff
+gb.tickets = tickets
+gb.pageFirst = eerste
+gb.pagePrevious = vorige
+gb.pageNext = volgende
+gb.head = HEAD
+gb.blame = blame
+gb.login = aanmelden
+gb.logout = afmelden
+gb.username = gebruikersnaam
+gb.password = wachtwoord
+gb.tagger = tagger
+gb.moreHistory = meer historie...
+gb.difftocurrent = diff naar current
+gb.search = zoeken
+gb.searchForAuthor = Zoeken naar commits authored door
+gb.searchForCommitter = Zoeken naar commits committed door
+gb.addition = additie
+gb.modification = wijziging
+gb.deletion = verwijdering
+gb.rename = hernoem
+gb.metrics = metrieken
+gb.stats = stats
+gb.markdown = markdown
+gb.changedFiles = gewijzigde bestanden
+gb.filesAdded = {0} bestanden toegevoegd
+gb.filesModified = {0} bestanden gewijzigd
+gb.filesDeleted = {0} bestanden verwijderd
+gb.filesCopied = {0} bestanden gekopieerd
+gb.filesRenamed = {0} bestanden hernoemd
+gb.missingUsername = Ontbrekende Gebruikersnaam
+gb.edit = edit
+gb.searchTypeTooltip = Selecteer Zoek Type
+gb.searchTooltip = Zoek {0}
+gb.delete = verwijder
+gb.docs = docs
+gb.accessRestriction = toegangsbeperking
+gb.name = naam
+gb.enableTickets = enable tickets
+gb.enableDocs = enable docs
+gb.save = opslaan
+gb.showRemoteBranches = toon remote branches
+gb.editUsers = wijzig gebruikers
+gb.confirmPassword = bevestig wachtwoord
+gb.restrictedRepositories = restricted repositories
+gb.canAdmin = kan beheren
+gb.notRestricted = anoniem view, clone, & push
+gb.pushRestricted = geauthenticeerde push
+gb.cloneRestricted = geauthenticeerde clone & push
+gb.viewRestricted = geauthenticeerde view, clone, & push
+gb.useTicketsDescription = readonly, gedistribueerde Ticgit issues
+gb.useDocsDescription = enumereer Markdown documentatie in repositorie
+gb.showRemoteBranchesDescription = toon remote branches
+gb.canAdminDescription = kan Gitblit server beheren
+gb.permittedUsers = toegestane gebruikers
+gb.isFrozen = is bevroren
+gb.isFrozenDescription = weiger push operaties
+gb.zip = zip
+gb.showReadme = toon readme
+gb.showReadmeDescription = toon een \"readme\" Markdown bestand in de samenvattingspagina
+gb.nameDescription = gebruik '/' voor het groeperen van repositories.  bijv. libraries/mycoollib.git
+gb.ownerDescription = de eigenaar mag repository instellingen wijzigen
+gb.blob = blob
+gb.commitActivityTrend = commit activiteit trend
+gb.commitActivityDOW = commit activiteit per dag van de week
+gb.commitActivityAuthors = primaire auteurs op basis van commit activiteit
+gb.feed = feed
+gb.cancel = afbreken
+gb.changePassword = wijzig wachtwoord
+gb.isFederated = is gefedereerd
+gb.federateThis = federeer deze repositorie
+gb.federateOrigin = federeer deze origin
+gb.excludeFromFederation = uitsluiten van federatie
+gb.excludeFromFederationDescription = sluit gefedereerde Gitblit instances uit van het pullen van dit account
+gb.tokens = federatie tokens
+gb.tokenAllDescription = alle repositories, gebruikers, & instellingen
+gb.tokenUnrDescription = alle repositories & gebruikers
+gb.tokenJurDescription = alle repositories
+gb.federatedRepositoryDefinitions = repositorie definities
+gb.federatedUserDefinitions = gebruikersdefinities
+gb.federatedSettingDefinitions = instellingendefinities
+gb.proposals = federatie voorstellen
+gb.received = ontvangen
+gb.type = type
+gb.token = token
+gb.repositories = repositories
+gb.proposal = voorstel
+gb.frequency = frequentie
+gb.folder = map
+gb.lastPull = laatste pull
+gb.nextPull = volgende pull
+gb.inclusions = inclusies
+gb.exclusions = exclusies
+gb.registration = registratie
+gb.registrations = federatie registraties
+gb.sendProposal = voorstel
+gb.status = status
+gb.origin = origin
+gb.headRef = default branch (HEAD)
+gb.headRefDescription = wijzig de ref waar HEAD naar linkt naar bijv. refs/heads/master
+gb.federationStrategy = federatie strategie
+gb.federationRegistration = federatie registratie
+gb.federationResults = federatie pull resultaten
+gb.federationSets = federatie sets
+gb.message = melding
+gb.myUrlDescription = de publiek toegankelijke url voor uw Gitblit instantie
+gb.destinationUrl = zend naar
+gb.destinationUrlDescription = de url van de Gitblit instantie voor het verzenden van uw voorstel
+gb.users = gebruikers
+gb.federation = federatie
+gb.error = fout
+gb.refresh = ververs
+gb.browse = blader
+gb.clone = clone
+gb.filter = filter
+gb.create = maak
+gb.servers = servers
+gb.recent = recent
+gb.available = beschikbaar
+gb.selected = geselecteerd
+gb.size = grootte
+gb.downloading = downloading
+gb.loading = laden
+gb.starting = starten
+gb.general = algemeen
+gb.settings = instellingen
+gb.manage = beheer
+gb.lastLogin = laatste login
+gb.skipSizeCalculation = geen berekening van de omvang
+gb.skipSizeCalculationDescription = geen berekening van de repositoriegrootte (beperkt laadtijd pagina)
+gb.skipSummaryMetrics = geen metrieken samenvatting
+gb.skipSummaryMetricsDescription = geen berekening van metrieken op de samenvattingspagina (beperkt laadtijd pagina)
+gb.accessLevel = toegangsniveau
+gb.default = standaard
+gb.setDefault = instellen als standaard
+gb.since = sinds
+gb.status = status
+gb.bootDate = boot datum
+gb.servletContainer = servlet container
+gb.heapMaximum = maximum heap
+gb.heapAllocated = toegewezen heap
+gb.heapUsed = gebruikte heap
+gb.free = beschikbaar
+gb.version = versie
+gb.releaseDate = release datum
+gb.date = datum
+gb.activity = activiteit
+gb.subscribe = aboneer
+gb.branch = branch
+gb.maxHits = max hits
+gb.recentActivity = recente activiteit
+gb.recentActivityStats = laatste {0} dagen / {1} commits door {2} auteurs
+gb.recentActivityNone = laatste {0} dagen / geen
+gb.dailyActivity = dagelijkse activiteit
+gb.activeRepositories = actieve repositories
+gb.activeAuthors = actieve auteurs
+gb.commits = commits
+gb.teams = teams
+gb.teamName = teamnaam
+gb.teamMembers = teamleden
+gb.teamMemberships = teamlidmaatschappen
+gb.newTeam = nieuw team
+gb.permittedTeams = toegestane teams
+gb.emptyRepository = lege repositorie
+gb.repositoryUrl = repositorie url
+gb.mailingLists = mailing lijsten
+gb.preReceiveScripts = pre-receive scripts
+gb.postReceiveScripts = post-receive scripts
+gb.hookScripts = hook scripts
+gb.customFields = custom velden
+gb.customFieldsDescription = custom velden beschikbaar voor Groovy hooks
+gb.accessPermissions = toegangsrechten
+gb.filters = filters
+gb.generalDescription = algemene instellingen
+gb.accessPermissionsDescription = beperk toegang voor gebruikers en teams
+gb.accessPermissionsForUserDescription = stel teamlidmaatschappen in of geef toegang tot specifieke besloten repositories
+gb.accessPermissionsForTeamDescription = stel teamlidmaatschappen in en geef toegang tot specifieke besloten repositories 
+gb.federationRepositoryDescription = deel deze repositorie met andere Gitblit servers
+gb.hookScriptsDescription = run Groovy scripts bij pushes naar deze Gitblit server
+gb.reset = reset
+gb.pages = paginas
+gb.workingCopy = werkkopie
+gb.workingCopyWarning = deze repositorie heeft een werkkopie en kan geen pushes ontvangen
+gb.query = query
+gb.queryHelp = Standaard query syntax wordt ondersteund.<p/><p/>Zie aub <a target="_new" href="http://lucene.apache.org/core/old_versioned_docs/versions/3_5_0/queryparsersyntax.html">Lucene Query Parser Syntax</a> voor informatie.
+gb.queryResults = resultaten {0} - {1} ({2} hits)
+gb.noHits = geen hits
+gb.authored = authored
+gb.committed = committed
+gb.indexedBranches = geïndexeerde branches
+gb.indexedBranchesDescription = kies de branches voor opname in uw Lucene index
+gb.noIndexedRepositoriesWarning = geen van uw repositories is geconfigureerd voor Lucene indexering
+gb.undefinedQueryWarning = query is niet gedefinieerd!
+gb.noSelectedRepositoriesWarning = kies aub één of meerdere repositories!
+gb.luceneDisabled = Lucene indexering staat uit
+gb.failedtoRead = Lezen is mislukt
+gb.isNotValidFile = is geen valide bestand
+gb.failedToReadMessage = Het lezen van de standaard boodschap van {0} is mislukt!
+gb.passwordsDoNotMatch = Wachtwoorden komen niet overeen!
+gb.passwordTooShort = Wachtwoord is te kort. Minimum lengte is {0} karakters.
+gb.passwordChanged = Wachtwoord succesvol gewijzigd.
+gb.passwordChangeAborted = Wijziging wachtwoord afgebroken.
+gb.pleaseSetRepositoryName = Vul aub een repositorie naam in!
+gb.illegalLeadingSlash = Leidende root folder referenties (/) zijn niet toegestaan.
+gb.illegalRelativeSlash = Relatieve folder referenties (../) zijn niet toegestaan.
+gb.illegalCharacterRepositoryName = Illegaal karakter ''{0}'' in repositorie naam!
+gb.selectAccessRestriction = Stel aub een toegangsbeperking in!
+gb.selectFederationStrategy = Selecteer aub een federatie strategie!
+gb.pleaseSetTeamName = Vul aub een teamnaam in!
+gb.teamNameUnavailable = Teamnaam ''{0}'' is niet beschikbaar.
+gb.teamMustSpecifyRepository = Een team moet minimaal één repositorie specificeren.
+gb.teamCreated = Nieuw team ''{0}'' successvol aangemaakt.
+gb.pleaseSetUsername = Vul aub een gebruikersnaam in!
+gb.usernameUnavailable = Gebruikersnaam ''{0}'' is niet beschikbaar.
+gb.combinedMd5Rename = Gitblit is geconfigureerd voor combined-md5 wachtwoord hashing. U moet een nieuw wachtwoord opgeven bij het hernoemen van een account.
+gb.userCreated = Nieuwe gebruiker ''{0}'' succesvol aangemaakt.
+gb.couldNotFindFederationRegistration = Kon de federatie registratie niet vinden!
+gb.failedToFindGravatarProfile = Kon het Gravatar profiel voor {0} niet vinden
+gb.branchStats = {0} commits en {1} tags in {2}
+gb.repositoryNotSpecified = Repositorie niet gespecificeerd!
+gb.repositoryNotSpecifiedFor = Repositorie niet gespecificeerd voor {0}!
+gb.canNotLoadRepository = Kan repositorie niet laden
+gb.commitIsNull = Commit is null
+gb.unauthorizedAccessForRepository = Niet toegestane toegang tot repositorie
+gb.failedToFindCommit = Het vinden van commit \"{0}\" in {1} voor {2} pagina is mislukt!
+gb.couldNotFindFederationProposal = Kon federatievoorstel niet vinden!
+gb.invalidUsernameOrPassword = Onjuiste gebruikersnaam of wachtwoord!
+gb.OneProposalToReview = Er is 1 federatie voorstel dat wacht op review. 
+gb.nFederationProposalsToReview = Er zijn {0} federatie verzoeken die wachten op review.
+gb.couldNotFindTag = Kon tag {0} niet vinden
+gb.couldNotCreateFederationProposal = Kon geen federatie voorstel maken!
+gb.pleaseSetGitblitUrl = Vul aub uw Gitblit url in!
+gb.pleaseSetDestinationUrl = Vul aub een bestemmings-url in voor uw voorstel!
+gb.proposalReceived = Voorstel correct ontvangen door {0}.
+gb.noGitblitFound = Sorry, {0} kon geen Gitblit instance vinden op {1}.
+gb.noProposals = Sorry, {0} accepteert geen voorstellen op dit moment.
+gb.noFederation = Sorry, {0} is niet geconfigureerd voor het federeren met een Gitblit instance.
+gb.proposalFailed = Sorry, {0} ontving geen voorstelgegevens!
+gb.proposalError = Sorry, {0} rapporteert dat een onverwachte fout is opgetreden!
+gb.failedToSendProposal = Voorstel verzenden is niet gelukt!
+gb.userServiceDoesNotPermitAddUser = {0} staat het toevoegen van een gebruikersaccount niet toe!
+gb.userServiceDoesNotPermitPasswordChanges = {0} staat wachtwoord wijzigingen niet toe!
+gb.displayName = display naam
+gb.emailAddress = emailadres
+gb.errorAdminLoginRequired = Aanmelden vereist voor beheerwerk
+gb.errorOnlyAdminMayCreateRepository = Alleen een beheerder kan een repositorie maken
+gb.errorOnlyAdminOrOwnerMayEditRepository = Alleen een beheerder of de eigenaar kan een repositorie wijzigen
+gb.errorAdministrationDisabled = Beheer is uitgeschakeld
+gb.lastNDays = laatste {0} dagen
+gb.completeGravatarProfile = Completeer profiel op Gravatar.com
+gb.none = geen
+gb.line = regel
+gb.content = inhoud
+gb.empty = leeg
+gb.inherited = geërfd
+gb.deleteRepository = Verwijder repositorie \"{0}\"?
+gb.repositoryDeleted = Repositorie ''{0}'' verwijderd.
+gb.repositoryDeleteFailed = Verwijdering van repositorie ''{0}'' mislukt! 
+gb.deleteUser = Verwijder gebruiker \"{0}\"?
+gb.userDeleted = Gebruiker ''{0}'' verwijderd.
+gb.userDeleteFailed = Verwijdering van gebruiker ''{0}'' mislukt!
+gb.time.justNow = net
+gb.time.today = vandaag
+gb.time.yesterday = gisteren
+gb.time.minsAgo = {0} minuten geleden
+gb.time.hoursAgo = {0} uren geleden
+gb.time.daysAgo = {0} dagen geleden
+gb.time.weeksAgo = {0} weken geleden
+gb.time.monthsAgo = {0} maanden geleden
+gb.time.oneYearAgo = 1 jaar geleden
+gb.time.yearsAgo = {0} jaren geleden
+gb.duration.oneDay = 1 dag
+gb.duration.days = {0} dagen
+gb.duration.oneMonth = 1 maand
+gb.duration.months = {0} maanden
+gb.duration.oneYear = 1 jaar
+gb.duration.years = {0} jaren
+gb.authorizationControl = authorisatiebeheer
+gb.allowAuthenticatedDescription = ken RW+ rechten toe aan alle geautoriseerde gebruikers
+gb.allowNamedDescription = ken verfijnde rechten toe aan genoemde gebruikers of teams
+gb.markdownFailure = Het parsen van Markdown content is mislukt!
+gb.clearCache = maak cache leeg
+gb.projects = projecten
+gb.project = project
+gb.allProjects = alle projecten
+gb.copyToClipboard = kopieer naar clipboard
+gb.fork = fork
+gb.forks = forks
+gb.forkRepository = fork {0}?
+gb.repositoryForked = {0} is geforked
+gb.repositoryForkFailed= fork is mislukt
+gb.personalRepositories = personlijke repositories
+gb.allowForks = sta forks toe
+gb.allowForksDescription = sta geauthoriseerde gebruikers toe om deze repositorie te forken
+gb.forkedFrom = geforked vanaf
+gb.canFork = kan geforked worden
+gb.canForkDescription = kan geauthoriseerde repositories forken naar persoonlijke repositories
+gb.myFork = toon mijn fork
+gb.forksProhibited = forks niet toegestaan
+gb.forksProhibitedWarning = deze repositorie staat forken niet toe
+gb.noForks = {0} heeft geen forks
+gb.forkNotAuthorized = sorry, u bent niet geautoriseerd voor het forken van {0}
+gb.forkInProgress = bezig met forken
+gb.preparingFork = bezig met het maken van uw fork...
+gb.isFork = is een fork
+gb.canCreate = mag maken
+gb.canCreateDescription = mag persoonlijke repositories maken
+gb.illegalPersonalRepositoryLocation = uw persoonlijke repositorie moet te vinden zijn op \"{0}\"
+gb.verifyCommitter = controleer committer
+gb.verifyCommitterDescription = vereis dat committer identiteit overeen komt met pushing Gitblt gebruikersaccount
+gb.verifyCommitterNote = alle merges vereisen "--no-ff" om committer identiteit af te dwingen
+gb.repositoryPermissions = repository rechten
+gb.userPermissions = gebruikersrechten
+gb.teamPermissions = teamrechten
+gb.add = toevoegen
+gb.noPermission = VERWIJDER DIT RECHT
+gb.excludePermission = {0} (exclude)
+gb.viewPermission = {0} (view)
+gb.clonePermission = {0} (clone)
+gb.pushPermission = {0} (push)
+gb.createPermission = {0} (push, ref creëer)
+gb.deletePermission = {0} (push, ref creëer+verwijdering)
+gb.rewindPermission = {0} (push, ref creëer+verwijdering+rewind)
+gb.permission = recht
+gb.regexPermission = dit recht is gezet vanaf de reguliere expressie \"{0}\"
+gb.accessDenied = toegang geweigerd
+gb.busyCollectingGarbage = sorry, Gitblit is bezig met opruimen in {0}
+gb.gcPeriod = opruim periode
+gb.gcPeriodDescription = tijdsduur tussen opruimacties
+gb.gcThreshold = opruim drempel
+gb.gcThresholdDescription = minimum totaalomvang van losse objecten voor het starten van opruimactie
+gb.ownerPermission = repositorie eigenaar
+gb.administrator = beheer
+gb.administratorPermission = Gitblit beheerder
+gb.team = team
+gb.teamPermission = permissie ingesteld via \"{0}\" teamlidmaatschap
+gb.missing = ontbrekend!
+gb.missingPermission = de repositorie voor deze permissie ontbreekt!
+gb.mutable = te wijzigen
+gb.specified = gespecificeerd
+gb.effective = geldig
+gb.organizationalUnit = organisatie eenheid
+gb.organization = organisatie
+gb.locality = localiteit
+gb.stateProvince = staat of provincie
+gb.countryCode = landcode
+gb.properties = eigenschappen
+gb.issued = uitgegeven
+gb.expires = verloopt op
+gb.expired = verlopen
+gb.expiring = verloopt
+gb.revoked = ingetrokken
+gb.serialNumber = serie nummer
+gb.certificates = certificaten
+gb.newCertificate = nieuwe certificaten
+gb.revokeCertificate = trek certificaat in
+gb.sendEmail = zend email
+gb.passwordHint = wachtwoord hint
+gb.ok = ok
+gb.invalidExpirationDate = ongeldige verloopdatum!
+gb.passwordHintRequired = wachtwoord hint vereist!
+gb.viewCertificate = toon certificaat
+gb.subject = onderwerp
+gb.issuer = issuer
+gb.validFrom = geldig vanaf
+gb.validUntil = geldig tot
+gb.publicKey = publieke sleutel
+gb.signatureAlgorithm = signature algoritme
+gb.sha1FingerPrint = SHA-1 Fingerprint
+gb.md5FingerPrint = MD5 Fingerprint
+gb.reason = reden
+gb.revokeCertificateReason = Kies aub een reden voor het intrekken van het certificaat
+gb.unspecified = niet gespecificeerd
+gb.keyCompromise = sleutel gecompromitteerd
+gb.caCompromise = CA gecompromitteerd
+gb.affiliationChanged = affiliatie gewijzigd
+gb.superseded = opgevolgd
+gb.cessationOfOperation = gestaakt
+gb.privilegeWithdrawn = privilege ingetrokken
+gb.time.inMinutes = in {0} minuten
+gb.time.inHours = in {0} uren
+gb.time.inDays = in {0} dagen
+gb.hostname = hostnaam
+gb.hostnameRequired = Vul aub een hostnaam in
+gb.newSSLCertificate = nieuw server SSL certificaat
+gb.newCertificateDefaults = nieuw certificaat defaults
+gb.duration = duur
+gb.certificateRevoked = Certificaat {0,number,0} is ingetrokken
+gb.clientCertificateGenerated =  Nieuw client certificaat voor {0} succesvol gegenereerd
+gb.sslCertificateGenerated = Nieuw server SSL certificaat voor {0} succesvol gegenereerd
+gb.newClientCertificateMessage = MERK OP:\nHet 'wachtwoord' is niet het wachtwoord van de gebruiker. Het is het wachtwoord voor het afschermen van de sleutelring van de gebruiker.  Dit wachtwoord wordt niet opgeslagen dus moet u ook een 'hint' invullen die zal worden opgenomen in de README instructies van de gebruiker.
+gb.certificate = certificaat
+gb.emailCertificateBundle = email client certificaat bundel
+gb.pleaseGenerateClientCertificate = Genereer aub een client certificaat voor {0}
+gb.clientCertificateBundleSent = Client certificaat bundel voor {0} verzonden
+gb.enterKeystorePassword = Vul aub het Gitblit keystore wachtwoord in
+gb.warning = waarschuwing
+gb.jceWarning = Uw Java Runtime Environment heeft geen \"JCE Unlimited Strength Jurisdiction Policy\" bestanden.\nDit zal de lengte van wachtwoorden voor het eventueel versleutelen van uw keystores beperken tot 7 karakters.\nDeze policy bestanden zijn een optionele download van Oracle.\n\nWilt u toch doorgaan en de certificaat infrastructuur genereren?\n\nNee antwoorden zal uw browser doorsturen naar de downloadpagina van Oracle zodat u de policybestanden kunt downloaden.
+gb.maxActivityCommits = maximum activiteit commits
+gb.maxActivityCommitsDescription = maximum aantal commits om bij te dragen aan de Activiteitspagina
+gb.noMaximum = geen maximum
+gb.attributes = attributen
+gb.serveCertificate = gebruik deze certificaten voor https
+gb.sslCertificateGeneratedRestart = Nieuwe SSL certificaten voor {0} succesvol gegenereerd.\nU dient Gitblit te herstarten om de nieuwe certificaten te gebruiken.\n\nAls u opstart met de '--alias' parameter moet u die wijzigen naar ''--alias {0}''.
+gb.validity = geldigheid
+gb.siteName = site naam
+gb.siteNameDescription = korte, verduidelijkende naam van deze server
+gb.excludeFromActivity = sluit uit van activiteitspagina
+gb.isSparkleshared = repository is Sparkleshared
+gb.owners = owners
+gb.sessionEnded = Sessie is afgesloten
+gb.closeBrowser = Sluit de browser af om de sessie helemaal te beeindigen.
+gb.doesNotExistInTree = {0} bestaat niet in de tree {1}
+gb.enableIncrementalPushTags = enable incrementele push tags
+gb.useIncrementalPushTagsDescription = bij een push, automatisch tag elke branch tip met een incrementeel revisie nummer
+gb.incrementalPushTagMessage = Auto-tagged [{0}] branch door een push
+gb.externalPermissions = {0} toegangsrechten worden exter beheert
+gb.viewAccess = U heeft geen Gitblit lees- of schrijfrechten
+gb.overview = overview
+gb.dashboard = dashboard
+gb.monthlyActivity = maandelijkse activiteit
+gb.myProfile = mijn profiel
+gb.compare = vergelijk
+gb.manual = manual
+gb.from = van
+gb.to = aan
+gb.at = op
+gb.of = van
+gb.in = in
+gb.moreChanges = alle wijzigingen...
+gb.pushedNCommitsTo = push {0} commits naar
+gb.pushedOneCommitTo = push 1 commit naar
+gb.commitsTo = {0} commits naar
+gb.oneCommitTo = 1 commit naar
+gb.byNAuthors = door {0} auteurs
+gb.byOneAuthor = door {0}
+gb.viewComparison = toon vergelijking van deze {0} commits \u00bb
+gb.nMoreCommits = {0} commits \u00bb
+gb.oneMoreCommit = 1 commit \u00bb
+gb.pushedNewTag = push nieuwe tag
+gb.createdNewTag = nieuww tag gemaakt
+gb.deletedTag = tag verwijderd
+gb.pushedNewBranch = push neuwe branch
+gb.createdNewBranch = nieuwe branch gemaakt
+gb.deletedBranch = branch verwijderd
+gb.createdNewPullRequest = pull verzoek gemaakt
+gb.mergedPullRequest = pull verzoek gemerged
+gb.rewind = REWIND
+gb.star = markeer
+gb.unstar = demarkeer
+gb.stargazers = sterrenkijkers
+gb.starredRepositories = repositories met een ster
+gb.failedToUpdateUser = Bijwerken gebruikersaccount niet gelukt!
+gb.myRepositories = mijn repositories
+gb.noActivity = er is geen activiteit geweest in de laatste {0} dagen
+gb.findSomeRepositories = vind repositories
+gb.metricAuthorExclusions = author metric exclusions
+gb.myDashboard = mijn dashboard
+gb.failedToFindAccount = kan gebruikersaccount ''{0}'' niet vinden
+gb.reflog = reflog
+gb.active = actief
+gb.starred = gemarkeerd
+gb.owned = eigendom
+gb.starredAndOwned = gemarkeerd & eigendom
+gb.reviewPatchset = review {0} patchset {1}
+gb.todaysActivityStats = vandaag / {1} commits door {2} auteurs
+gb.todaysActivityNone = vandaag / geen
+gb.noActivityToday = er is vandaag geen activiteit geweest
+gb.anonymousUser= anoniem
diff --git a/src/main/java/com/gitblit/wicket/GitBlitWebApp_pl.properties b/src/main/java/com/gitblit/wicket/GitBlitWebApp_pl.properties
new file mode 100644
index 0000000..e7d4339
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/GitBlitWebApp_pl.properties
@@ -0,0 +1,451 @@
+gb.repository = Repozytorium
+gb.owner = W\u0142a\u015Bciciel
+gb.description = Opis
+gb.lastChange = Ostatnia zmiana
+gb.refs = Referencje
+gb.tag = Tag
+gb.tags = Tagi
+gb.author = Autor
+gb.committer = Wgrywaj\u0105cy
+gb.commit = Commit
+gb.tree = Drzewo
+gb.parent = Rodzic
+gb.url = URL
+gb.history = Historia
+gb.raw = Raw
+gb.object = Obiekt
+gb.ticketId = Id ticketu
+gb.ticketAssigned = Przydzielony
+gb.ticketOpenDate = Data otwarcia
+gb.ticketState = Status
+gb.ticketComments = Komentarze
+gb.view = Widok
+gb.local = Lokalne
+gb.remote = Zdalne
+gb.branches = Ga\u0142\u0119zie
+gb.patch = \u0141atki
+gb.diff = Diff
+gb.log = Log
+gb.moreLogs = Wi\u0119cej log\u00F3w...
+gb.allTags = Wszystkie tagi...
+gb.allBranches = Wszystkie ga\u0142\u0119zie...
+gb.summary = Podsumowanie
+gb.ticket = Ticket
+gb.newRepository = Nowe repozytorium
+gb.newUser = Nowy u\u017Cytkownik
+gb.commitdiff = Diff zmiany
+gb.tickets = Tickets
+gb.pageFirst = Pierwsza strona
+gb.pagePrevious = Poprzednia strona
+gb.pageNext = Nast\u0119pna strona
+gb.head = HEAD
+gb.blame = Blame
+gb.login = Zaloguj
+gb.logout = Wyloguj
+gb.username = U\u017Cytkownik
+gb.password = Has\u0142o
+gb.tagger = Taguj\u0105cy
+gb.moreHistory = Wi\u0119cej historii...
+gb.difftocurrent = Por\u00F3wnaj z obecnym
+gb.search = Szukaj
+gb.searchForAuthor = Szukaj po autorze
+gb.searchForCommitter = Szukaj po wgrywaj\u0105cym
+gb.addition = Dodane
+gb.modification = Zmodyfikowane
+gb.deletion = Usuni\u0119te
+gb.rename = Zmiana nazwy
+gb.metrics = Metryki
+gb.stats = Statystyki
+gb.markdown = Markdown
+gb.changedFiles = Zmienione pliki
+gb.filesAdded = {0} plik\u00F3w dodano
+gb.filesModified = {0} plik\u00F3w zmieniono
+gb.filesDeleted = {0} plik\u00F3w usuni\u0119to
+gb.filesCopied = {0} plik\u00F3w skopiowano
+gb.filesRenamed = {0} plik\u00F3w przemianowano
+gb.missingUsername = Brak nazwy u\u017Cytkownika
+gb.edit = Edytuj
+gb.searchTypeTooltip = Szukaj typu
+gb.searchTooltip = Szukaj {0}
+gb.delete = Usu\u0144
+gb.docs = Dokumentacja
+gb.accessRestriction = Uprawnienia dost\u0119pu
+gb.name = Nazwa
+gb.enableTickets = Uaktywnij tickety
+gb.enableDocs = Uaktywnij dokumentacj\u0119
+gb.save = Zapisz
+gb.showRemoteBranches = Poka\u017C zdalne ga\u0142\u0119zie
+gb.editUsers = Edytuj u\u017Cytkownika
+gb.confirmPassword = Potwierd\u017A has\u0142o
+gb.restrictedRepositories = Chronione repozytoria
+gb.canAdmin = Mo\u017Ce administrowa\u0107
+gb.notRestricted = Anonimowy podgl\u0105d, klonowanie i zapis
+gb.pushRestricted = Uwierzytelniony zapis
+gb.cloneRestricted = Uwierzytelnione klonowanie i zapis
+gb.viewRestricted = Uwierzytelniony podgl\u0105d, klonowanie i zapis
+gb.useTicketsDescription = Rozproszone zg\u0142oszenia Ticgit 
+gb.useDocsDescription = Parsuj znaczniki Markdown w repozytorium
+gb.showRemoteBranchesDescription = Poka\u017C zdalne ga\u0142\u0119zie
+gb.canAdminDescription = Mo\u017Ce administrowa\u0107 serwerem Gitblit
+gb.permittedUsers = Uprawnieni u\u017Cytkownicy
+gb.isFrozen = jest zamro\u017Cony
+gb.isFrozenDescription = Odrzucaj operacje zapisu
+gb.zip = zip
+gb.showReadme = Poka\u017C readme
+gb.showReadmeDescription = Poka\u017C sparsowany \"readme\" na stronie podsumowania
+gb.nameDescription = u\u017Cyj '/' do grupowania repozytori\u00F3w, np. libraries/server-lib.git
+gb.ownerDescription = W\u0142a\u015Bciciel mo\u017Ce edytowa\u0107 ustawienia repozytorium
+gb.blob = blob
+gb.commitActivityTrend = Aktywno\u015B\u0107 zmian
+gb.commitActivityDOW = Aktywno\u015B\u0107 zmian wed\u0142ug dnia tygodnia
+gb.commitActivityAuthors = G\u0142\u00F3wni aktywni autorzy
+gb.feed = feed
+gb.cancel = Anuluj
+gb.changePassword = Zmie\u0144 has\u0142o
+gb.isFederated = Jest sfederowany
+gb.federateThis = Federuj to repozytorium
+gb.federateOrigin = Federuj origin
+gb.excludeFromFederation = Wy\u0142\u0105cz z federacji
+gb.excludeFromFederationDescription = Blokuj sfederowane instancje Gitblit od pobierania tego konta
+gb.tokens = Tokeny federacji
+gb.tokenAllDescription = Wszystkie repozytoria, u\u017Cytkownicy i ustawienia 
+gb.tokenUnrDescription = Wszystkie repozytoria i u\u017Cytkownicy
+gb.tokenJurDescription = Wszystkie repozytoria
+gb.federatedRepositoryDefinitions = Definicje repozytori\u00F3w
+gb.federatedUserDefinitions = Definicje u\u017Cytkownik\u00F3w
+gb.federatedSettingDefinitions = Definicje ustawie\u0144
+gb.proposals = Propozycje federacji
+gb.received = Otrzymane
+gb.type = Typ
+gb.token = Token
+gb.repositories = Repozytoria
+gb.proposal = Propozycja
+gb.frequency = Cz\u0119stotliwo\u015B\u0107
+gb.folder = Folder
+gb.lastPull = Ostatnie pobranie
+gb.nextPull = Nast\u0119pne pobranie
+gb.inclusions = Do\u0142\u0105czenia
+gb.exclusions = Wy\u0142\u0105czenia
+gb.registration = Rejestracja
+gb.registrations = Sfederowane rejestracje
+gb.sendProposal = Zaproponuj
+gb.status = Status
+gb.origin = origin
+gb.headRef = Domy\u015Blne ga\u0142\u0119zie (HEAD)
+gb.headRefDescription = Zmie\u0144 ref aby wskazywa\u0142o na to co HEAD np. refs/heads/master
+gb.federationStrategy = Strategia federowania
+gb.federationRegistration = Rejestracja federowania
+gb.federationResults = Wyniki sfederowanego pobierania
+gb.federationSets = Zbiory federacji
+gb.message = Wiadomo\u015B\u0107
+gb.myUrlDescription = Zewn\u0119trznie widoczy url do tej instancji Gitblit
+gb.destinationUrl = Wy\u015Blij do
+gb.destinationUrlDescription = Url instacji Gitblit, gdzie chcesz wys\u0142a\u0107 swoj\u0105 propozycj\u0119
+gb.users = U\u017Cytkownicy
+gb.federation = Federacja
+gb.error = B\u0142\u0105d
+gb.refresh = Od\u015Bwie\u017C
+gb.browse = Przegl\u0105daj
+gb.clone = Klonuj
+gb.filter = Filtr
+gb.create = Stw\u00F3rz
+gb.servers = Serwery
+gb.recent = Ostatnie
+gb.available = Dost\u0119pne
+gb.selected = Wybrane
+gb.size = Rozmiar
+gb.downloading = Pobieranie
+gb.loading = \u0141adowanie
+gb.starting = Startowanie
+gb.general = Og\u00F3lne
+gb.settings = Ustawienia
+gb.manage = Zarz\u0105dzaj
+gb.lastLogin = Ostatni login
+gb.skipSizeCalculation = Pomi\u0144 obliczanie rozmiaru
+gb.skipSizeCalculationDescription = Nie obliczaj rozmiaru repozytorium (przyspiesza \u0142adowanie strony)
+gb.skipSummaryMetrics = Pomi\u0144 obliczanie metryk
+gb.skipSummaryMetricsDescription = Nie obliczaj metryk na stronie podsumowania (przyspiesza \u0142adowanie strony)
+gb.accessLevel = Poziom dost\u0119pu
+gb.default = Domy\u015Blny
+gb.setDefault = Ustaw domy\u015Blny
+gb.since = Od
+gb.status = Status
+gb.bootDate = Data uruchomienia
+gb.servletContainer = Kontener serwlet\u00F3w
+gb.heapMaximum = Maksymalny stos
+gb.heapAllocated = Przydzielony stos
+gb.heapUsed = U\u017Cywany stos
+gb.free = Wolne
+gb.version = Wersja
+gb.releaseDate = Data wydania
+gb.date = Data
+gb.activity = Aktywno\u015B\u0107
+gb.subscribe = Subskrybuj
+gb.branch = Ga\u0142\u0105\u017A
+gb.maxHits = Maks. ilo\u015B\u0107 dost\u0119pu
+gb.recentActivity = Ostatnia aktywno\u015B\u0107
+gb.recentActivityStats = Ostatnich {0} dni / {1} zmian przez {2} autor\u00F3w
+gb.recentActivityNone = Ostatnich {0} dni / brak
+gb.dailyActivity = Dzienna aktywno\u015B\u0107
+gb.activeRepositories = Aktywne repozytoria
+gb.activeAuthors = Aktywni u\u017Cytkownicy
+gb.commits = Zmiany
+gb.teams = Zespo\u0142y
+gb.teamName = Nazwa zespo\u0142u
+gb.teamMembers = Cz\u0142onkowie zespo\u0142u
+gb.teamMemberships = Cz\u0142onkostwo zespo\u0142u
+gb.newTeam = Nowy zesp\u00F3\u0142
+gb.permittedTeams = Uprawnione zespo\u0142y
+gb.emptyRepository = Puste repozytorium
+gb.repositoryUrl = Url repozytorium
+gb.mailingLists = Lista mailingowa
+gb.preReceiveScripts = Skrypty przed odbiorem zmian
+gb.postReceiveScripts = Skrypty po odbiorze zmian
+gb.hookScripts = Wpi\u0119te haki
+gb.customFields = Niestandardowe pola
+gb.customFieldsDescription = Niestandardowe pola dost\u0119pne dla hak\u00F3w Groovy
+gb.accessPermissions = Uprawnienia dot\u0119pu
+gb.filters = Filtry
+gb.generalDescription = Wsp\u00F3lne ustawienia
+gb.accessPermissionsDescription = Ogranicz dost\u0119p u\u017Cytkownikom i zespo\u0142om
+gb.accessPermissionsForUserDescription = Ustal cz\u0142onkostwo w zespo\u0142ach lub udziel dost\u0119p do specyficznych repozytori\u00F3w
+gb.accessPermissionsForTeamDescription = Ustal cz\u0142onk\u00F3w zespo\u0142u i udziel dost\u0119p do specyficznych repozytori\u00F3w
+gb.federationRepositoryDescription = Udost\u0119pnij to repozytorium innym serwerom Gitblit
+gb.hookScriptsDescription = Uruchamiaj skrypty Groovy w momencie wgrania zmian na ten serwer
+gb.reset = Resetuj
+gb.pages = Strony
+gb.workingCopy = Kopia robocza
+gb.workingCopyWarning = To repozytorium ma kopi\u0119 robocz\u0105 i nie mo\u017Ce otrzymywa\u0107 zmian
+gb.query = Szukaj
+gb.queryHelp = Standardowa sk\u0142adnia wyszukiwa\u0144 jest wspierana.<p/><p/>Na stronie <a target="_new" href="http://lucene.apache.org/core/old_versioned_docs/versions/3_5_0/queryparsersyntax.html">Lucene Query Parser Syntax</a> dost\u0119pne s\u0105 dalsze szczeg\u00F3\u0142y.
+gb.queryResults = Wyniki {0} - {1} ({2} wynik\u00F3w)
+gb.noHits = Brak wynik\u00F3w
+gb.authored = utworzy\u0142
+gb.committed = wgra\u0142
+gb.indexedBranches = Indeksowane ga\u0142\u0119zie
+gb.indexedBranchesDescription = Wybierz ga\u0142\u0119zie do w\u0142\u0105czenia do indeksu Lucene
+gb.noIndexedRepositoriesWarning = \u017Badne z ga\u0142\u0119zi w repozytorium nie jest dost\u0119pne dla Lucene
+gb.undefinedQueryWarning = Wyszukanie nie zdefiniowane!
+gb.noSelectedRepositoriesWarning = Wska\u017C jedno lub wi\u0119cej repozytori\u00F3w!
+gb.luceneDisabled = Indeksowanie Lucene jest wy\u0142\u0105czone.
+gb.failedtoRead = B\u0142\u0105d podczas odczytu
+gb.isNotValidFile = nie jest poprawnym plikiem.
+gb.failedToReadMessage = B\u0142\u0105d podczas pdczytu domy\u015Blnego komunikatu z {0}!
+gb.passwordsDoNotMatch = Brak zgodno\u015Bci has\u0142a!
+gb.passwordTooShort = Has\u0142o za kr\u00F3tkie. Minimalna d\u0142ugo\u015B\u0107 to {0} znak\u00F3w.
+gb.passwordChanged = Pomy\u015Blnie zmieniono has\u0142o.
+gb.passwordChangeAborted = Zmiania has\u0142a porzucona.
+gb.pleaseSetRepositoryName = Wska\u017C nazw\u0119 repozytorium!
+gb.illegalLeadingSlash = Poprzedzaj\u0105cy g\u0142\u00F3wny folder znaki (/) s\u0105 niedozwolone.
+gb.illegalRelativeSlash = Wzgl\u0119dne odwo\u0142ania do folder\u00F3w (../) s\u0105 niedozwolone.
+gb.illegalCharacterRepositoryName = Zabronionu znak ''{0}'' w nazwie repozytorium!
+gb.selectAccessRestriction = Wska\u017C ograniczenia dost\u0119pu!
+gb.selectFederationStrategy = Wska\u017C strategi\u0119 federacji!
+gb.pleaseSetTeamName = Wpisz nazw\u0119 zespo\u0142u!
+gb.teamNameUnavailable = Nazwa zespo\u0142u ''{0}'' jest niedost\u0119pna.
+gb.teamMustSpecifyRepository = Zesp\u00F3\u0142 musi posiada\u0107 conajmniej jedno repozytorium.
+gb.teamCreated = Zesp\u00F3\u0142 ''{0}'' zosta\u0142 utworzony.
+gb.pleaseSetUsername = Wpisz nazw\u0119 u\u017Cytkownika!
+gb.usernameUnavailable = Nazwa u\u017Cytkownika''{0}'' jest niedost\u0119pna.
+gb.combinedMd5Rename = Gitblit jest skonfigurowany na po\u0142\u0105czone haszowanie hase\u0142 md5. Musisz wpisa\u0107 nowe has\u0142o przy zmianie nazwy konta.
+gb.userCreated = U\u017Cytkownik ''{0}'' zosta\u0142 utworzony.
+gb.couldNotFindFederationRegistration = Nie mo\u017Cna znale\u017A\u0107 rejestracji federacji!
+gb.failedToFindGravatarProfile = B\u0142\u0105d podczas dopasowania profilu Gravatar dla {0}
+gb.branchStats = {0} zmian oraz {1} tag\u00F3w w {2}
+gb.repositoryNotSpecified = Repozytorium nie wskazane!
+gb.repositoryNotSpecifiedFor = Repozytorium nie wskazane dla {0}!
+gb.canNotLoadRepository = Nie mo\u017Cna za\u0142adowa\u0107 repozytorium
+gb.commitIsNull = Zmiana jest nullem
+gb.unauthorizedAccessForRepository = Nieuwierzytelniony dost\u0119p do repozytorium
+gb.failedToFindCommit = B\u0142\u0105d podczas wyszukania zmiany \"{0}\" w {1} dla {2} strony!
+gb.couldNotFindFederationProposal = Nie mo\u017Cna odnale\u017A\u0107 propozycji federacji!
+gb.invalidUsernameOrPassword = B\u0142\u0119dny u\u017Cytkownik lub has\u0142o!
+gb.OneProposalToReview = Jest 1 oczekuj\u0105ca propozycja federacji.
+gb.nFederationProposalsToReview = Jest {0} oczekuj\u0105cych propozycji federacji.
+gb.couldNotFindTag = Nie mo\u017Cna odnale\u017A\u0107 taga {0}
+gb.couldNotCreateFederationProposal = Nie mo\u017Cna utworzy\u0107 propozycji federacji!
+gb.pleaseSetGitblitUrl = Wpisz url serwera Gitblit!
+gb.pleaseSetDestinationUrl = Wpisz url, gdzie ma trafi\u0107 propozycja!
+gb.proposalReceived = Odebrano propozycj\u0119 od {0}.
+gb.noGitblitFound = Przepraszamy, {0} nie mo\u017Cna odnale\u017A\u0107 instancji Gitblit pod {1}.
+gb.noProposals = Przepraszamy, {0} nie akceptuje obecnie propozycji.
+gb.noFederation = Przepraszamy, {0} nie jest skonfigurowany do federacji z jakimikolwiek instancjami Gitblit.
+gb.proposalFailed = Przepraszamy, {0} nie otrzyma\u0142 \u017Cadnych danych propozycji!
+gb.proposalError = Przepraszamy, {0} informuje o nieoczekiwanym b\u0142\u0119dzie!
+gb.failedToSendProposal = B\u0142\u0105d podczas wysy\u0142ania propozycji!
+gb.userServiceDoesNotPermitAddUser = {0} nie zezwala na utworzenie nowego u\u017Cytkownika!
+gb.userServiceDoesNotPermitPasswordChanges = {0} nie zezwala na zmian\u0119 has\u0142a!
+gb.displayName = Wy\u015Bwietlana nazwa
+gb.emailAddress = Adres email
+gb.errorAdminLoginRequired = Administracja wymaga zalogowania
+gb.errorOnlyAdminMayCreateRepository = Tylko administrator mo\u017Ce utworzy\u0107 repozytorium
+gb.errorOnlyAdminOrOwnerMayEditRepository = Tylko administrator lub w\u0142a\u015Bciciel mo\u017Ce edytowa\u0107 repozytorium.
+gb.errorAdministrationDisabled = Administracja jest wy\u0142\u0105czona
+gb.lastNDays = Ostatnich {0} dni
+gb.completeGravatarProfile = Pe\u0142ny profil na Gravatar.com
+gb.none = Brak
+gb.line = Linia
+gb.content = Zawarto\u015B\u0107
+gb.empty = Pusty
+gb.inherited = Odziedziczony
+gb.deleteRepository = Usun\u0105\u0107 repozytorium \"{0}\"?
+gb.repositoryDeleted = Repozytorium ''{0}'' usuni\u0119te.
+gb.repositoryDeleteFailed = B\u0142\u0105d podczas usuwania repozytorium ''{0}''!
+gb.deleteUser = Usun\u0105\u0107 u\u017Cytkownika \"{0}\"?
+gb.userDeleted = U\u017Cytkownik ''{0}'' usuni\u0119ty.
+gb.userDeleteFailed = B\u0142\u0105d podczas usuwania u\u017Cytkownika ''{0}''!
+gb.time.justNow = Przed chwil\u0105
+gb.time.today = Dzisiaj
+gb.time.yesterday = Wczoraj
+gb.time.minsAgo = {0} minut temu
+gb.time.hoursAgo = {0} godzin temu
+gb.time.daysAgo = {0} dni temu
+gb.time.weeksAgo = {0} tygodni temu
+gb.time.monthsAgo = {0} miesi\u0119cy temu
+gb.time.oneYearAgo = Rok temu
+gb.time.yearsAgo = {0} lat temu
+gb.duration.oneDay = Dzie\u0144 temu
+gb.duration.days = {0} dni
+gb.duration.oneMonth = Miesi\u0105c
+gb.duration.months = {0} miesi\u0119cy
+gb.duration.oneYear = rok
+gb.duration.years = {0} lat
+gb.authorizationControl = kontrola autoryzacji
+gb.allowAuthenticatedDescription = udziel ograniczonego dost\u0119pu wszystkim uwierzytelnionym u\u017Cytkownikom
+gb.allowNamedDescription = udziel ograniczonego dost\u0119pu nazwanym u\u017Cytkownikom lub zespo\u0142om
+gb.markdownFailure = Nieudane parsowanie znacznik\u00F3w Markdown!
+gb.clearCache = Wyczy\u015B\u0107 cache
+gb.projects = Projekty
+gb.project = Projekt
+gb.allProjects = Wszystkie projekty
+gb.copyToClipboard = Kopiuj do schowka
+gb.fork = kopia
+gb.forks = Kopie
+gb.forkRepository = Skopiowa\u0107 {0}?
+gb.repositoryForked = {0} zosta\u0142o skopiowane
+gb.repositoryForkFailed= kopiowanie nie powiod\u0142o si\u0119
+gb.personalRepositories = osobiste repozytoria
+gb.allowForks = zezw\u00F3l na kopiowanie
+gb.allowForksDescription = zezw\u00F3l uwierzytelnionym u\u017Cytkownikom na skopiowanie tego repozytorium
+gb.forkedFrom = skopiowane z
+gb.canFork = mo\u017Ce kopiowa\u0107
+gb.canForkDescription = mo\u017Cna kopiowa\u0107 uwierzytelnione repozytoria do osobistych repozytori\u00F3w
+gb.myFork = podejrz moj\u0105 kopi\u0119
+gb.forksProhibited = kopiowanie zabronione
+gb.forksProhibitedWarning = to repozytorium nie zezwala na kopiowanie
+gb.noForks = {0} nie ma kopii
+gb.forkNotAuthorized = Przepraszamy, nie jeste\u015B uprawniony, aby wykona\u0107 kopi\u0119 {0}
+gb.forkInProgress = kopiowanie w trakcie
+gb.preparingFork = przygotowywanie twojej kopii...
+gb.isFork = jest kopi\u0105
+gb.canCreate = mo\u017Ce tworzy\u0107
+gb.canCreateDescription = mo\u017Ce tworzy\u0107 osobiste repozytoria
+gb.illegalPersonalRepositoryLocation = twoje osobiste repozytorium musi znajdowa\u0107 si\u0119 w \"{0}\"
+gb.verifyCommitter = weryfikuj wgrywaj\u0105cego
+gb.verifyCommitterDescription = wymagaj dopasowania to\u017Csamo\u015Bci wgrywaj\u0105cego zmiany do wrzucaj\u0105cego na Gitblita
+gb.verifyCommitterNote = wszystkie z\u0142\u0105czenia wymagaj\u0105 "--no-ff" by wymusi\u0107 to\u017Csamo\u015B\u0107 wgrywaj\u0105cego
+gb.repositoryPermissions = uprawnienia repozytorium
+gb.userPermissions = uprawnienia u\u017Cytkownik\u00F3w
+gb.teamPermissions = uprawnienia zespo\u0142\u00F3w
+gb.add = dodaj
+gb.noPermission = USU\u0143 TO UPRAWNIENIE
+gb.excludePermission = {0} (wykluczenie)
+gb.viewPermission = {0} (podgl\u0105d)
+gb.clonePermission = {0} (klonowanie)
+gb.pushPermission = {0} (wgranie)
+gb.createPermission = {0} (wgranie, utworzenie referencji)
+gb.deletePermission = {0} (wgranie, utworzenie i usuni\u0119cie referencji)
+gb.rewindPermission = {0} (wgranie, utworzenie,usuni\u0119cie i przesuni\u0119cie referencji)
+gb.permission = uprawnienie
+gb.regexPermission = to uprawnienie jest nadane z wyra\u017Cenia regularnego \"{0}\"
+gb.accessDenied = dost\u0119p zabroniony
+gb.busyCollectingGarbage = Przepraszamy, Gitblit jest w trakcie od\u015Bmiecania zasob\u00F3w w {0}
+gb.gcPeriod = okres GC 
+gb.gcPeriodDescription = okres pomi\u0119dzy kolejnymi od\u015Bmieceniami
+gb.gcThreshold = pr\u00F3g GC
+gb.gcThresholdDescription = minimalna liczba wolnych obiekt\u00F3w jaka uruchomi wczesne od\u015Bmiecanie pami\u0119ci
+gb.ownerPermission = w\u0142a\u015Bciciel repozytorium
+gb.administrator = admin
+gb.administratorPermission = administrator Gitblit-a
+gb.team = zesp\u00F3\u0142
+gb.teamPermission = uprawnienie nadane przez cz\u0142onkostwo w zespole \"{0}\"
+gb.missing = brak!
+gb.missingPermission = brak repozytorium dla tego uprawnienia!
+gb.mutable = zmienne
+gb.specified = okre\u015Blone
+gb.effective = faktyczne
+gb.organizationalUnit = jednostka organizacyjna
+gb.organization = organizacja
+gb.locality = miejscowo\u015B\u0107
+gb.stateProvince = stan lub prowincja
+gb.countryCode = kod kraju
+gb.properties = w\u0142a\u015Bciwo\u015Bci
+gb.issued = wydany
+gb.expires = wyga\u015Bnie
+gb.expired = wygas\u0142
+gb.expiring = wygasa
+gb.revoked = cofni\u0119ty
+gb.serialNumber = numer seryjny
+gb.certificates = certyfikaty
+gb.newCertificate = nowy certyfikat
+gb.revokeCertificate = cofnij certyfikat
+gb.sendEmail = wy\u015Blij email
+gb.passwordHint = podpowied\u017A dla has\u0142a
+gb.ok = ok
+gb.invalidExpirationDate = nieprawid\u0142owa data wyga\u015Bni\u0119cia!
+gb.passwordHintRequired = wymagana podpowied\u017A dla has\u0142a!
+gb.viewCertificate = podejrz certyfikat
+gb.subject = podmiot
+gb.issuer = wystawca
+gb.validFrom = wa\u017Cny od
+gb.validUntil = wa\u017Cny do
+gb.publicKey = klucz publiczny
+gb.signatureAlgorithm = algorytm sygnatury
+gb.sha1FingerPrint = Odcisk SHA-1
+gb.md5FingerPrint = Odcisk MD5
+gb.reason = pow\u00F3d
+gb.revokeCertificateReason = Wybierz pow\u00F3d cofni\u0119cia certyfikatu
+gb.unspecified = niewyspecyfikowany
+gb.keyCompromise = kompromitacja klucza
+gb.caCompromise = kompromitacja klucza CA
+gb.affiliationChanged = zmiana przynale\u017Cno\u015Bci
+gb.superseded = odnowienie
+gb.cessationOfOperation = zaprzestanie operacji
+gb.privilegeWithdrawn = wycofanie uprawnie\u0144
+gb.time.inMinutes = w ci\u0105gu {0} minut
+gb.time.inHours = w ci\u0105gu {0} godzin
+gb.time.inDays = w ci\u0105gu {0} dni
+gb.hostname = nazwa hosta
+gb.hostnameRequired = Wpisz nazw\u0119 hosta
+gb.newSSLCertificate = nowy certyfikat SSL serwera
+gb.newCertificateDefaults = nowe warto\u015Bci domy\u015Blne certyfikatu
+gb.duration = czas trwania
+gb.certificateRevoked = Certyfikat {0,number,0} zosta\u0142 cofni\u0119ty
+gb.clientCertificateGenerated = Pomy\u015Blnie wygenerowano kliencki certyfikat dla {0}
+gb.sslCertificateGenerated = Pomy\u015Blnie wygenerowano serwerowy certyfikat SSL dla {0}
+gb.newClientCertificateMessage = NOTATKA:\nWskazane has\u0142o nie jest has\u0142em u\u017Cytkownika; to has\u0142o chroni\u0105ce keystore u\u017Cytkownika. To has\u0142o nie jest zapisywane, dlatego nale\u017Cy poda\u0107 tak\u017Ce wskaz\u00F3wk\u0119 dla has\u0142a, kt\u00F3ra zostanie do\u0142\u0105czona do README dla w\u0142a\u015Bciwego u\u017Cytkownika.
+gb.certificate = certyfikat
+gb.emailCertificateBundle = wy\u015Blij klientowi paczk\u0119 z certyfikatem
+gb.pleaseGenerateClientCertificate = Wygeneruj kliencki certyfikat dla {0}
+gb.clientCertificateBundleSent = Wys\u0142ano paczk\u0119 z klienckim certyfikatem dla {0}
+gb.enterKeystorePassword = Wpisz has\u0142o do keystore dla Gitblit-a
+gb.warning = ostrze\u017Cenie
+gb.jceWarning = Twoje \u015Brodowisko JRE nie posiada plik\u00F3w \"JCE Unlimited Strength Jurisdiction Policy\".\nW zwi\u0105zku z tym nie mo\u017Cesz u\u017Cy\u0107 has\u0142a d\u0142u\u017Cszego ni\u017C 7 znak\u00F3w dla swoich plik\u00F3w keystore.\nWspomniane pliki mo\u017Cna dodatkowo pobra\u0107 z strony Oracle.\n\nCzy pomimo tego kontynuowa\u0107 i utworzy\u0107 infrastruktur\u0119 certyfikat\u00F3w?\n\nWybieraj\u0105c Nie zostaniesz przekierowany do strony pobierania na witrynie Oracle, gdzie mo\u017Cesz pobra\u0107 odpowiednie pliki.
+gb.maxActivityCommits = maksymalnie commit\u00F3w na stronie aktywno\u015Bci
+gb.maxActivityCommitsDescription = maksymalna liczba wy\u015Bwietlanych commit\u00F3w na stronie Aktywno\u015Bci
+gb.noMaximum = brak maksimum
+gb.attributes = atrybuty
+gb.serveCertificate = obs\u0142uguj https z tym certyfikatem
+gb.sslCertificateGeneratedRestart = Pomy\u015Blnie przyznano nowy certyfikat SSL serwera dla {0}.\nNale\u017Cy zrestatowa\u0107 Gitblit-a aby u\u017Cy\u0107 nowego certyfikatu.\n\nJe\u017Celi podczas startu u\u017Cywasz parametru '--alias', musisz ustawi\u0107 go do postaci ''--alias {0}''.
+gb.validity = wa\u017Cno\u015B\u0107
+gb.siteName = nazwa witryny
+gb.siteNameDescription = Kr\u00F3tki, informatywny opis serwera
+gb.excludeFromActivity = wy\u0142\u0105cz z strony aktywno\u015Bci
+gb.isSparkleshared = repozytorium pod\u0142\u0105czone z Sparkleshare
+gb.owners = w\u0142a\u015Bciciele
+gb.sessionEnded = Sesja zosta\u0142a zamkni\u0119ta
+gb.closeBrowser = Zamknij przegl\u0105dark\u0119 aby poprawnie zako\u0144czy\u0107 sesj\u0119.
+gb.doesNotExistInTree = {0} nie istnieje w drzewie {1}
+gb.enableIncrementalPushTags = aktywuj przyrostowe genrowanie tag\u00F3w
+gb.useIncrementalPushTagsDescription = podczas wgrania, automatycznie oznaczaj czubek ka\u017Cdej ga\u0142\u0119zi u\u017Cywaj\u0105c przyrostowo wygenerowanego numeru rewizji
+gb.incrementalPushTagMessage = Automatycznie oznaczono [{0}] ga\u0142\u0105\u017A podczas wgrania
diff --git a/src/main/java/com/gitblit/wicket/GitBlitWebApp_pt_BR.properties b/src/main/java/com/gitblit/wicket/GitBlitWebApp_pt_BR.properties
new file mode 100644
index 0000000..a02d2ff
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/GitBlitWebApp_pt_BR.properties
@@ -0,0 +1,447 @@
+gb.repository = reposit�rio
+gb.owner = propriet�rio
+gb.description = descri��o
+gb.lastChange = �ltima altera��o
+gb.refs = refs
+gb.tag = tag
+gb.tags = tags
+gb.author = autor
+gb.committer = committer
+gb.commit = commit
+gb.tree = �rvore
+gb.parent = parent
+gb.url = URL
+gb.history = hist�rico
+gb.raw = raw
+gb.object = object
+gb.ticketId = ticket id
+gb.ticketAssigned = atribu�do
+gb.ticketOpenDate = data da abertura
+gb.ticketState = estado
+gb.ticketComments = coment�rios
+gb.view = visualizar
+gb.local = local
+gb.remote = remote
+gb.branches = branches
+gb.patch = patch
+gb.diff = diff
+gb.log = log
+gb.moreLogs = mais commits...
+gb.allTags = todas as tags...
+gb.allBranches = todos os branches...
+gb.summary = resumo
+gb.ticket = ticket
+gb.newRepository = novo reposit�rio
+gb.newUser = novo usu�rio
+gb.commitdiff = commitdiff
+gb.tickets = tickets
+gb.pageFirst = primeira
+gb.pagePrevious anterior
+gb.pageNext = pr�xima
+gb.head = HEAD
+gb.blame = blame
+gb.login = login
+gb.logout = logout
+gb.username = username
+gb.password = password
+gb.tagger = tagger
+gb.moreHistory = mais hist�rico...
+gb.difftocurrent = diff para a atual
+gb.search = pesquisar
+gb.searchForAuthor = Procurar por commits cujo autor �
+gb.searchForCommitter = Procurar por commits commitados por
+gb.addition = adicionados
+gb.modification = modificados
+gb.deletion = apagados
+gb.rename = renomear
+gb.metrics = m�tricas
+gb.stats = estat�sticas
+gb.markdown = markdown
+gb.changedFiles = arquivos alterados 
+gb.filesAdded = {0} arquivos adicionados
+gb.filesModified = {0} arquivos modificados
+gb.filesDeleted = {0} arquivos deletados
+gb.filesCopied = {0} arquivos copiados
+gb.filesRenamed = {0} arquivos renomeados
+gb.missingUsername = Faltando username
+gb.edit = editar
+gb.searchTypeTooltip = Selecione o Tipo de Pesquisa
+gb.searchTooltip = Pesquisar {0}
+gb.delete = deletar
+gb.docs = docs
+gb.accessRestriction = restri��o de acesso
+gb.name = nome
+gb.enableTickets = ativar tickets
+gb.enableDocs = ativar documenta��o
+gb.save = salvar
+gb.showRemoteBranches = mostrar branches remotos
+gb.editUsers = editar usu�rios
+gb.confirmPassword = confirmar password
+gb.restrictedRepositories = reposit�rios restritos
+gb.canAdmin = pode administrar
+gb.notRestricted = visualiza��o an�nima, clone, & push
+gb.pushRestricted = push aut�nticado
+gb.cloneRestricted = clone & push aut�nticados
+gb.viewRestricted = view, clone, & push aut�nticados
+gb.useTicketsDescription = somente leitura, issues do Ticgit distribu�dos
+gb.useDocsDescription = enumerar documenta��o Markdown no reposit�rio
+gb.showRemoteBranchesDescription = mostrar branches remotos
+gb.canAdminDescription = pode administrar o server Gitblit
+gb.permittedUsers = usu�rios autorizados
+gb.isFrozen = est� congelado
+gb.isFrozenDescription = proibir fazer push
+gb.zip = zip
+gb.showReadme = mostrar readme
+gb.showReadmeDescription = mostrar um arquivo \"leia-me\" na p�gina de resumo
+gb.nameDescription = usar '/' para agrupar reposit�rios.  e.g. libraries/mycoollib.git
+gb.ownerDescription = o propriet�rio pode editar configura��es do reposit�rio
+gb.blob = blob
+gb.commitActivityTrend = tend�ncia dos commits
+gb.commitActivityDOW = commits por dia da semana
+gb.commitActivityAuthors = principais committers
+gb.feed = feed
+gb.cancel = cancelar
+gb.changePassword = alterar password
+gb.isFederated = est� federado
+gb.federateThis = federar este reposit�rio
+gb.federateOrigin = federar o origin
+gb.excludeFromFederation = excluir da federa��o
+gb.excludeFromFederationDescription = bloquear inst�ncias federadas do GitBlit de fazer pull desta conta
+gb.tokens = tokens de federa��o
+gb.tokenAllDescription = todos reposit�rios, usu�rios & configura��es
+gb.tokenUnrDescription = todos reposit�rios & usu�rios
+gb.tokenJurDescription = todos reposit�rios
+gb.federatedRepositoryDefinitions = defini��es de reposit�rio
+gb.federatedUserDefinitions = defini��es de usu�rios
+gb.federatedSettingDefinitions = defini��es de configura��es
+gb.proposals = propostas de federa��es
+gb.received = recebidos
+gb.type = tipo
+gb.token = token
+gb.repositories = reposit�rios
+gb.proposal = propostas
+gb.frequency = frequ�ncia
+gb.folder = pasta
+gb.lastPull = �ltimo pull
+gb.nextPull = pr�ximo pull
+gb.inclusions = inclus�es
+gb.exclusions = exclu�es
+gb.registration = cadastro
+gb.registrations = cadastro de federa��es
+gb.sendProposal = enviar proposta
+gb.status = status
+gb.origin = origin
+gb.headRef = default branch (HEAD)
+gb.headRefDescription = alterar a ref o qual a HEAD aponta. e.g. refs/heads/master
+gb.federationStrategy = estrat�gia de federa��o 
+gb.federationRegistration = cadastro de federa��es
+gb.federationResults = resultados dos pulls de federa��es
+gb.federationSets = ajustes de federa��es
+gb.message = mensagem
+gb.myUrlDescription = a url de acesso p�blico para a inst�ncia Gitblit
+gb.destinationUrl = enviar para
+gb.destinationUrlDescription = a url da int�ncia do Gitblit para enviar sua proposta
+gb.users = usu�rios
+gb.federation = federa��o
+gb.error = erro
+gb.refresh = atualizar
+gb.browse = navegar
+gb.clone = clonar
+gb.filter = filtrar
+gb.create = criar
+gb.servers = servidores
+gb.recent = recente
+gb.available = dispon�vel
+gb.selected = selecionado
+gb.size = tamanho
+gb.downloading = downloading
+gb.loading = loading
+gb.starting = inciando
+gb.general = geral
+gb.settings = configura��es
+gb.manage = administrar
+gb.lastLogin = �ltimo login
+gb.skipSizeCalculation = ignorar c�lculo do tamanho
+gb.skipSizeCalculationDescription = n�o calcular o tamanho do reposit�rio (reduz o tempo de load da p�gina)
+gb.skipSummaryMetrics = ignorar resumo das m�tricas
+gb.skipSummaryMetricsDescription = n�o calcular m�tricas na p�gina de resumo
+gb.accessLevel = acesso
+gb.default = default
+gb.setDefault = tornar default
+gb.since = desde
+gb.status = status
+gb.bootDate = data do boot
+gb.servletContainer = servlet container
+gb.heapMaximum = heap m�ximo
+gb.heapAllocated = alocar heap
+gb.heapUsed = usar heap
+gb.free = free
+gb.version = vers�o
+gb.releaseDate = data de release
+gb.date = data
+gb.activity = atividade
+gb.subscribe = inscrever
+gb.branch = branch
+gb.maxHits = hits m�ximos
+gb.recentActivity = atividade recente
+gb.recentActivityStats = �ltimos {0} dias / {1} commits por {2} autores
+gb.recentActivityNone = �ltimos {0} dias / nenhum
+gb.dailyActivity = atividade di�ria
+gb.activeRepositories = reposit�rios ativos
+gb.activeAuthors = autores ativos
+gb.commits = commits
+gb.teams = equipes
+gb.teamName = nome da equipe
+gb.teamMembers = membros
+gb.teamMemberships = filia��es em equipes
+gb.newTeam = nova equipe
+gb.permittedTeams = equipes permitidas
+gb.emptyRepository = reposit�rio vazio
+gb.repositoryUrl = url do reposit�rio
+gb.mailingLists = listas de e-mails
+gb.preReceiveScripts = pre-receive scripts
+gb.postReceiveScripts = post-receive scripts
+gb.hookScripts = hook scripts
+gb.customFields = campos customizados
+gb.customFieldsDescription = campos customizados dispon�veis para Groovy hooks
+gb.accessPermissions = permiss�es de acesso
+gb.filters = filtros
+gb.generalDescription = configura��es comuns
+gb.accessPermissionsDescription = restringir acesso por usu�rios e equipes
+gb.accessPermissionsForUserDescription = ajustar filia��es em equipes ou garantir acesso a reposit�rios espec�ficos
+gb.accessPermissionsForTeamDescription = ajustar membros da equipe e garantir acesso a reposit�rios espec�ficos
+gb.federationRepositoryDescription = compartilhar este reposit�rio com outros servidores Gitblit
+gb.hookScriptsDescription = rodar scripts Groovy nos pushes para este servidor Gitblit
+gb.reset = reset
+gb.pages = p�ginas
+gb.workingCopy = working copy
+gb.workingCopyWarning = este reposit�rio tem uma working copy e n�o pode receber pushes
+gb.query = query
+gb.queryHelp =  Standard query syntax � suportada.<p/><p/>Por favor veja <a target="_new" href="http://lucene.apache.org/core/old_versioned_docs/versions/3_5_0/queryparsersyntax.html">Lucene Query Parser Syntax</a> para mais detalhes.
+gb.queryResults = resultados {0} - {1} ({2} hits)
+gb.noHits = sem hits
+gb.authored = foi autor de
+gb.committed = committed
+gb.indexedBranches = branches indexados
+gb.indexedBranchesDescription = selecione os branches para incluir nos seus �ndices Lucene
+gb.noIndexedRepositoriesWarning = nenhum dos seus reposit�rios foram configurados para indexa��o do Lucene
+gb.undefinedQueryWarning = a query n�o foi definida!
+gb.noSelectedRepositoriesWarning = por favor selecione um ou mais reposit�rios!
+gb.luceneDisabled = indexa��o do Lucene est� desabilitada
+gb.failedtoRead = leitura falhou
+gb.isNotValidFile = n�o � um arquivo v�lido
+gb.failedToReadMessage = Falhou em ler mensagens default de {0}!
+gb.passwordsDoNotMatch = Passwords n�o conferem!
+gb.passwordTooShort = Password � muito curto. Tamanho m�nimo s�o {0} caracteres.
+gb.passwordChanged = Password alterado com sucesso.
+gb.passwordChangeAborted = altera��o do password foi abortada.
+gb.pleaseSetRepositoryName = Por favor ajuste o nome do reposit�rio!
+gb.illegalLeadingSlash = Refer�ncias a diret�rios raiz come�ando com (/) s�o proibidas.
+gb.illegalRelativeSlash = Refer�ncias a diret�rios relativos (../) s�o proibidas.
+gb.illegalCharacterRepositoryName = Caractere ilegal ''{0}'' no nome do reposit�rio!
+gb.selectAccessRestriction = Por favor selecione a restri��o de acesso!
+gb.selectFederationStrategy = Por favor selecione a estrat�gia de federa��o!
+gb.pleaseSetTeamName = Por favor insira um nome de equipe!
+gb.teamNameUnavailable = O nome de equipe ''{0}'' est� indispon�vel.
+gb.teamMustSpecifyRepository = Uma equipe deve especificar pelo menos um reposit�rio.
+gb.teamCreated = Nova equipe ''{0}'' criada com sucesso.
+gb.pleaseSetUsername = Por favor entre com um username!
+gb.usernameUnavailable = Username ''{0}'' est� indispon�vel.
+gb.combinedMd5Rename = Gitblit est� configurado para usar um hash combinado-md5. Voc� deve inserir um novo password ao renamear a conta.
+gb.userCreated = Novo usu�rio ''{0}'' criado com sucesso.
+gb.couldNotFindFederationRegistration = N�o foi poss�vel localizar o registro da federa��o!
+gb.failedToFindGravatarProfile = Falhou em localizar um perfil Gravatar para {0}
+gb.branchStats = {0} commits e {1} tags em {2}
+gb.repositoryNotSpecified = Reposit�rio n�o espec�ficado!
+gb.repositoryNotSpecifiedFor = Reposit�rio n�o espec�ficado para {0}!
+gb.canNotLoadRepository = N�o foi poss�vel carregar o reposit�rio
+gb.commitIsNull = Commit est� nulo
+gb.unauthorizedAccessForRepository = Acesso n�o autorizado para o reposit�rio
+gb.failedToFindCommit = N�o foi poss�vel achar o commit \"{0}\" em {1} para {2} p�gina!
+gb.couldNotFindFederationProposal = N�o foi poss�vel localizar propostas de federa��o!
+gb.invalidUsernameOrPassword = username ou password inv�lido!
+gb.OneProposalToReview = Existe uma proposta de federa��o aguardando revis�o. 
+gb.nFederationProposalsToReview = Existem {0} propostas de federa��o aguardando revis�o.
+gb.couldNotFindTag = N�o foi poss�vel localizar a tag {0}
+gb.couldNotCreateFederationProposal = N�o foi poss�vel criar uma proposta de federation!
+gb.pleaseSetGitblitUrl = Por favor insira sua url do Gitblit!
+gb.pleaseSetDestinationUrl = Por favor insira a url de destino para sua proposta!
+gb.proposalReceived = Proposta recebida com sucesso por {0}.
+gb.noGitblitFound = Desculpe, {0} n�o localizou uma inst�ncia do Gitblit em {1}.
+gb.noProposals = Desculpe, {0} n�o est� aceitando propostas agora.
+gb.noFederation = Desculpe, {0} n�o est� configurado com nenhuma int�ncia do Gitblit.
+gb.proposalFailed = Desculpe, {0} n�o recebeu nenhum dado de proposta!
+gb.proposalError = Desculpe, {0} reportou que um erro inesperado ocorreu!
+gb.failedToSendProposal = N�o foi poss�vel enviar a proposta!
+gb.userServiceDoesNotPermitAddUser = {0} n�o permite adicionar uma conta de usu�rio!
+gb.userServiceDoesNotPermitPasswordChanges = {0} n�o permite altera��es no password!
+gb.displayName = nome
+gb.emailAddress = e-mail
+gb.errorAdminLoginRequired = Administra��o requer um login
+gb.errorOnlyAdminMayCreateRepository = Somente umadministrador pode criar um reposit�rio
+gb.errorOnlyAdminOrOwnerMayEditRepository = Somente umadministrador pode editar um reposit�rio
+gb.errorAdministrationDisabled = Administra��o est� desabilitada
+gb.lastNDays = �ltimos {0} dias
+gb.completeGravatarProfile = Profile completo em Gravatar.com
+gb.none = nenhum
+gb.line = linha
+gb.content = conte�do
+gb.empty = vazio
+gb.inherited = herdado
+gb.deleteRepository = Deletar reposit�rio \"{0}\"?
+gb.repositoryDeleted = Reposit�rio ''{0}'' deletado.
+gb.repositoryDeleteFailed = N�o foi poss�vel apagar o reposit�rio ''{0}''!
+gb.deleteUser = Deletar usu�rio \"{0}\"?
+gb.userDeleted = Usu�rio ''{0}'' deletado.
+gb.userDeleteFailed = N�o foi poss�vel apagar o usu�rio ''{0}''!
+gb.time.justNow = agora mesmo
+gb.time.today = hoje
+gb.time.yesterday = ontem
+gb.time.minsAgo = h� {0} minutos
+gb.time.hoursAgo = h� {0} horas
+gb.time.daysAgo = h� {0} dias
+gb.time.weeksAgo = h� {0} semanas
+gb.time.monthsAgo = h� {0} meses
+gb.time.oneYearAgo = h� 1 ano
+gb.time.yearsAgo = h� {0} anos 
+gb.duration.oneDay = 1 dia
+gb.duration.days = {0} dias
+gb.duration.oneMonth = 1 m�s
+gb.duration.months = {0} meses
+gb.duration.oneYear = 1 ano
+gb.duration.years = {0} anos
+gb.authorizationControl = controle de autoriza��o
+gb.allowAuthenticatedDescription = conceder permiss�o RW+ para todos os usu�rios aut�nticados
+gb.allowNamedDescription = conceder permiss�es refinadas para usu�rios escolhidos ou equipes
+gb.markdownFailure = N�o foi poss�vel converter conte�do Markdown!
+gb.clearCache = limpar o cache
+gb.projects = projetos
+gb.project = projeto
+gb.allProjects = todos projetos
+gb.copyToClipboard = copiar para o clipboard
+gb.fork = fork
+gb.forks = forks
+gb.forkRepository = fork {0}?
+gb.repositoryForked = fork feito em {0}
+gb.repositoryForkFailed= n�o foi poss�vel fazer fork
+gb.personalRepositories = reposit�rios pessoais
+gb.allowForks = permitir forks
+gb.allowForksDescription = permitir usu�rios autorizados a fazer fork deste reposit�rio
+gb.forkedFrom = forked de
+gb.canFork = pode fazer fork
+gb.canForkDescription = pode fazer fork de reposit�rios autorizados para reposit�rios pessoais
+gb.myFork = visualizar meu fork
+gb.forksProhibited = forks proibidos
+gb.forksProhibitedWarning = este reposit�rio pro�be forks
+gb.noForks = {0} n�o possui forks
+gb.forkNotAuthorized = desculpe, voc� n�o est� autorizado a fazer fork de {0}
+gb.forkInProgress = fork em progresso
+gb.preparingFork = preparando seu fork...
+gb.isFork = � fork
+gb.canCreate = pode criar
+gb.canCreateDescription = pode criar reposit�rios pessoais
+gb.illegalPersonalRepositoryLocation = seu reposit�rio pessoal deve estar localizado em \"{0}\"
+gb.verifyCommitter = verificar committer
+gb.verifyCommitterDescription = requer a identidade do committer para combinar com uma conta do Gitblt
+gb.verifyCommitterNote = todos os merges requerem "--no-ff" para impor a identidade do committer
+gb.repositoryPermissions = permiss�es de reposit�rio
+gb.userPermissions = permiss�es de usu�rio
+gb.teamPermissions = permiss�es de equipe
+gb.add = add
+gb.noPermission = APAGAR ESTA PERMISS�O
+gb.excludePermission = {0} (excluir)
+gb.viewPermission = {0} (visualizar)
+gb.clonePermission = {0} (clonar)
+gb.pushPermission = {0} (push)
+gb.createPermission = {0} (push, ref creation)
+gb.deletePermission = {0} (push, ref creation+deletion)
+gb.rewindPermission = {0} (push, ref creation+deletion+rewind)
+gb.permission = permiss�o
+gb.regexPermission = esta permiss�o foi configurada atrav�s da express�o regular \"{0}\"
+gb.accessDenied = acesso negado
+gb.busyCollectingGarbage = desculpe, o Gitblit est� ocupado coletando lixo em {0}
+gb.gcPeriod = per�odo do GC
+gb.gcPeriodDescription = dura��o entre as coletas de lixo
+gb.gcThreshold = limite do GC 
+gb.gcThresholdDescription = tamanho total m�nimo de objetos \"soltos\" que ativam a coleta de lixo
+gb.ownerPermission = propriet�rio do reposit�rio
+gb.administrator = administrador
+gb.administratorPermission = administrador do Gitblit
+gb.team = equipe
+gb.teamPermission = permiss�o concedida pela filia��o a equipe \"{0}\"
+gb.missing = faltando!
+gb.missingPermission = o reposit�rio para esta permiss�o est� faltando!
+gb.mutable = mut�vel
+gb.specified = espec�fico
+gb.effective = efetivo
+gb.organizationalUnit = unidade organizacional
+gb.organization = organiza��o
+gb.locality = localidade
+gb.stateProvince = estado ou prov�ncia
+gb.countryCode = c�digo do pa�s
+gb.properties = propriedades
+gb.issued = emitido
+gb.expires = expira
+gb.expired = expirado
+gb.expiring = expirando
+gb.revoked = revogado
+gb.serialNumber = n�mero serial
+gb.certificates = certificados
+gb.newCertificate = novo certificado
+gb.revokeCertificate = revogar certificado
+gb.sendEmail = enviar email
+gb.passwordHint = dica de password
+gb.ok = ok
+gb.invalidExpirationDate = data de expira��o inv�lida!
+gb.passwordHintRequired = dica de password requerida!
+gb.viewCertificate = visualizar certificado
+gb.subject = assunto
+gb.issuer = emissor
+gb.validFrom = v�lido a partir de
+gb.validUntil = v�lido at�
+gb.publicKey = chave p�blica
+gb.signatureAlgorithm = algoritmo de assinatura
+gb.sha1FingerPrint = digital SHA-1 
+gb.md5FingerPrint = digital MD5
+gb.reason = raz�o
+gb.revokeCertificateReason = Por selecione a raz�o da revoga��o do certificado
+gb.unspecified = n�o espec�fico
+gb.keyCompromise = comprometimento de chave
+gb.caCompromise = compromisso CA
+gb.affiliationChanged = afilia��o foi alterada
+gb.superseded = substitu�das
+gb.cessationOfOperation = cessa��o de funcionamento
+gb.privilegeWithdrawn = privil�gio retirado
+gb.time.inMinutes = em {0} minutos
+gb.time.inHours = em {0} horas
+gb.time.inDays = em {0} dias
+gb.hostname = hostname
+gb.hostnameRequired = Por favor insira um hostname
+gb.newSSLCertificate = novo servidor de certificado SSL
+gb.newCertificateDefaults = novos padr�es de certifica��o
+gb.duration = dura��o
+gb.certificateRevoked = Certificado {0, n�mero, 0} foi revogado
+gb.clientCertificateGenerated = Novo certificado cliente para {0} foi gerado com sucesso
+gb.sslCertificateGenerated = Novo servidor de certificado SSL gerado com sucesso para {0}
+gb.newClientCertificateMessage = OBSERVA��O:\nO 'password' n�o � o password do usu�rio mas sim o password usado para proteger a keystore.  Este password n�o ser� salvo ent�o voc� tamb�m inserir uma dica que ser� inclu�da nas instru��es de LEIA-ME do usu�rio.
+gb.certificate = certificado
+gb.emailCertificateBundle = pacote certificado de cliente de email
+gb.pleaseGenerateClientCertificate = Por favor gere um certificado cliente para {0}
+gb.clientCertificateBundleSent = Pacote de certificado de cliente para {0} enviada
+gb.enterKeystorePassword = Por favor insira uma chave para keystore do Gitblit
+gb.warning = warning
+gb.jceWarning = Seu Java Runtime Environment n�o tem os arquivos \"JCE Unlimited Strength Jurisdiction Policy\".\nIsto ir� limitar o tamanho dos passwords que voc� usar� para encriptar suas keystores para 7 caracteres.\nEstes arquivos de pol�ticas s�o um download opcional da Oracle.\n\nVoc� gostaria de continuar e gerar os certificados de infraestrutura de qualquer forma?\n\nRespondendo "N�o" ir� redirecionar o seu browser para a p�gina de downloads da Oracle, de onde voc� poder� fazer download desses arquivos.
+gb.maxActivityCommits = limitar exibi��o de commits
+gb.maxActivityCommitsDescription = quantidade m�xima de commits para contribuir para a p�gina de atividade
+gb.noMaximum = ilimitado
+gb.attributes = atributos
+gb.serveCertificate = servir https com este certificado
+gb.sslCertificateGeneratedRestart = Novo certificado SSL de servidor gerado com sucesso para {0}.\nVoc� deve reiniciar o Gitblit para usar o novo certificado.\n\nSe voc� estiver executando com o par�metro '--alias', voc� precisar� alter�-lo para ''--alias {0}''.
+gb.validity = validade
+gb.siteName = nome do site
+gb.siteNameDescription = breve, mas ainda assim um nome descritivo para seu servidor
+gb.excludeFromActivity = excluir da p�gina de atividades
+gb.isSparkleshared = reposit�rio � Sparkleshared
+gb.owners = propriet�rios
+gb.sessionEnded = Session has been closed
+gb.closeBrowser = Please close the browser to properly end the session.
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/GitBlitWebApp_zh_CN.properties b/src/main/java/com/gitblit/wicket/GitBlitWebApp_zh_CN.properties
new file mode 100644
index 0000000..3f1ab0d
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/GitBlitWebApp_zh_CN.properties
@@ -0,0 +1,504 @@
+gb.repository = \u7248\u672c\u5e93
+gb.owner = \u7ba1\u7406\u5458
+gb.description = \u63cf\u8ff0
+gb.lastChange = \u6700\u8fd1\u4fee\u6539
+gb.refs = refs
+gb.tag = \u6807\u7b7e
+gb.tags = \u6807\u7b7e
+gb.author = \u7528\u6237
+gb.committer = \u63d0\u4ea4\u8005
+gb.commit = \u63d0\u4ea4
+gb.tree = \u76ee\u5f55
+gb.parent = parent
+gb.url = URL
+gb.history = \u5386\u53f2\u4fe1\u606f
+gb.raw = raw
+gb.object = object
+gb.ticketId = ticket id
+gb.ticketAssigned = assigned
+gb.ticketOpenDate = \u5f00\u542f\u65e5\u671f
+gb.ticketState = \u72b6\u6001
+gb.ticketComments = \u8bc4\u8bba
+gb.view = \u67e5\u770b
+gb.local = \u672c\u5730
+gb.remote = \u8fdc\u7a0b
+gb.branches = \u5206\u652f
+gb.patch = patch
+gb.diff = \u5bf9\u6bd4
+gb.log = \u65e5\u5fd7
+gb.moreLogs = \u66f4\u591a\u63d0\u4ea4...
+gb.allTags = \u6240\u6709\u6807\u7b7e...
+gb.allBranches = \u6240\u6709\u5206\u652f...
+gb.summary = \u6982\u51b5
+gb.ticket = ticket
+gb.newRepository = \u521b\u5efa\u7248\u672c\u5e93
+gb.newUser = \u6dfb\u52a0\u7528\u6237
+gb.commitdiff = \u5bf9\u6bd4\u63d0\u4ea4\u7684\u5185\u5bb9
+gb.tickets = tickets
+gb.pageFirst = \u9996\u9875
+gb.pagePrevious = \u524d\u4e00\u9875
+gb.pageNext = \u4e0b\u4e00\u9875
+gb.head = HEAD
+gb.blame = blame
+gb.login = \u767b\u5f55
+gb.logout = \u6ce8\u9500
+gb.username = \u7528\u6237\u540d
+gb.password = \u5bc6\u7801
+gb.tagger = \u6807\u8bb0\u8005
+gb.moreHistory = \u66f4\u591a\u7684\u5386\u53f2\u4fe1\u606f...
+gb.difftocurrent = \u5bf9\u6bd4\u5f53\u524d
+gb.search = \u641c\u7d22
+gb.searchForAuthor = \u6309\u4f5c\u8005\u641c\u7d22 commits
+gb.searchForCommitter = \u6309\u63d0\u4ea4\u8005\u641c\u7d22 commits
+gb.addition = \u6dfb\u52a0
+gb.modification = \u4fee\u6539
+gb.deletion = \u5220\u9664
+gb.rename = \u91cd\u547d\u540d
+gb.metrics = metrics
+gb.stats = \u7edf\u8ba1
+gb.markdown = markdown
+gb.changedFiles = \u5df2\u4fee\u6539\u6587\u4ef6
+gb.filesAdded = {0}\u4e2a\u6587\u4ef6\u5df2\u6dfb\u52a0
+gb.filesModified = {0}\u4e2a\u6587\u4ef6\u5df2\u4fee\u6539
+gb.filesDeleted = {0}\u4e2a\u6587\u4ef6\u5df2\u5220\u9664
+gb.filesCopied = {0} \u6587\u4ef6\u5df2\u590d\u5236
+gb.filesRenamed = {0} \u6587\u4ef6\u5df2\u91cd\u547d\u540d
+gb.missingUsername = \u7528\u6237\u540d\u4e0d\u5b58\u5728
+gb.edit = \u7f16\u8f91
+gb.searchTypeTooltip = \u9009\u62e9\u641c\u7d22\u7c7b\u578b
+gb.searchTooltip = \u641c\u7d22 {0}
+gb.delete = \u5220\u9664
+gb.docs = \u6587\u6863
+gb.accessRestriction = \u8bbf\u95ee\u9650\u5236
+gb.name = \u540d\u79f0
+gb.enableTickets = \u5141\u8bb8 tickets
+gb.enableDocs = \u5141\u8bb8\u6587\u6863
+gb.save = \u4fdd\u5b58
+gb.showRemoteBranches = \u663e\u793a\u8fdc\u7a0b\u5206\u652f
+gb.editUsers = \u7f16\u8f91\u7528\u6237
+gb.confirmPassword = \u786e\u8ba4\u5bc6\u7801
+gb.restrictedRepositories = \u7248\u672c\u5e93\u8bbe\u7f6e
+gb.canAdmin = \u7ba1\u7406\u6743\u9650
+gb.notRestricted = anonymous view, clone, & push
+gb.pushRestricted = authenticated push
+gb.cloneRestricted = authenticated clone & push
+gb.viewRestricted = authenticated view, clone, & push
+gb.useTicketsDescription = distributed Ticgit issues
+gb.useDocsDescription = \u5217\u51fa\u7248\u672c\u5e93\u5185\u6240\u6709 Markdown \u6587\u6863
+gb.showRemoteBranchesDescription = \u663e\u793a\u8fdc\u7a0b\u5206\u652f
+gb.canAdminDescription = Gitblit \u670d\u52a1\u5668\u7ba1\u7406\u5458
+gb.permittedUsers = \u5141\u8bb8\u7528\u6237
+gb.isFrozen = \u88ab\u51bb\u7ed3
+gb.isFrozenDescription = \u7981\u6b62\u63a8\u9001\u64cd\u4f5c
+gb.zip = zip
+gb.showReadme = \u663e\u793areadme
+gb.showReadmeDescription = \u5728\u6982\u51b5\u9875\u9762\u663e\u793a \\"readme\\" Markdown \u6587\u4ef6
+gb.nameDescription = \u4f7f\u7528 '/' \u5bf9\u7248\u672c\u5e93\u8fdb\u884c\u5206\u7ec4  \u4f8b\u5982. libraries/mycoollib.git
+gb.ownerDescription = \u521b\u5efa\u8005\u53ef\u4ee5\u7f16\u8f91\u7248\u672c\u5e93\u5c5e\u6027
+gb.blob = blob
+gb.commitActivityTrend = commit \u6d3b\u52a8\u8d8b\u52bf
+gb.commitActivityDOW = \u6bcf\u5468 commit \u6d3b\u52a8
+gb.commitActivityAuthors = commit \u6d3b\u52a8\u4e3b\u8981\u7528\u6237
+gb.feed = feed
+gb.cancel = \u53d6\u6d88
+gb.changePassword = \u4fee\u6539\u5bc6\u7801
+gb.isFederated = is federated
+gb.federateThis = federate this repository
+gb.federateOrigin = federate the origin
+gb.excludeFromFederation = exclude from federation
+gb.excludeFromFederationDescription = \u7981\u6b62\u5df2 federated \u7684 Gitblit \u5b9e\u4f8b\u4ece\u672c\u8d26\u6237\u62c9\u53d6
+gb.tokens = federation tokens
+gb.tokenAllDescription = all repositories, users, & settings
+gb.tokenUnrDescription = all repositories & users
+gb.tokenJurDescription = all repositories
+gb.federatedRepositoryDefinitions = \u7248\u672c\u5e93\u5b9a\u4e49
+gb.federatedUserDefinitions = \u7528\u6237\u5b9a\u4e49
+gb.federatedSettingDefinitions = \u8bbe\u7f6e\u5b9a\u4e49
+gb.proposals = federation proposals
+gb.received = \u5df2\u63a5\u53d7
+gb.type = type
+gb.token = token
+gb.repositories = \u7248\u672c\u5e93
+gb.proposal = proposal
+gb.frequency = \u9891\u7387
+gb.folder = \u6587\u4ef6\u5939
+gb.lastPull = \u4e0a\u4e00\u6b21\u62c9\u53d6
+gb.nextPull = \u4e0b\u4e00\u6b21\u62c9\u53d6
+gb.inclusions = \u5305\u542b\u5185\u5bb9
+gb.exclusions = \u4f8b\u5916
+gb.registration = \u6ce8\u518c
+gb.registrations = federation \u6ce8\u518c
+gb.sendProposal = propose
+gb.status = \u72b6\u6001
+gb.origin = origin
+gb.headRef = \u9ed8\u8ba4\u5206\u652f (HEAD)
+gb.headRefDescription = \u4fee\u6539 HEAD \u6240\u6307\u5411\u7684 ref\u3002 \u4f8b\u5982: refs/heads/master
+gb.federationStrategy = federation \u7b56\u7565
+gb.federationRegistration = federation \u6ce8\u518c
+gb.federationResults = federation \u62c9\u53d6\u7ed3\u679c
+gb.federationSets = federation \u96c6
+gb.message = \u6d88\u606f
+gb.myUrlDescription = \u60a8\u7684 Gitblit \u5b9e\u4f8b\u7684\u516c\u5171\u8bbf\u95ee\u7f51\u5740
+gb.destinationUrl = \u53d1\u9001\u81f3
+gb.destinationUrlDescription = \u4f60\u6240\u8981\u53d1\u9001proposal\u7684 Gitblit \u5b9e\u4f8b\u7f51\u5740
+gb.users = \u7528\u6237
+gb.federation = federation
+gb.error = \u9519\u8bef
+gb.refresh = \u5237\u65b0
+gb.browse = \u6d4f\u89c8
+gb.clone = \u514b\u9686
+gb.filter = \u8fc7\u6ee4
+gb.create = \u521b\u5efa
+gb.servers = \u670d\u52a1\u5668
+gb.recent = \u6700\u8fd1
+gb.available = \u53ef\u7528
+gb.selected = \u5df2\u9009\u4e2d
+gb.size = \u5927\u5c0f
+gb.downloading = \u4e0b\u8f7d\u4e2d
+gb.loading = \u8f7d\u5165\u4e2d
+gb.starting = \u542f\u52a8\u4e2d
+gb.general = \u5e38\u89c4
+gb.settings = \u8bbe\u7f6e
+gb.manage = \u7ba1\u7406
+gb.lastLogin = \u4e0a\u6b21\u767b\u5f55
+gb.skipSizeCalculation = \u5ffd\u7565\u5927\u5c0f\u4f30\u8ba1
+gb.skipSizeCalculationDescription = \u4e0d\u8ba1\u7b97\u7248\u672c\u5e93\u5927\u5c0f\uff08\u8282\u7701\u9875\u9762\u8f7d\u5165\u65f6\u95f4\uff09
+gb.skipSummaryMetrics = \u5ffd\u7565\u6982\u51b5\u5904 metrics
+gb.skipSummaryMetricsDescription = \u6982\u51b5\u9875\u9762\u4e0d\u8ba1\u7b97metrics\uff08\u8282\u7701\u9875\u9762\u8f7d\u5165\u65f6\u95f4\uff09
+gb.accessLevel = \u8bbf\u95ee\u7ea7\u522b
+gb.default = \u9ed8\u8ba4
+gb.setDefault = \u9ed8\u8ba4\u8bbe\u7f6e
+gb.since = \u81ea\u4ece
+gb.status = \u72b6\u6001
+gb.bootDate = \u542f\u52a8\u65e5\u671f
+gb.servletContainer = servlet container
+gb.heapMaximum = \u6700\u5927\u5806
+gb.heapAllocated = \u5df2\u5206\u914d\u5806
+gb.heapUsed = \u5df2\u4f7f\u7528\u5806
+gb.free = \u7a7a\u95f2
+gb.version = \u7248\u672c
+gb.releaseDate = \u53d1\u884c\u65e5\u671f
+gb.date = \u65e5\u671f
+gb.activity = \u6d3b\u52a8
+gb.subscribe = \u8ba2\u9605
+gb.branch = \u5206\u652f
+gb.maxHits = \u6700\u5927\u547d\u4e2d\u6570
+gb.recentActivity = \u6700\u8fd1\u6d3b\u52a8
+gb.recentActivityStats = \u6700\u8fd1{0}\u5929 / {2}\u4f4d\u7528\u6237\u505a\u4e86{1}\u6b21\u63d0\u4ea4
+gb.recentActivityNone = \u6700\u8fd1{0}\u5929 / \u6ca1\u6709\u6d3b\u52a8
+gb.dailyActivity = \u65e5\u5e38\u6d3b\u52a8
+gb.activeRepositories = \u6d3b\u8dc3\u7684\u7248\u672c\u5e93
+gb.activeAuthors = \u6d3b\u8dc3\u7528\u6237
+gb.commits = \u63d0\u4ea4\u6b21\u6570
+gb.teams = \u56e2\u961f
+gb.teamName = \u56e2\u961f\u540d\u79f0
+gb.teamMembers = \u56e2\u961f\u6210\u5458
+gb.teamMemberships = \u56e2\u961f\u6210\u5458
+gb.newTeam = \u6dfb\u52a0\u56e2\u961f
+gb.permittedTeams = \u5141\u8bb8\u56e2\u961f
+gb.emptyRepository = \u7a7a\u7248\u672c\u5e93
+gb.repositoryUrl = \u7248\u672c\u5e93\u5730\u5740
+gb.mailingLists = \u90ae\u4ef6\u5217\u8868
+gb.preReceiveScripts = pre-receive \u811a\u672c
+gb.postReceiveScripts = post-receive \u811a\u672c
+gb.hookScripts = hook \u811a\u672c
+gb.customFields = \u81ea\u5b9a\u4e49\u57df
+gb.customFieldsDescription = Groovy\u811a\u672c\u652f\u6301\u7684\u81ea\u5b9a\u4e49\u57df
+gb.accessPermissions = \u8bbf\u95ee\u6743\u9650
+gb.filters = \u8fc7\u6ee4
+gb.generalDescription = \u4e00\u822c\u8bbe\u7f6e
+gb.accessPermissionsDescription = \u6309\u7167\u7528\u6237\u548c\u56e2\u961f\u9650\u5236\u8bbf\u95ee
+gb.accessPermissionsForUserDescription = \u8bbe\u7f6e\u56e2\u961f\u6210\u5458\u6216\u8005\u6388\u4e88\u6307\u5b9a\u7248\u672c\u5e93\u6743\u9650
+gb.accessPermissionsForTeamDescription = \u8bbe\u7f6e\u56e2\u961f\u6210\u5458\u5e76\u6388\u4e88\u6307\u5b9a\u7248\u672c\u5e93\u6743\u9650
+gb.federationRepositoryDescription = \u4e0e\u5176\u4ed6Gitblit\u670d\u52a1\u5668\u5206\u4eab\u7248\u672c\u5e93
+gb.hookScriptsDescription = \u5728\u670d\u52a1\u5668\u4e0a\u8fd0\u884cGroovy\u811a\u672c
+gb.reset = \u91cd\u7f6e
+gb.pages = \u9875\u9762
+gb.workingCopy = \u5de5\u4f5c\u526f\u672c
+gb.workingCopyWarning = \u6b64\u7248\u672c\u5e93\u5b58\u5728\u4e00\u4efd\u5de5\u4f5c\u526f\u672c\uff0c\u65e0\u6cd5\u8fdb\u884c\u63a8\u9001
+gb.query = \u67e5\u8be2
+gb.queryHelp = \u652f\u6301\u6807\u51c6\u67e5\u8be2\u683c\u5f0f.<p/><p/>\u8bf7\u67e5\u770b <a target="_new" href="http://lucene.apache.org/core/old_versioned_docs/versions/3_5_0/queryparsersyntax.html">Lucene \u67e5\u8be2\u5904\u7406\u5668\u683c\u5f0f</a> \u4ee5\u83b7\u53d6\u8be6\u7ec6\u5185\u5bb9\u3002
+gb.queryResults = \u7ed3\u679c {0} - {1} ({2} \u6b21\u547d\u4e2d)
+gb.noHits = \u672a\u547d\u4e2d
+gb.authored = authored
+gb.committed = committed
+gb.indexedBranches = \u5df2\u7d22\u5f15\u5206\u652f
+gb.indexedBranchesDescription = \u9009\u62e9\u8981\u653e\u5165\u4f60\u7684 Lucene \u7d22\u5f15\u7684\u5206\u652f
+gb.noIndexedRepositoriesWarning = \u60a8\u7684\u6240\u6709\u7248\u672c\u5e93\u90fd\u6ca1\u6709\u7ecf\u8fc7Lucene\u7d22\u5f15
+gb.undefinedQueryWarning = \u67e5\u8be2\u672a\u5b9a\u4e49!
+gb.noSelectedRepositoriesWarning = \u8bf7\u81f3\u5c11\u9009\u62e9\u4e00\u4e2a\u7248\u672c\u5e93!
+gb.luceneDisabled = Lucene\u7d22\u5f15\u5df2\u88ab\u7981\u6b62
+gb.failedtoRead = \u8bfb\u53d6\u5931\u8d25
+gb.isNotValidFile = \u4e0d\u662f\u5408\u6cd5\u6587\u4ef6
+gb.failedToReadMessage = \u5728 {0} \u4e2d\u8bfb\u53d6\u9ed8\u8ba4\u6d88\u606f\u5931\u8d25!
+gb.passwordsDoNotMatch = \u5bc6\u7801\u4e0d\u5339\u914d!
+gb.passwordTooShort = \u5bc6\u7801\u957f\u5ea6\u592a\u77ed\u3002\u6700\u77ed\u957f\u5ea6 {0} \u4e2a\u5b57\u7b26\u3002
+gb.passwordChanged = \u5bc6\u7801\u4fee\u6539\u6210\u529f\u3002
+gb.passwordChangeAborted = \u5bc6\u7801\u4fee\u6539\u7ec8\u6b62
+gb.pleaseSetRepositoryName = \u8bf7\u8bbe\u7f6e\u4e00\u4e2a\u7248\u672c\u5e93\u540d\u79f0!
+gb.illegalLeadingSlash = \u7981\u6b62\u4f7f\u7528\u6839\u76ee\u5f55\u5f15\u7528 (/) \u3002
+gb.illegalRelativeSlash = \u76f8\u5bf9\u6587\u4ef6\u5939\u8def\u5f84(../)\u7981\u6b62\u4f7f\u7528
+gb.illegalCharacterRepositoryName = \u7248\u672c\u5e93\u4e2d\u542b\u6709\u4e0d\u5408\u6cd5\u5b57\u7b26 ''{0}'' !
+gb.selectAccessRestriction = \u8bf7\u9009\u62e9\u8bbf\u95ee\u6743\u9650\uff01
+gb.selectFederationStrategy = \u8bf7\u9009\u62e9federation\u7b56\u7565!
+gb.pleaseSetTeamName = \u8bf7\u8f93\u5165\u4e00\u4e2a\u56e2\u961f\u540d\u79f0\uff01
+gb.teamNameUnavailable = \u56e2\u961f\u540d ''{0}'' \u4e0d\u5408\u6cd5.
+gb.teamMustSpecifyRepository = \u56e2\u961f\u5fc5\u987b\u62e5\u6709\u81f3\u5c11\u4e00\u4e2a\u7248\u672c\u5e93\u3002
+gb.teamCreated = \u6210\u529f\u521b\u5efa\u65b0\u56e2\u961f ''{0}'' .
+gb.pleaseSetUsername = \u8bf7\u8f93\u5165\u7528\u6237\u540d\uff01
+gb.usernameUnavailable = \u7528\u6237\u540d ''{0}'' \u4e0d\u53ef\u7528..
+gb.combinedMd5Rename = Gitblit\u91c7\u7528\u6df7\u5408md5\u5bc6\u7801\u54c8\u5e0c\u3002\u56e0\u6b64\u5fc5\u987b\u5728\u4fee\u6539\u7528\u6237\u540d\u540e\u4fee\u6539\u5bc6\u7801\u3002
+gb.userCreated = \u6210\u529f\u521b\u5efa\u65b0\u7528\u6237 \\"{0}\\"\u3002
+gb.couldNotFindFederationRegistration = \u65e0\u6cd5\u627e\u5230federation registration!
+gb.failedToFindGravatarProfile = \u52a0\u8f7d {0} \u7684Gravatar\u4fe1\u606f\u5931\u8d25
+gb.branchStats = {0} \u4e2a\u63d0\u4ea4\u548c {1} \u4e2a\u6807\u7b7e\u5728 {2} \u5185
+gb.repositoryNotSpecified = \u672a\u6307\u5b9a\u7248\u672c\u5e93!
+gb.repositoryNotSpecifiedFor = \u6ca1\u6709\u4e3a {0} \u8bbe\u7f6e\u7248\u672c\u5e93!
+gb.canNotLoadRepository = \u65e0\u6cd5\u8f7d\u5165\u7248\u672c\u5e93
+gb.commitIsNull = \u63d0\u4ea4\u5185\u5bb9\u4e3a\u7a7a
+gb.unauthorizedAccessForRepository = \u672a\u6388\u6743\u8bbf\u95ee\u7248\u672c\u5e93
+gb.failedToFindCommit = \u5728 {1} \u4e2d {2} \u4e2a\u9875\u9762\u5185\u67e5\u627e\u63d0\u4ea4 \\"{0}\\"\u5931\u8d25!
+gb.couldNotFindFederationProposal = \u65e0\u6cd5\u627e\u5230federation proposal!
+gb.invalidUsernameOrPassword = \u7528\u6237\u540d\u6216\u8005\u5bc6\u7801\u9519\u8bef\uff01
+gb.OneProposalToReview = 1\u4e2afederation proposals\u7b49\u5f85\u68c0\u67e5\u3002
+gb.nFederationProposalsToReview = {0} \u4e2afederation proposals\u7b49\u5f85\u68c0\u67e5
+gb.couldNotFindTag = \u65e0\u6cd5\u627e\u5230\u6807\u7b7e {0}
+gb.couldNotCreateFederationProposal = \u65e0\u6cd5\u521b\u5efafederation proposal!
+gb.pleaseSetGitblitUrl = \u8bf7\u8f93\u5165\u4f60\u7684Gitblit\u7f51\u5740!
+gb.pleaseSetDestinationUrl = \u8bf7\u4e3a\u4f60\u7684proposal\u8f93\u5165\u4e00\u4e2a\u76ee\u6807\u5730\u5740!
+gb.proposalReceived = \u6210\u529f\u4ece {0} \u63a5\u6536Proposal.
+gb.noGitblitFound = \u62b1\u6b49, {0} \u65e0\u6cd5\u5728{1} \u4e2d\u627e\u5230Gitblit\u5b9e\u4f8b\u3002
+gb.noProposals = \u62b1\u6b49, {0} \u5f53\u524d\u4e0d\u63a5\u53d7proposals\u3002
+gb.noFederation = \u62b1\u6b49, {0} \u6ca1\u6709\u4e0e\u4efb\u4f55Gitblit\u5b9e\u4f8b\u8bbe\u7f6efederate\u3002.
+gb.proposalFailed = \u62b1\u6b49, {0} \u65e0\u6cd5\u63a5\u53d7\u4efb\u4f55proposal\u6570\u636e!
+gb.proposalError = \u62b1\u6b49\uff0c{0} \u62a5\u544a\u4e2d\u53d1\u73b0\u672a\u9884\u671f\u7684\u9519\u8bef\uff01
+gb.failedToSendProposal = \u53d1\u9001proposal\u5931\u8d25!
+gb.userServiceDoesNotPermitAddUser = {0} \u4e0d\u5141\u8bb8\u6dfb\u52a0\u7528\u6237!
+gb.userServiceDoesNotPermitPasswordChanges = {0} \u4e0d\u5141\u8bb8\u8fdb\u884c\u5bc6\u7801\u4fee\u6539!
+gb.displayName = \u663e\u793a\u540d\u79f0
+gb.emailAddress = \u90ae\u7bb1
+gb.errorAdminLoginRequired = \u9700\u8981\u7ba1\u7406\u5458\u767b\u9646
+gb.errorOnlyAdminMayCreateRepository = \u53ea\u6709\u7ba1\u7406\u5458\u624d\u53ef\u4ee5\u521b\u5efa\u7248\u672c\u5e93
+gb.errorOnlyAdminOrOwnerMayEditRepository = \u53ea\u6709\u7ba1\u7406\u5458\u6216\u8005\u6240\u6709\u8005\u624d\u53ef\u4ee5\u7f16\u8f91\u4ee3\u7801\u5e93
+gb.errorAdministrationDisabled = \u7ba1\u7406\u6743\u9650\u88ab\u7981\u6b62\u3002
+gb.lastNDays = \u6700\u8fd1 {0} \u5929
+gb.completeGravatarProfile = \u5728Gravatar.com\u4e0a\u5b8c\u6210\u4e2a\u4eba\u8bbe\u5b9a
+gb.none = \u65e0
+gb.line = \u884c
+gb.content = \u5185\u5bb9
+gb.empty = \u7a7a\u767d\u7248\u672c\u5e93
+gb.inherited = \u7ee7\u627f
+gb.deleteRepository = \u5220\u9664\u7248\u672c\u5e93 \\"{0}\\" \uff1f
+gb.repositoryDeleted = \u7248\u672c\u5e93 ''{0}'' \u5df2\u5220\u9664\u3002
+gb.repositoryDeleteFailed = \u5220\u9664\u7248\u672c\u5e93 \\"{0}\\" \u5931\u8d25\uff01
+gb.deleteUser = \u5220\u9664\u7528\u6237 \\"{0}\\" \uff1f
+gb.userDeleted = \u7528\u6237 ''{0}'' \u5df2\u5220\u9664\uff01
+gb.userDeleteFailed = \u5220\u9664\u7528\u6237''{0}''\u5931\u8d25\uff01
+gb.time.justNow = \u521a\u521a
+gb.time.today = \u4eca\u5929
+gb.time.yesterday = \u6628\u5929
+gb.time.minsAgo = {0} \u5206\u949f\u4ee5\u524d
+gb.time.hoursAgo = {0} \u5c0f\u65f6\u4ee5\u524d
+gb.time.daysAgo = {0} \u5929\u4ee5\u524d
+gb.time.weeksAgo = {0} \u5468\u4ee5\u524d
+gb.time.monthsAgo = {0} \u4e2a\u6708\u4ee5\u524d
+gb.time.oneYearAgo = 1 \u5e74\u4ee5\u524d
+gb.time.yearsAgo = {0} \u5e74\u4ee5\u524d
+gb.duration.oneDay = 1 \u5929
+gb.duration.days = {0} \u5929
+gb.duration.oneMonth = 1 \u6708
+gb.duration.months = {0} \u6708
+gb.duration.oneYear = 1 \u5e74
+gb.duration.years = {0} \u5e74
+gb.authorizationControl = \u6388\u6743\u63a7\u5236
+gb.allowAuthenticatedDescription = \u6388\u4e88\u6240\u6709\u8ba4\u8bc1\u7528\u6237\u53d7\u9650\u5236\u7684\u8bbf\u95ee\u6743\u9650
+gb.allowNamedDescription = \u6388\u4e88\u6307\u5b9a\u540d\u79f0\u7684\u7528\u6237\u6216\u56e2\u961f\u53d7\u9650\u5236\u7684\u8bbf\u95ee\u6743\u9650
+gb.markdownFailure = \u8bfb\u53d6 Markdown \u5185\u5bb9\u5931\u8d25\uff01
+gb.clearCache = \u6e05\u9664\u7f13\u5b58
+gb.projects = \u9879\u76ee
+gb.project = \u9879\u76ee
+gb.allProjects = \u6240\u6709\u9879\u76ee
+gb.copyToClipboard = \u590d\u5236\u5230\u526a\u8d34\u677f
+gb.fork = \u6d3e\u751f
+gb.forks = \u6d3e\u751f
+gb.forkRepository = \u6d3e\u751f {0} ?
+gb.repositoryForked = {0} \u5df2\u88ab\u6d3e\u751f
+gb.repositoryForkFailed = \u6d3e\u751f\u5931\u8d25
+gb.personalRepositories = \u79c1\u4eba\u7248\u672c\u5e93
+gb.allowForks = \u5141\u8bb8\u6d3e\u751f
+gb.allowForksDescription = \u5141\u8bb8\u8ba4\u8bc1\u7528\u6237\u6d3e\u751f\u6b64\u7248\u672c\u5e93
+gb.forkedFrom = \u6d3e\u751f\u81ea
+gb.canFork = \u5141\u8bb8\u6d3e\u751f
+gb.canForkDescription = \u5141\u8bb8\u6d3e\u751f\u8ba4\u8bc1\u7248\u672c\u5e93\u5230\u79c1\u4eba\u7248\u672c\u5e93
+gb.myFork = \u67e5\u770b\u6211\u7684\u6d3e\u751f
+gb.forksProhibited = \u7981\u6b62\u6d3e\u751f
+gb.forksProhibitedWarning = \u5f53\u524d\u7248\u672c\u5e93\u7981\u6b62\u6d3e\u751f
+gb.noForks = {0} \u6ca1\u6709\u6d3e\u751f
+gb.forkNotAuthorized = \u62b1\u6b49\uff0c\u4f60\u65e0\u6743\u6d3e\u751f {0}
+gb.forkInProgress = \u6b63\u5728\u6d3e\u751f
+gb.preparingFork = \u6b63\u5728\u4e3a\u60a8\u51c6\u5907\u6d3e\u751f...
+gb.isFork = \u5df2\u6d3e\u751f
+gb.canCreate = \u5141\u8bb8\u521b\u5efa
+gb.canCreateDescription = \u5141\u8bb8\u521b\u5efa\u79c1\u4eba\u7248\u672c\u5e93
+gb.illegalPersonalRepositoryLocation = \u60a8\u7684\u79c1\u4eba\u7248\u672c\u5e93\u5fc5\u987b\u4f4d\u4e8e \\"{0}\\"
+gb.verifyCommitter = \u9a8c\u8bc1\u63d0\u4ea4\u8005
+gb.verifyCommitterDescription = \u9700\u8981\u63d0\u4ea4\u8005\u7684\u8eab\u4efd\u4e0e Gitblit \u7528\u6237\u8eab\u4efd\u76f8\u7b26
+gb.verifyCommitterNote = \u6240\u6709\u5408\u5e76\u9009\u9879\u9700\u8981\u4f7f\u7528 \\"--no-ff\\" \u6765\u6267\u884c\u63d0\u4ea4\u8005\u9a8c\u8bc1
+gb.repositoryPermissions = \u7248\u672c\u5e93\u6743\u9650
+gb.userPermissions = \u7528\u6237\u6743\u9650
+gb.teamPermissions = \u56e2\u961f\u6743\u9650
+gb.add = \u6dfb\u52a0
+gb.noPermission = \u5220\u9664\u6b64\u6743\u9650
+gb.excludePermission = {0} (exclude)
+gb.viewPermission = {0} (view)
+gb.clonePermission = {0} (clone)
+gb.pushPermission = {0} (push)
+gb.createPermission = {0} (push, ref creation)
+gb.deletePermission = {0} (push, ref creation+deletion)
+gb.rewindPermission = {0} (push, ref creation+deletion+rewind)
+gb.permission = \u6743\u9650
+gb.regexPermission = \u6b64\u6743\u9650\u662f\u901a\u8fc7\u6b63\u5219\u8868\u8fbe\u5f0f \\"{0}\\" \u8bbe\u7f6e
+gb.accessDenied = \u8bbf\u95ee\u88ab\u62d2\u7edd
+gb.busyCollectingGarbage = \u62b1\u6b49\uff0cGitblit\u6b63\u5728 {0} \u5185\u6e05\u7406\u5783\u573e
+gb.gcPeriod = GC \u65f6\u95f4
+gb.gcPeriodDescription = \u5783\u573e\u6e05\u7406\u7684\u6301\u7eed\u65f6\u95f4
+gb.gcThreshold = GC \u9600\u503c
+gb.gcThresholdDescription = \u6fc0\u53d1\u5783\u573e\u6e05\u7406\u7684\u6700\u5c0f objects \u5927\u5c0f
+gb.ownerPermission = \u7248\u672c\u5e93\u521b\u5efa\u8005
+gb.administrator = \u7ba1\u7406\u5458
+gb.administratorPermission = Gitblit \u7ba1\u7406\u5458
+gb.team = \u56e2\u961f
+gb.teamPermission = \u901a\u8fc7 \\"{0}\\" \u56e2\u961f\u6210\u5458\u8bbe\u7f6e\u6743\u9650
+gb.missing = \u4e0d\u5b58\u5728!
+gb.missingPermission = \u6b64\u6743\u9650\u7684\u7248\u672c\u5e93\u4e0d\u5b58\u5728!
+gb.mutable = mutable
+gb.specified = specified
+gb.effective = effective
+gb.organizationalUnit = \u7ec4\u7ec7\u90e8\u5206
+gb.organization = \u7ec4\u7ec7
+gb.locality = \u5730\u533a
+gb.stateProvince = \u5dde\u6216\u7701
+gb.countryCode = \u56fd\u5bb6\u4ee3\u7801
+gb.properties = \u5c5e\u6027
+gb.issued = issued
+gb.expires = \u5230\u671f
+gb.expired = \u5df2\u5230\u671f
+gb.expiring = \u5373\u5c06\u8fc7\u671f
+gb.revoked = \u5df2\u64a4\u9500
+gb.serialNumber = \u5e8f\u5217\u53f7
+gb.certificates = \u8bc1\u4e66
+gb.newCertificate = \u521b\u5efa\u8bc1\u4e66
+gb.revokeCertificate = \u64a4\u9500\u8bc1\u4e66
+gb.sendEmail = \u53d1\u9001\u90ae\u4ef6
+gb.passwordHint = \u5bc6\u7801\u63d0\u793a
+gb.ok = \u786e\u5b9a
+gb.invalidExpirationDate = \u65e0\u6548\u7684\u8fc7\u671f\u65f6\u95f4!
+gb.passwordHintRequired = \u9700\u8981\u586b\u5199\u5bc6\u7801\u63d0\u793a!
+gb.viewCertificate = \u67e5\u770b\u8bc1\u4e66
+gb.subject = \u4e3b\u9898
+gb.issuer = \u63d0\u4ea4\u8005
+gb.validFrom = \u6709\u6548\u671f\u5f00\u59cb\u81ea
+gb.validUntil = \u6709\u6548\u671f\u622a\u6b62\u4e8e
+gb.publicKey = \u516c\u94a5
+gb.signatureAlgorithm = \u7b7e\u540d\u7b97\u6cd5
+gb.sha1FingerPrint = SHA-1 \u6307\u7eb9\u7b97\u6cd5
+gb.md5FingerPrint = MD5 \u6307\u7eb9\u7b97\u6cd5
+gb.reason = \u7406\u7531
+gb.revokeCertificateReason = \u8bf7\u9009\u62e9\u64a4\u9500\u8bc1\u4e66\u7684\u7406\u7531
+gb.unspecified = \u672a\u6307\u5b9a
+gb.keyCompromise = key compromise
+gb.caCompromise = CA compromise
+gb.affiliationChanged = \u96b6\u5c5e\u5173\u7cfb\u5df2\u4fee\u6539
+gb.superseded = \u5df2\u53d6\u4ee3
+gb.cessationOfOperation = \u505c\u6b62\u64cd\u4f5c
+gb.privilegeWithdrawn = \u7279\u6743\u5df2\u64a4\u56de
+gb.time.inMinutes = {0} \u5206\u949f\u4e4b\u5185
+gb.time.inHours = {0} \u5c0f\u65f6\u4e4b\u5185
+gb.time.inDays = {0} \u5929\u4e4b\u5185
+gb.hostname = hostname
+gb.hostnameRequired = \u8bf7\u8f93\u5165 hostname
+gb.newSSLCertificate = \u521b\u5efa\u670d\u52a1\u5668 SSL \u8bc1\u4e66
+gb.newCertificateDefaults = \u521b\u5efa\u8bc1\u4e66\u9ed8\u8ba4\u8bbe\u7f6e
+gb.duration = \u6301\u7eed\u65f6\u95f4
+gb.certificateRevoked = \u8bc1\u4e66 {0,number,0} \u5df2\u88ab\u64a4\u9500
+gb.clientCertificateGenerated = \u6210\u529f\u4e3a {0} \u751f\u6210\u65b0\u7684\u5ba2\u6237\u7aef\u8bc1\u4e66
+gb.sslCertificateGenerated = \u6210\u529f\u4e3a {0} \u751f\u6210\u65b0\u7684\u670d\u52a1\u5668 SSL \u8bc1\u4e66
+gb.newClientCertificateMessage = \u6ce8\u610f:\\n\u6b64\u5bc6\u7801\u5e76\u975e\u7528\u6237\u5bc6\u7801, \u8fd9\u662f\u4fdd\u5b58\u7528\u6237 keystore \u7684\u5bc6\u7801\u3002  \u7531\u4e8e\u672c\u5bc6\u7801\u672a\u5b58\u50a8\uff0c\u56e0\u6b64\u4f60\u5fc5\u987b\u4e00\u4e2a\u5bc6\u7801\u63d0\u793a\uff0c\u8fd9\u4e2a\u63d0\u793a\u4f1a\u8bb0\u5f55\u5728\u7528\u6237\u7684 README \u6587\u6863\u5185\u3002
+gb.certificate = \u8bc1\u4e66
+gb.emailCertificateBundle = \u53d1\u9001\u5ba2\u6237\u7aef\u8bc1\u4e66
+gb.pleaseGenerateClientCertificate = \u8bf7\u4e3a {0} \u751f\u6210\u4e00\u4e2a\u5ba2\u6237\u7aef\u8bc1\u4e66
+gb.clientCertificateBundleSent = {0} \u7684\u5ba2\u6237\u7aef\u8bc1\u4e66\u5df2\u53d1\u9001
+gb.enterKeystorePassword = \u8bf7\u8f93\u5165 Gitblit keystore \u5bc6\u7801
+gb.warning = \u8b66\u544a
+gb.jceWarning = \u60a8\u7684 JAVA \u8fd0\u884c\u73af\u5883\u4e0d\u5305\u542b \\"JCE Unlimited Strength Jurisdiction Policy\\" \u6587\u4ef6\u3002\\n\u8fd9\u5c06\u5bfc\u81f4\u60a8\u6700\u591a\u53ea\u80fd\u75287\u4e2a\u5b57\u7b26\u7684\u5bc6\u7801\u4fdd\u62a4\u60a8\u7684 keystore\u3002 \\n\u8fd9\u4e9b\u662f\u4e00\u4e9b\u53ef\u9009\u4e0b\u8f7d\u7684\u653f\u7b56\u6587\u4ef6\u3002\\n\\n\u4f60\u662f\u5426\u8981\u7ee7\u7eed\u751f\u6210\u8bc1\u4e66\uff1f\\n\\n\u9009\u62e9\u5426\u7684\u8bdd\uff0c\u5c06\u4f1a\u6253\u5f00\u4e00\u4e2a\u6d4f\u89c8\u5668\u754c\u9762\u4f9b\u60a8\u4e0b\u8f7d\u76f8\u5173\u6587\u4ef6\u3002
+gb.maxActivityCommits = \u6700\u5927\u6d3b\u52a8\u63d0\u4ea4\u6570
+gb.maxActivityCommitsDescription = \u6d3b\u52a8\u9875\u9762\u663e\u793a\u7684\u6700\u5927\u63d0\u4ea4\u6570
+gb.noMaximum = \u65e0\u4e0a\u9650
+gb.attributes = \u5c5e\u6027
+gb.serveCertificate = \u4f7f\u7528\u6b64\u8bc1\u4e66\u63d0\u4f9b https \u652f\u6301
+gb.sslCertificateGeneratedRestart = \u6210\u529f\u4e3a {0} \u751f\u6210\u65b0\u7684 SSL \u8bc1\u4e66.\\n\u4f60\u5fc5\u987b\u91cd\u65b0\u542f\u52a8 Gitblit \u4ee5\u4f7f\u7528\u6b64\u8bc1\u4e66\u3002\\n\\n\u5982\u679c\u60a8\u4f7f\u7528 '--alias' \u53c2\u6570\u542f\u52a8\uff0c\u4f60\u5fc5\u987b\u4e5f\u8981\u8bbe\u7f6e ''--alias {0}''\u3002
+gb.validity = \u5408\u6cd5\u6027
+gb.siteName = \u7f51\u7ad9\u540d\u79f0
+gb.siteNameDescription = \u60a8\u7684\u670d\u52a1\u5668\u7684\u7b80\u8981\u63cf\u8ff0
+gb.excludeFromActivity = \u4ece\u6d3b\u52a8\u9875\u9762\u6392\u9664
+gb.isSparkleshared = \u7248\u672c\u5e93\u5df2\u901a\u8fc7Sparkleshare\u5b8c\u6210\u540c\u6b65
+gb.owners = \u62e5\u6709\u8005
+gb.sessionEnded = \u4f1a\u8bdd\u5df2\u5173\u95ed
+gb.closeBrowser = \u8bf7\u5173\u95ed\u6d4f\u89c8\u5668\u4ee5\u4fbf\u6b63\u5e38\u5173\u95ed\u4f1a\u8bdd\u3002
+gb.doesNotExistInTree = {1} \u76ee\u5f55\u4e2d\u4e0d\u5b58\u5728 {0}
+gb.enableIncrementalPushTags = \u5141\u8bb8\u9012\u589e\u5f0f\u63a8\u9001\u6807\u7b7e
+gb.useIncrementalPushTagsDescription = \u6bcf\u6b21\u63a8\u9001\u65f6\uff0c\u81ea\u52a8\u4e3a\u6bcf\u4e2a\u5206\u652f\u6dfb\u52a0\u9012\u589e\u7684revision\u7f16\u53f7
+gb.incrementalPushTagMessage = \u63a8\u9001\u65f6\u81ea\u52a8\u4e3a\u5206\u652f [{0}] \u6dfb\u52a0\u6807\u7b7e
+gb.externalPermissions = {0} \u7684\u8bbf\u95ee\u6743\u9650\u5c5e\u4e8e\u5916\u90e8\u63a7\u5236
+gb.viewAccess = \u60a8\u6ca1\u6709 Gitblit \u8bfb\u6216\u5199\u7684\u6743\u9650
+gb.overview = \u603b\u89c8
+gb.dashboard = \u516c\u544a\u677f
+gb.monthlyActivity = \u6708\u5ea6\u6d3b\u52a8
+gb.myProfile = \u7528\u6237\u4e2d\u5fc3
+gb.compare = \u5bf9\u6bd4
+gb.manual = \u624b\u518c
+gb.from = from
+gb.to = to
+gb.at = at
+gb.of = of
+gb.in = in
+gb.moreChanges = \u6240\u6709\u53d8\u52a8...
+gb.pushedNCommitsTo = \u5df2\u63a8\u9001 {0} \u6b21\u81f3
+gb.pushedOneCommitTo = \u5df2\u63a8\u9001 1 \u6b21\u81f3
+gb.commitsTo = {0} \u6b21\u63a8\u9001\u81f3
+gb.oneCommitTo = 1 \u6b21\u63a8\u9001\u81f3
+gb.byNAuthors = \u6765\u81ea {0}
+gb.byOneAuthor = \u6765\u81ea {0}
+gb.viewComparison = \u5bf9\u6bd4\u4ee5\u4e0b {0} \u6b21\u63d0\u4ea4\u5185\u5bb9 \xbb
+gb.nMoreCommits = \u5176\u4ed6 {0} \u6b21\u63d0\u4ea4\xbb
+gb.oneMoreCommit = \u5176\u4ed6 1 \u6b21\u63d0\u4ea4 \xbb
+gb.pushedNewTag = \u63a8\u9001\u65b0\u6807\u7b7e
+gb.createdNewTag = \u521b\u5efa\u65b0\u6807\u7b7e
+gb.deletedTag = \u5220\u9664\u6807\u7b7e
+gb.pushedNewBranch = \u63a8\u9001\u65b0\u5206\u652f
+gb.createdNewBranch = \u521b\u5efa\u65b0\u5206\u652f
+gb.deletedBranch = \u5df2\u5220\u9664\u5206\u652f
+gb.createdNewPullRequest = \u521b\u5efa pull request
+gb.mergedPullRequest = \u5408\u5e76 pull request
+gb.rewind = REWIND
+gb.star = \u5173\u6ce8
+gb.unstar = \u53d6\u6d88\u5173\u6ce8
+gb.stargazers = stargazers
+gb.starredRepositories = \u5df2\u5173\u6ce8\u7248\u672c\u5e93
+gb.failedToUpdateUser = \u66f4\u65b0\u7528\u6237\u8d26\u6237\u4fe1\u606f\u5931\u8d25!
+gb.myRepositories = \u6211\u7684\u7248\u672c\u5e93
+gb.noActivity = \u6700\u8fd1 {0} \u5929\u5185\u6ca1\u6709\u4efb\u4f55\u6d3b\u52a8
+gb.findSomeRepositories = \u5bfb\u627e\u7248\u672c\u5e93
+gb.metricAuthorExclusions = author metric exclusions
+gb.myDashboard = \u6211\u7684\u516c\u544a\u677f
+gb.failedToFindAccount = \u5bfb\u627e\u8d26\u6237 ''{0}'' \u5931\u8d25
+gb.reflog = reflog
+gb.active = \u6d3b\u52a8
+gb.starred = \u5df2\u5173\u6ce8
+gb.owned = \u5c5e\u4e8e\u60a8
+gb.starredAndOwned = \u5df2\u5173\u6ce8 & \u5c5e\u4e8e\u60a8
+gb.reviewPatchset = review {0} patchset {1}
+gb.todaysActivityStats = \u4eca\u5929 / \u6765\u81ea {2} \u7684 {1} \u6b21\u63d0\u4ea4
+gb.todaysActivityNone = \u4eca\u5929 / \u65e0
+gb.noActivityToday = \u4eca\u5929\u6ca1\u6709\u4efb\u4f55\u6d3b\u52a8
+gb.anonymousUser = \u533f\u540d
diff --git a/src/main/java/com/gitblit/wicket/GitBlitWebSession.java b/src/main/java/com/gitblit/wicket/GitBlitWebSession.java
new file mode 100644
index 0000000..f25bcf9
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/GitBlitWebSession.java
@@ -0,0 +1,165 @@
+/*
+ * 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.wicket;
+
+import java.util.Locale;
+import java.util.Map;
+import java.util.TimeZone;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.apache.wicket.Page;
+import org.apache.wicket.PageParameters;
+import org.apache.wicket.RedirectToUrlException;
+import org.apache.wicket.Request;
+import org.apache.wicket.Session;
+import org.apache.wicket.protocol.http.RequestUtils;
+import org.apache.wicket.protocol.http.WebRequestCycle;
+import org.apache.wicket.protocol.http.WebSession;
+import org.apache.wicket.protocol.http.request.WebClientInfo;
+
+import com.gitblit.Constants.AuthenticationType;
+import com.gitblit.models.UserModel;
+
+public final class GitBlitWebSession extends WebSession {
+
+	private static final long serialVersionUID = 1L;
+
+	protected TimeZone timezone;
+
+	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;
+	}
+
+	public void invalidate() {
+		super.invalidate();
+		user = null;
+	}
+	
+	/**
+	 * Cache the requested protected resource pending successful authentication.
+	 * 
+	 * @param pageClass
+	 */
+	public void cacheRequest(Class<? extends Page> pageClass) {
+		// build absolute url with correctly encoded parameters?!
+		Request req = WebRequestCycle.get().getRequest();
+		Map<String, ?> params = req.getRequestParameters().getParameters();
+		PageParameters pageParams = new PageParameters(params);
+		String relativeUrl = WebRequestCycle.get().urlFor(pageClass, pageParams).toString();
+		requestUrl = RequestUtils.toAbsolutePath(relativeUrl);
+		if (isTemporary())
+		{
+			// we must bind the temporary session into the session store
+			// so that we can re-use this session for reporting an error message
+			// on the redirected page and continuing the request after
+			// authentication.
+			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() {
+		if (requestUrl != null) {
+			String url = requestUrl;
+			requestUrl = null;
+			throw new RedirectToUrlException(url);
+		}
+		return false;
+	}
+
+	public boolean isLoggedIn() {
+		return user != null;
+	}
+
+	public boolean canAdmin() {
+		if (user == null) {
+			return false;
+		}
+		return user.canAdmin();
+	}
+	
+	public String getUsername() {
+		return user == null ? "anonymous" : user.username;
+	}
+
+	public UserModel getUser() {
+		return user;
+	}
+
+	public void setUser(UserModel user) {
+		this.user = user;
+		if (user != null) {
+			Locale preferredLocale = user.getPreferences().getLocale();
+			if (preferredLocale != null) {
+				// set the user's preferred locale
+				setLocale(preferredLocale);
+			}
+		}
+	}
+
+	public TimeZone getTimezone() {
+		if (timezone == null) {
+			timezone = ((WebClientInfo) getClientInfo()).getProperties().getTimeZone();
+		}
+		// use server timezone if we can't determine the client timezone
+		if (timezone == null) {
+			timezone = TimeZone.getDefault();
+		}
+		return timezone;
+	}
+
+	public void cacheErrorMessage(String message) {
+		this.errorMessage = message;
+	}
+
+	public String clearErrorMessage() {
+		String msg = errorMessage;
+		errorMessage = null;
+		return msg;
+	}
+	
+	public boolean isForking() {
+		return isForking.get();
+	}
+	
+	public void isForking(boolean val) {
+		isForking.set(val);
+	}
+
+	public static GitBlitWebSession get() {
+		return (GitBlitWebSession) Session.get();
+	}
+}
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/GitblitParamUrlCodingStrategy.java b/src/main/java/com/gitblit/wicket/GitblitParamUrlCodingStrategy.java
similarity index 100%
rename from src/com/gitblit/wicket/GitblitParamUrlCodingStrategy.java
rename to src/main/java/com/gitblit/wicket/GitblitParamUrlCodingStrategy.java
diff --git a/src/com/gitblit/wicket/GitblitRedirectException.java b/src/main/java/com/gitblit/wicket/GitblitRedirectException.java
similarity index 100%
rename from src/com/gitblit/wicket/GitblitRedirectException.java
rename to src/main/java/com/gitblit/wicket/GitblitRedirectException.java
diff --git a/src/main/java/com/gitblit/wicket/GitblitWicketFilter.java b/src/main/java/com/gitblit/wicket/GitblitWicketFilter.java
new file mode 100644
index 0000000..f46c51e
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/GitblitWicketFilter.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright 2013 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit.wicket;
+
+import java.util.Date;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.wicket.protocol.http.WicketFilter;
+import org.apache.wicket.util.string.Strings;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+
+import com.gitblit.GitBlit;
+import com.gitblit.Keys;
+import com.gitblit.models.ProjectModel;
+import com.gitblit.models.RepositoryModel;
+import com.gitblit.utils.JGitUtils;
+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
+	 */
+	protected long getLastModified(final HttpServletRequest servletRequest)	{
+		final String pathInfo = getRelativePath(servletRequest);
+		if (Strings.isEmpty(pathInfo))
+			return -1;
+		long lastModified = super.getLastModified(servletRequest);
+		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 = "";
+		if (paths.length >= 2) {
+			repo = paths[1];
+		}
+		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();
+		int expires = GitBlit.getInteger(Keys.web.pageCacheExpires, 0);
+		if (!StringUtils.isEmpty(page) && app.isCacheablePage(page) && expires > 0) {
+			// page can be cached by the browser
+			CacheControl cacheControl = app.getCacheControl(page);
+			Date bootDate = GitBlit.getBootDate();
+			switch (cacheControl.value()) {
+			case ACTIVITY:
+				// returns the last activity date of the server
+				Date activityDate = GitBlit.getLastActivityDate();
+				if (activityDate != null) {
+					return activityDate.after(bootDate) ? activityDate.getTime() : bootDate.getTime();
+				}
+				return bootDate.getTime();
+			case BOOT:
+				// return the boot date of the server
+				return bootDate.getTime();
+			case PROJECT:
+				// return the latest change date for the project OR the boot date
+				ProjectModel project = GitBlit.self().getProjectModel(StringUtils.getRootPath(repo));
+				if (project != null) {
+					return project.lastChange.after(bootDate) ? project.lastChange.getTime() : bootDate.getTime();
+				}
+				break;
+			case REPOSITORY:
+				// return the lastest change date for the repository OR the boot
+				// date, whichever is latest
+				RepositoryModel repository = GitBlit.self().getRepositoryModel(repo);
+				if (repository != null && repository.lastChange != null) {
+					return repository.lastChange.after(bootDate) ? repository.lastChange.getTime() : bootDate.getTime();
+				}
+				break;
+			case COMMIT:
+				// get the date of the specified commit
+				if (StringUtils.isEmpty(commitId)) {
+					// no commit id, use boot date
+					return bootDate.getTime();
+				} else {
+					// last modified date is the commit date 
+					Repository r = null;
+					try {
+						// return the timestamp of the associated commit
+						r = GitBlit.self().getRepository(repo);
+						if (r != null) {
+							RevCommit commit = JGitUtils.getCommit(r, commitId);
+							if (commit != null) {
+								Date date = JGitUtils.getCommitDate(commit);
+								return date.after(bootDate) ? date.getTime() : bootDate.getTime();
+							}
+						}
+					} finally {
+						if (r != null) {
+							r.close();
+						}
+					}
+				}
+				break;
+			default:
+				break;
+			}
+		}			
+
+		return -1;
+	}
+}
diff --git a/src/main/java/com/gitblit/wicket/PageRegistration.java b/src/main/java/com/gitblit/wicket/PageRegistration.java
new file mode 100644
index 0000000..b0cb470
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/PageRegistration.java
@@ -0,0 +1,243 @@
+/*
+ * 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.wicket;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.wicket.PageParameters;
+import org.apache.wicket.markup.html.WebPage;
+
+import com.gitblit.utils.StringUtils;
+
+/**
+ * Represents a page link registration for the topbar.
+ * 
+ * @author James Moger
+ * 
+ */
+public class PageRegistration implements Serializable {
+	private static final long serialVersionUID = 1L;
+
+	public final String translationKey;
+	public final Class<? extends WebPage> pageClass;
+	public final PageParameters params;
+	public final boolean hiddenPhone;
+
+	public PageRegistration(String translationKey, Class<? extends WebPage> pageClass) {
+		this(translationKey, pageClass, null);
+	}
+
+	public PageRegistration(String translationKey, Class<? extends WebPage> pageClass,
+			PageParameters params) {
+		this(translationKey, pageClass, params, false);
+	}
+	
+	public PageRegistration(String translationKey, Class<? extends WebPage> pageClass,
+			PageParameters params, boolean hiddenPhone) {
+		this.translationKey = translationKey;
+		this.pageClass = pageClass;
+		this.params = params;
+		this.hiddenPhone = hiddenPhone;
+	}
+
+	/**
+	 * Represents a page link to a non-Wicket page. Might be external.
+	 * 
+	 * @author James Moger
+	 * 
+	 */
+	public static class OtherPageLink extends PageRegistration {
+
+		private static final long serialVersionUID = 1L;
+
+		public final String url;
+
+		public OtherPageLink(String translationKey, String url) {
+			super(translationKey, null);
+			this.url = url;
+		}
+		
+		public OtherPageLink(String translationKey, String url, boolean hiddenPhone) {
+			super(translationKey, null, null, hiddenPhone);
+			this.url = url;
+		}
+	}
+
+	/**
+	 * Represents a DropDownMenu for the topbar
+	 * 
+	 * @author James Moger
+	 * 
+	 */
+	public static class DropDownMenuRegistration extends PageRegistration {
+
+		private static final long serialVersionUID = 1L;
+
+		public final List<DropDownMenuItem> menuItems;
+
+		public DropDownMenuRegistration(String translationKey, Class<? extends WebPage> pageClass) {
+			super(translationKey, pageClass);
+			menuItems = new ArrayList<DropDownMenuItem>();
+		}
+	}
+
+	/**
+	 * A MenuItem for the DropDownMenu.
+	 * 
+	 * @author James Moger
+	 * 
+	 */
+	public static class DropDownMenuItem implements Serializable {
+
+		private static final long serialVersionUID = 1L;
+
+		final PageParameters parameters;
+		final String displayText;
+		final String parameter;
+		final String value;
+		final boolean isSelected;
+
+		/**
+		 * Divider constructor.
+		 */
+		public DropDownMenuItem() {
+			this(null, null, null, null);
+		}
+
+		/**
+		 * Standard Menu Item constructor.
+		 * 
+		 * @param displayText
+		 * @param parameter
+		 * @param value
+		 */
+		public DropDownMenuItem(String displayText, String parameter, String value) {
+			this(displayText, parameter, value, null);
+		}
+
+		/**
+		 * Standard Menu Item constructor that preserves aggregate parameters.
+		 * 
+		 * @param displayText
+		 * @param parameter
+		 * @param value
+		 */
+		public DropDownMenuItem(String displayText, String parameter, String value,
+				PageParameters params) {
+			this.displayText = displayText;
+			this.parameter = parameter;
+			this.value = value;
+
+			if (params == null) {
+				// no parameters specified
+				parameters = new PageParameters();
+				setParameter(parameter, value);
+				isSelected = false;
+			} else {
+				parameters = new PageParameters(params);
+				if (parameters.containsKey(parameter)) {
+					isSelected = params.getString(parameter).equals(value);
+					// set the new selection value
+					setParameter(parameter, value);
+				} else {
+					// not currently selected
+					isSelected = false;
+					setParameter(parameter, value);
+				}
+			}
+		}
+
+		protected void setParameter(String parameter, String value) {
+			if (!StringUtils.isEmpty(parameter)) {
+				if (StringUtils.isEmpty(value)) {
+					this.parameters.remove(parameter);
+				} else {
+					this.parameters.put(parameter, value);
+				}
+			}
+		}
+
+		public String formatParameter() {
+			if (StringUtils.isEmpty(parameter) || StringUtils.isEmpty(value)) {
+				return "";
+			}
+			return parameter + "=" + value;
+		}
+
+		public PageParameters getPageParameters() {
+			return parameters;
+		}
+
+		public boolean isDivider() {
+			return displayText == null && value == null && parameter == null;
+		}
+
+		public boolean isSelected() {
+			return isSelected;
+		}
+
+		@Override
+		public int hashCode() {
+			if (isDivider()) {
+				// divider menu item
+				return super.hashCode();
+			}
+			if (StringUtils.isEmpty(displayText)) {
+				return value.hashCode() + parameter.hashCode();
+			}
+			return displayText.hashCode();
+		}
+
+		@Override
+		public boolean equals(Object o) {
+			if (o instanceof DropDownMenuItem) {
+				return hashCode() == o.hashCode();
+			}
+			return false;
+		}
+
+		@Override
+		public String toString() {
+			if (StringUtils.isEmpty(displayText)) {
+				return formatParameter();
+			}
+			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
+		 */
+		public DropDownToggleItem(String displayText, String parameter, String value,
+				PageParameters params) {
+			super(displayText, parameter, value, params);
+			if (isSelected) {
+				// already selected, so remove this enables toggling
+				parameters.remove(parameter);
+			}
+		}
+	}
+}
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/RequiresAdminRole.java b/src/main/java/com/gitblit/wicket/RequiresAdminRole.java
similarity index 100%
rename from src/com/gitblit/wicket/RequiresAdminRole.java
rename to src/main/java/com/gitblit/wicket/RequiresAdminRole.java
diff --git a/src/com/gitblit/wicket/SessionlessForm.java b/src/main/java/com/gitblit/wicket/SessionlessForm.java
similarity index 100%
rename from src/com/gitblit/wicket/SessionlessForm.java
rename to src/main/java/com/gitblit/wicket/SessionlessForm.java
diff --git a/src/com/gitblit/wicket/StringChoiceRenderer.java b/src/main/java/com/gitblit/wicket/StringChoiceRenderer.java
similarity index 100%
rename from src/com/gitblit/wicket/StringChoiceRenderer.java
rename to src/main/java/com/gitblit/wicket/StringChoiceRenderer.java
diff --git a/src/main/java/com/gitblit/wicket/WicketUtils.java b/src/main/java/com/gitblit/wicket/WicketUtils.java
new file mode 100644
index 0000000..87f2f3f
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/WicketUtils.java
@@ -0,0 +1,629 @@
+/*
+ * 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.wicket;
+
+import java.text.DateFormat;
+import java.text.MessageFormat;
+import java.text.SimpleDateFormat;
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
+import java.util.TimeZone;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.wicket.Component;
+import org.apache.wicket.PageParameters;
+import org.apache.wicket.Request;
+import org.apache.wicket.behavior.HeaderContributor;
+import org.apache.wicket.behavior.SimpleAttributeModifier;
+import org.apache.wicket.markup.html.IHeaderContributor;
+import org.apache.wicket.markup.html.IHeaderResponse;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.image.ContextImage;
+import org.apache.wicket.protocol.http.WebRequest;
+import org.apache.wicket.resource.ContextRelativeResource;
+import org.eclipse.jgit.diff.DiffEntry.ChangeType;
+import org.wicketstuff.googlecharts.AbstractChartData;
+import org.wicketstuff.googlecharts.IChartData;
+
+import com.gitblit.Constants;
+import com.gitblit.Constants.AccessPermission;
+import com.gitblit.Constants.FederationPullStatus;
+import com.gitblit.GitBlit;
+import com.gitblit.Keys;
+import com.gitblit.models.FederationModel;
+import com.gitblit.models.Metric;
+import com.gitblit.utils.HttpUtils;
+import com.gitblit.utils.StringUtils;
+import com.gitblit.utils.TimeUtils;
+
+public class WicketUtils {
+
+	public static void setCssClass(Component container, String value) {
+		container.add(new SimpleAttributeModifier("class", value));
+	}
+
+	public static void setCssStyle(Component container, String value) {
+		container.add(new SimpleAttributeModifier("style", value));
+	}
+
+	public static void setCssBackground(Component container, String value) {
+		String background = MessageFormat.format("background-color:{0};",
+				StringUtils.getColor(value));
+		container.add(new SimpleAttributeModifier("style", background));
+	}
+
+	public static void setHtmlTooltip(Component container, String value) {
+		container.add(new SimpleAttributeModifier("title", value));
+	}
+
+	public static void setInputPlaceholder(Component container, String value) {
+		container.add(new SimpleAttributeModifier("placeholder", value));
+	}
+
+	public static void setChangeTypeCssClass(Component container, ChangeType type) {
+		switch (type) {
+		case ADD:
+			setCssClass(container, "addition");
+			break;
+		case COPY:
+		case RENAME:
+			setCssClass(container, "rename");
+			break;
+		case DELETE:
+			setCssClass(container, "deletion");
+			break;
+		case MODIFY:
+			setCssClass(container, "modification");
+			break;
+		}
+	}
+
+	public static void setTicketCssClass(Component container, String state) {
+		String css = null;
+		if (state.equals("open")) {
+			css = "label label-important";
+		} else if (state.equals("hold")) {
+			css = "label label-warning";
+		} else if (state.equals("resolved")) {
+			css = "label label-success";
+		} else if (state.equals("invalid")) {
+			css = "label";
+		}
+		if (css != null) {
+			setCssClass(container, css);
+		}
+	}
+	
+	public static void setPermissionClass(Component container, AccessPermission permission) {
+		if (permission == null) {
+			setCssClass(container, "badge");
+			return;
+		}
+		switch (permission) {
+		case REWIND:
+		case DELETE:
+		case CREATE:
+			setCssClass(container, "badge badge-success");
+			break;
+		case PUSH:
+			setCssClass(container, "badge badge-info");
+			break;
+		case CLONE:
+			setCssClass(container, "badge badge-inverse");
+			break;
+		default:
+			setCssClass(container, "badge");
+			break;
+		}	
+	}
+
+	public static void setAlternatingBackground(Component c, int i) {
+		String clazz = i % 2 == 0 ? "light" : "dark";
+		setCssClass(c, clazz);
+	}
+
+	public static Label createAuthorLabel(String wicketId, String author) {
+		Label label = new Label(wicketId, author);
+		WicketUtils.setHtmlTooltip(label, author);
+		return label;
+	}
+
+	public static ContextImage getPullStatusImage(String wicketId, FederationPullStatus status) {
+		String filename = null;
+		switch (status) {
+		case MIRRORED:
+		case PULLED:
+			filename = "bullet_green.png";
+			break;
+		case SKIPPED:
+			filename = "bullet_yellow.png";
+			break;
+		case FAILED:
+			filename = "bullet_red.png";
+			break;
+		case EXCLUDED:
+			filename = "bullet_white.png";
+			break;
+		case PENDING:
+		case NOCHANGE:
+		default:
+			filename = "bullet_black.png";
+		}
+		return WicketUtils.newImage(wicketId, filename, status.name());
+	}
+
+	public static ContextImage getFileImage(String wicketId, String filename) {
+		filename = filename.toLowerCase();
+		if (filename.endsWith(".java")) {
+			return newImage(wicketId, "file_java_16x16.png");
+		} else if (filename.endsWith(".rb")) {
+			return newImage(wicketId, "file_ruby_16x16.png");
+		} else if (filename.endsWith(".php")) {
+			return newImage(wicketId, "file_php_16x16.png");
+		} else if (filename.endsWith(".cs")) {
+			return newImage(wicketId, "file_cs_16x16.png");
+		} else if (filename.endsWith(".cpp")) {
+			return newImage(wicketId, "file_cpp_16x16.png");
+		} else if (filename.endsWith(".c")) {
+			return newImage(wicketId, "file_c_16x16.png");
+		} else if (filename.endsWith(".h")) {
+			return newImage(wicketId, "file_h_16x16.png");
+		} else if (filename.endsWith(".sln")) {
+			return newImage(wicketId, "file_vs_16x16.png");
+		} else if (filename.endsWith(".csv") || filename.endsWith(".xls")
+				|| filename.endsWith(".xlsx")) {
+			return newImage(wicketId, "file_excel_16x16.png");
+		} else if (filename.endsWith(".doc") || filename.endsWith(".docx")) {
+			return newImage(wicketId, "file_doc_16x16.png");
+		} else if (filename.endsWith(".ppt")) {
+			return newImage(wicketId, "file_ppt_16x16.png");
+		} else if (filename.endsWith(".zip")) {
+			return newImage(wicketId, "file_zip_16x16.png");
+		} else if (filename.endsWith(".pdf")) {
+			return newImage(wicketId, "file_acrobat_16x16.png");
+		} else if (filename.endsWith(".htm") || filename.endsWith(".html")) {
+			return newImage(wicketId, "file_world_16x16.png");
+		} else if (filename.endsWith(".xml")) {
+			return newImage(wicketId, "file_code_16x16.png");
+		} else if (filename.endsWith(".properties")) {
+			return newImage(wicketId, "file_settings_16x16.png");
+		}
+
+		List<String> mdExtensions = GitBlit.getStrings(Keys.web.markdownExtensions);
+		for (String ext : mdExtensions) {
+			if (filename.endsWith('.' + ext.toLowerCase())) {
+				return newImage(wicketId, "file_world_16x16.png");
+			}
+		}
+		return newImage(wicketId, "file_16x16.png");
+	}
+
+	public static ContextImage getRegistrationImage(String wicketId, FederationModel registration,
+			Component c) {
+		if (registration.isResultData()) {
+			return WicketUtils.newImage(wicketId, "information_16x16.png",
+					c.getString("gb.federationResults"));
+		} else {
+			return WicketUtils.newImage(wicketId, "arrow_left.png",
+					c.getString("gb.federationRegistration"));
+		}
+	}
+
+	public static ContextImage newClearPixel(String wicketId) {
+		return newImage(wicketId, "pixel.png");
+	}
+
+	public static ContextImage newBlankImage(String wicketId) {
+		return newImage(wicketId, "blank.png");
+	}
+
+	public static ContextImage newImage(String wicketId, String file) {
+		return newImage(wicketId, file, null);
+	}
+
+	public static ContextImage newImage(String wicketId, String file, String tooltip) {
+		ContextImage img = new ContextImage(wicketId, file);
+		if (!StringUtils.isEmpty(tooltip)) {
+			setHtmlTooltip(img, tooltip);
+		}
+		return img;
+	}
+
+	public static Label newIcon(String wicketId, String css) {
+		Label lbl = new Label(wicketId);
+		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);
+	}
+
+	public static String getGitblitURL(Request request) {
+		HttpServletRequest req = ((WebRequest) request).getHttpServletRequest();
+		return HttpUtils.getGitblitURL(req);
+	}
+
+	public static HeaderContributor syndicationDiscoveryLink(final String feedTitle,
+			final String url) {
+		return new HeaderContributor(new IHeaderContributor() {
+			private static final long serialVersionUID = 1L;
+
+			public void renderHead(IHeaderResponse response) {
+				String contentType = "application/rss+xml";
+
+				StringBuilder buffer = new StringBuilder();
+				buffer.append("<link rel=\"alternate\" ");
+				buffer.append("type=\"").append(contentType).append("\" ");
+				buffer.append("title=\"").append(feedTitle).append("\" ");
+				buffer.append("href=\"").append(url).append("\" />");
+				response.renderString(buffer.toString());
+			}
+		});
+	}
+
+	public static PageParameters newTokenParameter(String token) {
+		return new PageParameters("t=" + token);
+	}
+
+	public static PageParameters newRegistrationParameter(String url, String name) {
+		return new PageParameters("u=" + url + ",n=" + name);
+	}
+
+	public static PageParameters newUsernameParameter(String username) {
+		return new PageParameters("user=" + username);
+	}
+
+	public static PageParameters newTeamnameParameter(String teamname) {
+		return new PageParameters("team=" + teamname);
+	}
+
+	public static PageParameters newProjectParameter(String projectName) {
+		return new PageParameters("p=" + projectName);
+	}
+
+	public static PageParameters newRepositoryParameter(String repositoryName) {
+		return new PageParameters("r=" + repositoryName);
+	}
+
+	public static PageParameters newObjectParameter(String objectId) {
+		return new PageParameters("h=" + objectId);
+	}
+
+	public static PageParameters newObjectParameter(String repositoryName, String objectId) {
+		if (StringUtils.isEmpty(objectId)) {
+			return newRepositoryParameter(repositoryName);
+		}
+		return new PageParameters("r=" + repositoryName + ",h=" + objectId);
+	}
+
+	public static PageParameters newRangeParameter(String repositoryName, String startRange, String endRange) {
+		return new PageParameters("r=" + repositoryName + ",h=" + startRange + ".." + endRange);
+	}
+
+	public static PageParameters newPathParameter(String repositoryName, String objectId,
+			String path) {
+		if (StringUtils.isEmpty(path)) {
+			return newObjectParameter(repositoryName, objectId);
+		}
+		if (StringUtils.isEmpty(objectId)) {
+			return new PageParameters("r=" + repositoryName + ",f=" + path);
+		}
+		return new PageParameters("r=" + repositoryName + ",h=" + objectId + ",f=" + path);
+	}
+
+	public static PageParameters newLogPageParameter(String repositoryName, String objectId,
+			int pageNumber) {
+		if (pageNumber <= 1) {
+			return newObjectParameter(repositoryName, objectId);
+		}
+		if (StringUtils.isEmpty(objectId)) {
+			return new PageParameters("r=" + repositoryName + ",pg=" + pageNumber);
+		}
+		return new PageParameters("r=" + repositoryName + ",h=" + objectId + ",pg=" + pageNumber);
+	}
+
+	public static PageParameters newHistoryPageParameter(String repositoryName, String objectId,
+			String path, int pageNumber) {
+		if (pageNumber <= 1) {
+			return newObjectParameter(repositoryName, objectId);
+		}
+		if (StringUtils.isEmpty(objectId)) {
+			return new PageParameters("r=" + repositoryName + ",f=" + path + ",pg=" + pageNumber);
+		}
+		return new PageParameters("r=" + repositoryName + ",h=" + objectId + ",f=" + path + ",pg="
+				+ pageNumber);
+	}
+
+	public static PageParameters newBlobDiffParameter(String repositoryName, String baseCommitId,
+			String commitId, String path) {
+		if (StringUtils.isEmpty(commitId)) {
+			return new PageParameters("r=" + repositoryName + ",f=" + path + ",hb=" + baseCommitId);
+		}
+		return new PageParameters("r=" + repositoryName + ",h=" + commitId + ",f=" + path + ",hb="
+				+ baseCommitId);
+	}
+
+	public static PageParameters newSearchParameter(String repositoryName, String commitId,
+			String search, Constants.SearchType type) {
+		if (StringUtils.isEmpty(commitId)) {
+			return new PageParameters("r=" + repositoryName + ",s=" + search + ",st=" + type.name());
+		}
+		return new PageParameters("r=" + repositoryName + ",h=" + commitId + ",s=" + search
+				+ ",st=" + type.name());
+	}
+
+	public static PageParameters newSearchParameter(String repositoryName, String commitId,
+			String search, Constants.SearchType type, int pageNumber) {
+		if (StringUtils.isEmpty(commitId)) {
+			return new PageParameters("r=" + repositoryName + ",s=" + search + ",st=" + type.name()
+					+ ",pg=" + pageNumber);
+		}
+		return new PageParameters("r=" + repositoryName + ",h=" + commitId + ",s=" + search
+				+ ",st=" + type.name() + ",pg=" + pageNumber);
+	}
+
+	public static String getProjectName(PageParameters params) {
+		return params.getString("p", "");
+	}
+
+	public static String getRepositoryName(PageParameters params) {
+		return params.getString("r", "");
+	}
+
+	public static String getObject(PageParameters params) {
+		return params.getString("h", null);
+	}
+
+	public static String getPath(PageParameters params) {
+		return params.getString("f", null);
+	}
+
+	public static String getBaseObjectId(PageParameters params) {
+		return params.getString("hb", null);
+	}
+
+	public static String getSearchString(PageParameters params) {
+		return params.getString("s", null);
+	}
+
+	public static String getSearchType(PageParameters params) {
+		return params.getString("st", null);
+	}
+
+	public static int getPage(PageParameters params) {
+		// index from 1
+		return params.getInt("pg", 1);
+	}
+
+	public static String getRegEx(PageParameters params) {
+		return params.getString("x", "");
+	}
+
+	public static String getSet(PageParameters params) {
+		return params.getString("set", "");
+	}
+
+	public static String getTeam(PageParameters params) {
+		return params.getString("team", "");
+	}
+
+	public static int getDaysBack(PageParameters params) {
+		return params.getInt("db", 0);
+	}
+
+	public static String getUsername(PageParameters params) {
+		return params.getString("user", "");
+	}
+
+	public static String getTeamname(PageParameters params) {
+		return params.getString("team", "");
+	}
+
+	public static String getToken(PageParameters params) {
+		return params.getString("t", "");
+	}
+
+	public static String getUrlParameter(PageParameters params) {
+		return params.getString("u", "");
+	}
+
+	public static String getNameParameter(PageParameters params) {
+		return params.getString("n", "");
+	}
+
+	public static Label createDateLabel(String wicketId, Date date, TimeZone timeZone, TimeUtils timeUtils) {
+		String format = GitBlit.getString(Keys.web.datestampShortFormat, "MM/dd/yy");
+		DateFormat df = new SimpleDateFormat(format);
+		if (timeZone == null) {
+			timeZone = GitBlit.getTimezone();
+		}
+		df.setTimeZone(timeZone);
+		String dateString;
+		if (date.getTime() == 0) {
+			dateString = "--";
+		} else {
+			dateString = df.format(date);
+		}
+		String title = null;
+		if (date.getTime() <= System.currentTimeMillis()) {
+			// past
+			title = timeUtils.timeAgo(date);
+		}
+		if ((System.currentTimeMillis() - date.getTime()) < 10 * 24 * 60 * 60 * 1000L) {
+			String tmp = dateString;
+			dateString = title;
+			title = tmp;
+		}
+		Label label = new Label(wicketId, dateString);
+		WicketUtils.setCssClass(label, timeUtils.timeAgoCss(date));
+		if (!StringUtils.isEmpty(title)) {
+			WicketUtils.setHtmlTooltip(label, title);
+		}
+		return label;
+	}
+
+	public static Label createTimeLabel(String wicketId, Date date, TimeZone timeZone, TimeUtils timeUtils) {
+		String format = GitBlit.getString(Keys.web.timeFormat, "HH:mm");
+		DateFormat df = new SimpleDateFormat(format);
+		if (timeZone == null) {
+			timeZone = GitBlit.getTimezone();
+		}
+		df.setTimeZone(timeZone);
+		String timeString;
+		if (date.getTime() == 0) {
+			timeString = "--";
+		} else {
+			timeString = df.format(date);
+		}
+		String title = timeUtils.timeAgo(date);
+		Label label = new Label(wicketId, timeString);
+		if (!StringUtils.isEmpty(title)) {
+			WicketUtils.setHtmlTooltip(label, title);
+		}
+		return label;
+	}
+
+	public static Label createDatestampLabel(String wicketId, Date date, TimeZone timeZone, TimeUtils timeUtils) {
+		String format = GitBlit.getString(Keys.web.datestampLongFormat, "EEEE, MMMM d, yyyy");
+		DateFormat df = new SimpleDateFormat(format);
+		if (timeZone == null) {
+			timeZone = GitBlit.getTimezone();
+		}
+		df.setTimeZone(timeZone);
+		String dateString;
+		if (date.getTime() == 0) {
+			dateString = "--";
+		} else {
+			dateString = df.format(date);
+		}
+		String title = null;
+		if (TimeUtils.isToday(date, timeZone)) {
+			title = timeUtils.today();
+		} else if (TimeUtils.isYesterday(date, timeZone)) {
+				title = timeUtils.yesterday();
+		} else if (date.getTime() <= System.currentTimeMillis()) {
+			// past
+			title = timeUtils.timeAgo(date);
+		}
+		if ((System.currentTimeMillis() - date.getTime()) < 10 * 24 * 60 * 60 * 1000L) {
+			String tmp = dateString;
+			dateString = title;
+			title = tmp;
+		}
+		Label label = new Label(wicketId, dateString);
+		if (!StringUtils.isEmpty(title)) {
+			WicketUtils.setHtmlTooltip(label, title);
+		}
+		return label;
+	}
+
+	public static Label createTimestampLabel(String wicketId, Date date, TimeZone timeZone, TimeUtils timeUtils) {
+		String format = GitBlit.getString(Keys.web.datetimestampLongFormat,
+				"EEEE, MMMM d, yyyy HH:mm Z");
+		DateFormat df = new SimpleDateFormat(format);
+		if (timeZone == null) {
+			timeZone = GitBlit.getTimezone();
+		}
+		df.setTimeZone(timeZone);
+		String dateString;
+		if (date.getTime() == 0) {
+			dateString = "--";
+		} else {
+			dateString = df.format(date);
+		}
+		String title = null;
+		if (date.getTime() <= System.currentTimeMillis()) {
+			// past
+			title = timeUtils.timeAgo(date);
+		}
+		Label label = new Label(wicketId, dateString);
+		if (!StringUtils.isEmpty(title)) {
+			WicketUtils.setHtmlTooltip(label, title);
+		}
+		return label;
+	}
+
+	public static IChartData getChartData(Collection<Metric> metrics) {
+		final double[] commits = new double[metrics.size()];
+		final double[] tags = new double[metrics.size()];
+		int i = 0;
+		double max = 0;
+		for (Metric m : metrics) {
+			commits[i] = m.count;
+			if (m.tag > 0) {
+				tags[i] = m.count;
+			} else {
+				tags[i] = -1d;
+			}
+			max = Math.max(max, m.count);
+			i++;
+		}
+		IChartData data = new AbstractChartData(max) {
+			private static final long serialVersionUID = 1L;
+
+			public double[][] getData() {
+				return new double[][] { commits, tags };
+			}
+		};
+		return data;
+	}
+
+	public static double maxValue(Collection<Metric> metrics) {
+		double max = Double.MIN_VALUE;
+		for (Metric m : metrics) {
+			if (m.count > max) {
+				max = m.count;
+			}
+		}
+		return max;
+	}
+
+	public static IChartData getScatterData(Collection<Metric> metrics) {
+		final double[] y = new double[metrics.size()];
+		final double[] x = new double[metrics.size()];
+		int i = 0;
+		double max = 0;
+		for (Metric m : metrics) {
+			y[i] = m.count;
+			if (m.duration > 0) {
+				x[i] = m.duration;
+			} else {
+				x[i] = -1d;
+			}
+			max = Math.max(max, m.count);
+			i++;
+		}
+		IChartData data = new AbstractChartData(max) {
+			private static final long serialVersionUID = 1L;
+
+			public double[][] getData() {
+				return new double[][] { x, y };
+			}
+		};
+		return data;
+	}
+
+}
diff --git a/src/main/java/com/gitblit/wicket/charting/GoogleChart.java b/src/main/java/com/gitblit/wicket/charting/GoogleChart.java
new file mode 100644
index 0000000..334b870
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/charting/GoogleChart.java
@@ -0,0 +1,107 @@
+/*
+ * 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.wicket.charting;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+import com.gitblit.utils.StringUtils;
+
+/**
+ * Abstract parent class for Google Charts built with the Visualization API.
+ * 
+ * @author James Moger
+ * 
+ */
+public abstract class GoogleChart implements Serializable {
+
+	private static final long serialVersionUID = 1L;
+	final String tagId;
+	final String dataName;
+	final String title;
+	final String keyName;
+	final String valueName;
+	final List<ChartValue> values;
+	int width;
+	int height;
+	boolean showLegend;
+
+	public GoogleChart(String tagId, String title, String keyName, String valueName) {
+		this.tagId = tagId;
+		this.dataName = StringUtils.getSHA1(title).substring(0, 8);
+		this.title = title;
+		this.keyName = keyName;
+		this.valueName = valueName;
+		values = new ArrayList<ChartValue>();
+		showLegend = true;
+	}
+
+	public void setWidth(int width) {
+		this.width = width;
+	}
+
+	public void setHeight(int height) {
+		this.height = height;
+	}
+	
+	public void setShowLegend(boolean val) {
+		this.showLegend = val;
+	}
+
+	public void addValue(String name, int value) {
+		values.add(new ChartValue(name, value));
+	}
+
+	public void addValue(String name, float value) {
+		values.add(new ChartValue(name, value));
+	}
+
+	public void addValue(String name, double value) {
+		values.add(new ChartValue(name, (float) value));
+	}
+
+	protected abstract void appendChart(StringBuilder sb);
+
+	protected void line(StringBuilder sb, String line) {
+		sb.append(line);
+		sb.append('\n');
+	}
+
+	protected class ChartValue implements Serializable, Comparable<ChartValue> {
+
+		private static final long serialVersionUID = 1L;
+
+		final String name;
+		final float value;
+
+		ChartValue(String name, float value) {
+			this.name = name;
+			this.value = value;
+		}
+
+		@Override
+		public int compareTo(ChartValue o) {
+			// sorts the dataset by largest value first
+			if (value > o.value) {
+				return -1;
+			} else if (value < o.value) {
+				return 1;
+			}
+			return 0;
+		}
+	}
+}
diff --git a/src/com/gitblit/wicket/charting/GoogleCharts.java b/src/main/java/com/gitblit/wicket/charting/GoogleCharts.java
similarity index 100%
rename from src/com/gitblit/wicket/charting/GoogleCharts.java
rename to src/main/java/com/gitblit/wicket/charting/GoogleCharts.java
diff --git a/src/com/gitblit/wicket/charting/GoogleLineChart.java b/src/main/java/com/gitblit/wicket/charting/GoogleLineChart.java
similarity index 100%
rename from src/com/gitblit/wicket/charting/GoogleLineChart.java
rename to src/main/java/com/gitblit/wicket/charting/GoogleLineChart.java
diff --git a/src/main/java/com/gitblit/wicket/charting/GooglePieChart.java b/src/main/java/com/gitblit/wicket/charting/GooglePieChart.java
new file mode 100644
index 0000000..1f5ae70
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/charting/GooglePieChart.java
@@ -0,0 +1,86 @@
+/*
+ * 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.wicket.charting;
+
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import com.gitblit.utils.StringUtils;
+
+/**
+ * Builds an interactive pie chart using the Visualization API.
+ * 
+ * @author James Moger
+ * 
+ */
+public class GooglePieChart extends GoogleChart {
+
+	private static final long serialVersionUID = 1L;
+
+	public GooglePieChart(String tagId, String title, String keyName, String valueName) {
+		super(tagId, title, keyName, valueName);
+	}
+
+	@Override
+	protected void appendChart(StringBuilder sb) {
+		// create dataset
+		String dName = "data_" + dataName;
+		line(sb, MessageFormat.format("var {0} = new google.visualization.DataTable();", dName));
+		line(sb, MessageFormat.format("{0}.addColumn(''string'', ''{1}'');", dName, keyName));
+		line(sb, MessageFormat.format("{0}.addColumn(''number'', ''{1}'');", dName, valueName));
+		line(sb, MessageFormat.format("{0}.addRows({1,number,0});", dName, values.size()));
+
+		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);
+			colors.append('\'');
+			colors.append(StringUtils.getColor(value.name));
+			colors.append('\'');
+			if (i < values.size() - 1) {
+				colors.append(',');
+			}
+			line(sb, MessageFormat.format("{0}.setValue({1,number,0}, 0, ''{2}'');", dName, i,
+					value.name));
+			line(sb, MessageFormat.format("{0}.setValue({1,number,0}, 1, {2,number,0.0});", dName,
+					i, value.value));
+		}
+		colors.append(']');
+
+		// instantiate chart
+		String cName = "chart_" + dataName;
+		line(sb, MessageFormat.format(
+				"var {0} = new google.visualization.PieChart(document.getElementById(''{1}''));",
+				cName, tagId));
+		line(sb,
+				MessageFormat
+						.format("{0}.draw({1}, '{' title: ''{4}'', {5}, legend: '{' position:''{6}'' '}' '}');",
+								cName, dName, width, height, title, colors.toString(), showLegend ? "right" : "none"));
+		line(sb, "");
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/charting/SecureChart.java b/src/main/java/com/gitblit/wicket/charting/SecureChart.java
new file mode 100644
index 0000000..60dae4b
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/charting/SecureChart.java
@@ -0,0 +1,633 @@
+/*
+ * Copyright 2007 Daniel Spiewak.
+ * Copyright 2013 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit.wicket.charting;
+
+import java.awt.Color;
+import java.awt.Dimension;
+import java.io.Serializable;
+import java.lang.reflect.InvocationTargetException;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+import org.apache.wicket.markup.ComponentTag;
+import org.apache.wicket.markup.html.WebComponent;
+import org.wicketstuff.googlecharts.ChartDataEncoding;
+import org.wicketstuff.googlecharts.IChartAxis;
+import org.wicketstuff.googlecharts.IChartData;
+import org.wicketstuff.googlecharts.IChartFill;
+import org.wicketstuff.googlecharts.IChartGrid;
+import org.wicketstuff.googlecharts.IChartProvider;
+import org.wicketstuff.googlecharts.IFillArea;
+import org.wicketstuff.googlecharts.ILineStyle;
+import org.wicketstuff.googlecharts.ILinearGradientFill;
+import org.wicketstuff.googlecharts.ILinearStripesFill;
+import org.wicketstuff.googlecharts.IRangeMarker;
+import org.wicketstuff.googlecharts.IShapeMarker;
+import org.wicketstuff.googlecharts.ISolidFill;
+import org.wicketstuff.googlecharts.Range;
+
+/**
+ * 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
+ */
+public class SecureChart extends WebComponent implements Serializable {
+
+    private static final long serialVersionUID = 6286305912682861488L;
+    private IChartProvider provider;
+    private StringBuilder url;
+    private final ReadWriteLock lock = new ReentrantReadWriteLock();
+
+    public SecureChart(String id, IChartProvider provider) {
+        super(id);
+
+        this.provider = provider;
+    }
+
+    public void invalidate() {
+        lock.writeLock().lock();
+        try {
+            url = null;
+        } finally {
+            lock.writeLock().unlock();
+        }
+    }
+
+    public CharSequence constructURL() {
+        lock.writeLock().lock();
+        try {
+            if (url != null) {
+                return url;
+            }
+
+            url = new StringBuilder("https://chart.googleapis.com/chart?");
+
+            addParameter(url, "chs", render(provider.getSize()));
+            addParameter(url, "chd", render(provider.getData()));
+            addParameter(url, "cht", render(provider.getType()));
+            addParameter(url, "chbh", render(provider.getBarWidth(), provider.getBarGroupSpacing()));
+            addParameter(url, "chtt", render(provider.getTitle()));
+            addParameter(url, "chdl", render(provider.getLegend()));
+            addParameter(url, "chco", render(provider.getColors()));
+
+            IChartFill bgFill = provider.getBackgroundFill();
+            IChartFill fgFill = provider.getChartFill();
+
+            StringBuilder fillParam = new StringBuilder();
+
+            if (bgFill != null) {
+                fillParam.append("bg,").append(render(bgFill));
+            }
+
+            if (fgFill != null) {
+                if (fillParam.length() > 0) {
+                    fillParam.append('|');
+                }
+
+                fillParam.append("c,").append(render(fgFill));
+            }
+
+            if (fillParam.toString().trim().equals("")) {
+                fillParam = null;
+            }
+
+            addParameter(url, "chf", fillParam);
+
+            IChartAxis[] axes = provider.getAxes();
+            addParameter(url, "chxt", renderTypes(axes));
+            addParameter(url, "chxl", renderLabels(axes));
+            addParameter(url, "chxp", renderPositions(axes));
+            addParameter(url, "chxr", renderRanges(axes));
+            addParameter(url, "chxs", renderStyles(axes));
+
+            addParameter(url, "chg", render(provider.getGrid()));
+            addParameter(url, "chm", render(provider.getShapeMarkers()));
+            addParameter(url, "chm", render(provider.getRangeMarkers()));
+            addParameter(url, "chls", render(provider.getLineStyles()));
+            addParameter(url, "chm", render(provider.getFillAreas()));
+            addParameter(url, "chl", render(provider.getPieLabels()));
+
+            return url;
+        } finally {
+            lock.writeLock().unlock();
+        }
+    }
+
+    private void addParameter(StringBuilder url, CharSequence param, CharSequence value) {
+        if (value == null || value.length() == 0) {
+            return;
+        }
+
+        if (url.charAt(url.length() - 1) != '?') {
+            url.append('&');
+        }
+
+        url.append(param).append('=').append(value);
+    }
+    
+    private CharSequence convert(ChartDataEncoding encoding, double value, double max) {
+    	switch (encoding) {
+    	case TEXT:
+    		return SecureChartDataEncoding.TEXT.convert(value, max);
+    	case EXTENDED:
+    		return SecureChartDataEncoding.EXTENDED.convert(value, max);
+    	case SIMPLE:
+    	default:
+    		return SecureChartDataEncoding.SIMPLE.convert(value, max);
+    	}
+    }
+
+    private CharSequence render(Dimension dim) {
+        if (dim == null) {
+            return null;
+        }
+
+        return new StringBuilder().append(dim.width).append('x').append(dim.height);
+    }
+
+    private CharSequence render(IChartData data) {
+        if (data == null) {
+            return null;
+        }
+
+        ChartDataEncoding encoding = data.getEncoding();
+
+        StringBuilder back = new StringBuilder();
+        back.append(render(encoding)).append(':');
+
+        for (double[] set : data.getData()) {
+            if (set == null || set.length == 0) {
+                back.append(convert(encoding, -1, data.getMax()));
+            } else {
+                for (double value : set) {
+                    back.append(convert(encoding, value, data.getMax())).append(encoding.getValueSeparator());
+                }
+
+                if (back.substring(back.length() - encoding.getValueSeparator().length(),
+                        back.length()).equals(encoding.getValueSeparator())) {
+                    back.setLength(back.length() - encoding.getValueSeparator().length());
+                }
+            }
+
+            back.append(encoding.getSetSeparator());
+        }
+
+        if (back.substring(back.length() - encoding.getSetSeparator().length(),
+                back.length()).equals(encoding.getSetSeparator())) {
+            back.setLength(back.length() - encoding.getSetSeparator().length());
+        }
+
+        return back;
+    }
+
+    private CharSequence render(Enum<?> value) {
+        if (value == null) {
+            return null;
+        }
+
+        try {
+            Object back = value.getClass().getMethod("getRendering").invoke(value);
+
+            if (back != null) {
+                return back.toString();
+            }
+        } catch (IllegalArgumentException e) {
+        } catch (SecurityException e) {
+        } catch (IllegalAccessException e) {
+        } catch (InvocationTargetException e) {
+        } catch (NoSuchMethodException e) {
+        }
+
+        return null;
+    }
+
+    private CharSequence render(int barWidth, int groupSpacing) {
+        if (barWidth == -1) {
+            return null;
+        }
+
+        StringBuilder back = new StringBuilder(barWidth);
+
+        if (groupSpacing >= 0) {
+            back.append(',').append(groupSpacing);
+        }
+
+        return back;
+    }
+
+    private CharSequence render(String[] values) {
+        if (values == null) {
+            return null;
+        }
+
+        StringBuilder back = new StringBuilder();
+
+        for (String value : values) {
+            CharSequence toRender = render(value);
+            if (toRender == null) {
+                toRender = "";
+            }
+
+            back.append(toRender).append('|');
+        }
+
+        if (back.length() > 0) {
+            back.setLength(back.length() - 1);
+        }
+
+        return back;
+    }
+
+    private CharSequence render(String value) {
+        if (value == null) {
+            return value;
+        }
+
+        StringBuilder back = new StringBuilder();
+
+        for (char c : value.toCharArray()) {
+            if (c == ' ') {
+                back.append('+');
+            } else {
+                back.append(c);
+            }
+        }
+
+        return back;
+    }
+
+    private CharSequence render(Color[] values) {
+        if (values == null) {
+            return null;
+        }
+
+        StringBuilder back = new StringBuilder();
+
+        for (Color value : values) {
+            CharSequence toRender = render(value);
+            if (toRender == null) {
+                toRender = "";
+            }
+
+            back.append(toRender).append(',');
+        }
+
+        if (back.length() > 0) {
+            back.setLength(back.length() - 1);
+        }
+
+        return back;
+    }
+
+    private CharSequence render(Color value) {
+        if (value == null) {
+            return null;
+        }
+
+        StringBuilder back = new StringBuilder();
+
+        {
+            String toPad = Integer.toHexString(value.getRed());
+
+            if (toPad.length() == 1) {
+                back.append(0);
+            }
+            back.append(toPad);
+        }
+
+        {
+            String toPad = Integer.toHexString(value.getGreen());
+
+            if (toPad.length() == 1) {
+                back.append(0);
+            }
+            back.append(toPad);
+        }
+
+        {
+            String toPad = Integer.toHexString(value.getBlue());
+
+            if (toPad.length() == 1) {
+                back.append(0);
+            }
+            back.append(toPad);
+        }
+
+        {
+            String toPad = Integer.toHexString(value.getAlpha());
+
+            if (toPad.length() == 1) {
+                back.append(0);
+            }
+            back.append(toPad);
+        }
+
+        return back;
+    }
+
+    private CharSequence render(IChartFill fill) {
+        if (fill == null) {
+            return null;
+        }
+
+        StringBuilder back = new StringBuilder();
+
+        if (fill instanceof ISolidFill) {
+            ISolidFill solidFill = (ISolidFill) fill;
+
+            back.append("s,");
+            back.append(render(solidFill.getColor()));
+        } else if (fill instanceof ILinearGradientFill) {
+            ILinearGradientFill gradientFill = (ILinearGradientFill) fill;
+
+            back.append("lg,").append(gradientFill.getAngle()).append(',');
+
+            Color[] colors = gradientFill.getColors();
+            double[] offsets = gradientFill.getOffsets();
+            for (int i = 0; i < colors.length; i++) {
+                back.append(render(colors[i])).append(',').append(offsets[i]).append(',');
+            }
+
+            back.setLength(back.length() - 1);
+        } else if (fill instanceof ILinearStripesFill) {
+            ILinearStripesFill stripesFill = (ILinearStripesFill) fill;
+
+            back.append("ls,").append(stripesFill.getAngle()).append(',');
+
+            Color[] colors = stripesFill.getColors();
+            double[] widths = stripesFill.getWidths();
+            for (int i = 0; i < colors.length; i++) {
+                back.append(render(colors[i])).append(',').append(widths[i]).append(',');
+            }
+
+            back.setLength(back.length() - 1);
+        } else {
+            return null;
+        }
+
+        return back;
+    }
+
+    private CharSequence renderTypes(IChartAxis[] axes) {
+        if (axes == null) {
+            return null;
+        }
+
+        StringBuilder back = new StringBuilder();
+
+        for (IChartAxis axis : axes) {
+            back.append(render(axis.getType())).append(',');
+        }
+
+        if (back.length() > 0) {
+            back.setLength(back.length() - 1);
+        }
+
+        return back;
+    }
+
+    private CharSequence renderLabels(IChartAxis[] axes) {
+        if (axes == null) {
+            return null;
+        }
+
+        StringBuilder back = new StringBuilder();
+
+        for (int i = 0; i < axes.length; i++) {
+            if (axes[i] == null || axes[i].getLabels() == null) {
+                continue;
+            }
+
+            back.append(i).append(":|");
+
+            for (String label : axes[i].getLabels()) {
+                if (label == null) {
+                    back.append('|');
+                    continue;
+                }
+
+                back.append(render(label)).append('|');
+            }
+
+            if (i == axes.length - 1) {
+                back.setLength(back.length() - 1);
+            }
+        }
+
+        return back;
+    }
+
+    private CharSequence renderPositions(IChartAxis[] axes) {
+        if (axes == null) {
+            return null;
+        }
+
+        StringBuilder back = new StringBuilder();
+
+        for (int i = 0; i < axes.length; i++) {
+            if (axes[i] == null || axes[i].getPositions() == null) {
+                continue;
+            }
+
+            back.append(i).append(',');
+
+            for (double position : axes[i].getPositions()) {
+                back.append(position).append(',');
+            }
+
+            back.setLength(back.length() - 1);
+
+            back.append('|');
+        }
+
+        if (back.length() > 0) {
+            back.setLength(back.length() - 1);
+        }
+
+        return back;
+    }
+
+    private CharSequence renderRanges(IChartAxis[] axes) {
+        if (axes == null) {
+            return null;
+        }
+
+        StringBuilder back = new StringBuilder();
+
+        for (int i = 0; i < axes.length; i++) {
+            if (axes[i] == null || axes[i].getRange() == null) {
+                continue;
+            }
+
+            back.append(i).append(',');
+
+            Range range = axes[i].getRange();
+            back.append(range.getStart()).append(',').append(range.getEnd()).append('|');
+        }
+
+        if (back.length() > 0) {
+            back.setLength(back.length() - 1);
+        }
+
+        return back;
+    }
+
+    private CharSequence renderStyles(IChartAxis[] axes) {
+        if (axes == null) {
+            return null;
+        }
+
+        StringBuilder back = new StringBuilder();
+
+        for (int i = 0; i < axes.length; i++) {
+            if (axes[i] == null || axes[i].getColor() == null
+                    || axes[i].getFontSize() < 0 || axes[i].getAlignment() == null) {
+                continue;
+            }
+
+            back.append(i).append(',');
+            back.append(render(axes[i].getColor())).append(',');
+            back.append(axes[i].getFontSize()).append(',');
+            back.append(render(axes[i].getAlignment())).append('|');
+        }
+
+        if (back.length() > 0) {
+            back.setLength(back.length() - 1);
+        }
+
+        return back;
+    }
+
+    private CharSequence render(IChartGrid grid) {
+        if (grid == null) {
+            return null;
+        }
+
+        StringBuilder back = new StringBuilder();
+
+        back.append(grid.getXStepSize()).append(',');
+        back.append(grid.getYStepSize());
+
+        if (grid.getSegmentLength() >= 0) {
+            back.append(',').append(grid.getSegmentLength());
+            back.append(',').append(grid.getBlankLength());
+        }
+
+        return back;
+    }
+
+    private CharSequence render(IShapeMarker[] markers) {
+        if (markers == null) {
+            return null;
+        }
+
+        StringBuilder back = new StringBuilder();
+
+        for (IShapeMarker marker : markers) {
+            back.append(render(marker.getType())).append(',');
+            back.append(render(marker.getColor())).append(',');
+            back.append(marker.getIndex()).append(',');
+            back.append(marker.getPoint()).append(',');
+            back.append(marker.getSize()).append('|');
+        }
+
+        if (back.length() > 0) {
+            back.setLength(back.length() - 1);
+        }
+
+        return back;
+    }
+
+    private CharSequence render(IRangeMarker[] markers) {
+        if (markers == null) {
+            return null;
+        }
+
+        StringBuilder back = new StringBuilder();
+
+        for (IRangeMarker marker : markers) {
+            back.append(render(marker.getType())).append(',');
+            back.append(render(marker.getColor())).append(',');
+            back.append(0).append(',');
+            back.append(marker.getStart()).append(',');
+            back.append(marker.getEnd()).append('|');
+        }
+
+        if (back.length() > 0) {
+            back.setLength(back.length() - 1);
+        }
+
+        return back;
+    }
+
+    private CharSequence render(IFillArea[] areas) {
+        if (areas == null) {
+            return null;
+        }
+
+        StringBuilder back = new StringBuilder();
+
+        for (IFillArea area : areas) {
+            back.append(render(area.getType())).append(',');
+            back.append(render(area.getColor())).append(',');
+            back.append(area.getStartIndex()).append(',');
+            back.append(area.getEndIndex()).append(',');
+            back.append(0).append('|');
+        }
+
+        if (back.length() > 0) {
+            back.setLength(back.length() - 1);
+        }
+
+        return back;
+    }
+
+    private CharSequence render(ILineStyle[] styles) {
+        if (styles == null) {
+            return null;
+        }
+
+        StringBuilder back = new StringBuilder();
+
+        for (ILineStyle style : styles) {
+            if (style == null) {
+                back.append('|');
+                continue;
+            }
+
+            back.append(style.getThickness()).append(',');
+            back.append(style.getSegmentLength()).append(',');
+            back.append(style.getBlankLength()).append('|');
+        }
+
+        if (back.length() > 0) {
+            back.setLength(back.length() - 1);
+        }
+
+        return back;
+    }
+
+    @Override
+    protected void onComponentTag(ComponentTag tag) {
+        checkComponentTag(tag, "img");
+        super.onComponentTag(tag);
+
+        tag.put("src", constructURL());
+    }
+}
diff --git a/src/main/java/com/gitblit/wicket/charting/SecureChartDataEncoding.java b/src/main/java/com/gitblit/wicket/charting/SecureChartDataEncoding.java
new file mode 100644
index 0000000..90a0596
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/charting/SecureChartDataEncoding.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2007 Daniel Spiewak.
+ * Copyright 2013 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit.wicket.charting;
+
+/**
+ * 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) {
+            if (value < 0) {
+                return "_";
+            }
+
+            value = Math.round((CHARS.length() - 1) * value / max);
+
+            if (value > CHARS.length() - 1) {
+                throw new IllegalArgumentException(value + " is out of range for SIMPLE encoding");
+            }
+
+            return Character.toString(CHARS.charAt((int) value));
+        }
+    },
+    TEXT("t", ",", "|") {
+
+    	CharSequence convert(double value, double max) {
+            if (value < 0) {
+                value = -1;
+            }
+
+            if (value > 100) {
+                throw new IllegalArgumentException(value + " is out of range for TEXT encoding");
+            }
+
+            return Double.toString(value);
+        }
+    },
+    EXTENDED("e", "", ",") {
+
+    	CharSequence convert(double value, double max) {
+            if (value < 0) {
+                return "__";
+            }
+
+            value = Math.round(value);
+
+            if (value > (EXT_CHARS.length() - 1) * (EXT_CHARS.length() - 1)) {
+                throw new IllegalArgumentException(value + " is out of range for EXTENDED encoding");
+            }
+
+            int rem = (int) (value % EXT_CHARS.length());
+            int exp = (int) (value / EXT_CHARS.length());
+
+            return new StringBuilder().append(EXT_CHARS.charAt(exp)).append(EXT_CHARS.charAt(rem));
+        }
+    };
+    private static final String CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
+    private static final String EXT_CHARS = CHARS + "-_.";
+    private final String rendering, valueSeparator, setSeparator;
+
+    private SecureChartDataEncoding(String rendering, String valueSeparator, String setSeparator) {
+        this.rendering = rendering;
+        this.valueSeparator = valueSeparator;
+        this.setSeparator = setSeparator;
+    }
+
+    public String getRendering() {
+        return rendering;
+    }
+
+    public String getValueSeparator() {
+        return valueSeparator;
+    }
+
+    public String getSetSeparator() {
+        return setSeparator;
+    }
+
+    abstract CharSequence convert(double value, double max);
+}
diff --git a/src/main/java/com/gitblit/wicket/freemarker/Freemarker.java b/src/main/java/com/gitblit/wicket/freemarker/Freemarker.java
new file mode 100644
index 0000000..ad7aa96
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/freemarker/Freemarker.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2013 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit.wicket.freemarker;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.Map;
+
+import freemarker.template.Configuration;
+import freemarker.template.DefaultObjectWrapper;
+import freemarker.template.Template;
+import freemarker.template.TemplateException;
+
+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);
+	}
+
+}
diff --git a/src/main/java/com/gitblit/wicket/freemarker/FreemarkerPanel.java b/src/main/java/com/gitblit/wicket/freemarker/FreemarkerPanel.java
new file mode 100644
index 0000000..d57c3a0
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/freemarker/FreemarkerPanel.java
@@ -0,0 +1,308 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit.wicket.freemarker;
+
+import java.io.IOException;
+import java.io.StringWriter;
+import java.util.Map;
+
+import org.apache.wicket.MarkupContainer;
+import org.apache.wicket.WicketRuntimeException;
+import org.apache.wicket.markup.ComponentTag;
+import org.apache.wicket.markup.IMarkupCacheKeyProvider;
+import org.apache.wicket.markup.IMarkupResourceStreamProvider;
+import org.apache.wicket.markup.MarkupStream;
+import org.apache.wicket.markup.html.panel.Panel;
+import org.apache.wicket.model.IModel;
+import org.apache.wicket.model.Model;
+import org.apache.wicket.util.resource.IResourceStream;
+import org.apache.wicket.util.resource.StringResourceStream;
+import org.apache.wicket.util.string.Strings;
+
+import com.gitblit.utils.StringUtils;
+
+import freemarker.template.Template;
+import freemarker.template.TemplateException;
+
+/**
+ * This class allows FreeMarker to be used as a Wicket preprocessor or as a
+ * 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
+		implements
+			IMarkupResourceStreamProvider,
+			IMarkupCacheKeyProvider
+{
+	private static final long serialVersionUID = 1L;
+
+	private final String template;
+	private boolean parseGeneratedMarkup;
+	private boolean escapeHtml;
+	private boolean throwFreemarkerExceptions;
+	private transient String stackTraceAsString;
+	private transient String evaluatedTemplate;
+
+	
+	/**
+	 * Construct.
+	 * 
+	 * @param id
+	 *            Component id
+	 * @param template
+	 *            The Freemarker template
+	 * @param values
+	 *            values map that can be substituted by Freemarker.
+	 */
+	public FreemarkerPanel(final String id, String template, final Map<String, Object> values)
+	{
+		this(id, template, Model.ofMap(values));
+	}
+	
+	/**
+	 * Construct.
+	 * 
+	 * @param id
+	 *            Component id
+	 * @param templateResource
+	 *            The Freemarker template as a string resource
+	 * @param model
+	 *            Model with variables that can be substituted by Freemarker.
+	 */
+	public FreemarkerPanel(final String id, final String template, final IModel< ? extends Map<String, Object>> model)
+	{
+		super(id, model);
+		this.template = template;
+	}
+
+	/**
+	 * Gets the Freemarker template.
+	 * 
+	 * @return the Freemarker template
+	 */
+	private Template getTemplate()
+	{
+		if (StringUtils.isEmpty(template))
+		{
+			throw new IllegalArgumentException("Template not specified!");
+		}
+
+		try {
+			return Freemarker.getTemplate(template);
+		} catch (IOException e) {
+			onException(e);
+		}
+
+		return null;
+	}
+
+	/**
+	 * @see org.apache.wicket.markup.html.panel.Panel#onComponentTagBody(org.apache.wicket.markup.
+	 *      MarkupStream, org.apache.wicket.markup.ComponentTag)
+	 */
+	@Override
+	protected void onComponentTagBody(MarkupStream markupStream, ComponentTag openTag)
+	{
+		if (!Strings.isEmpty(stackTraceAsString))
+		{
+			// TODO: only display the Freemarker error/stacktrace in development
+			// mode?
+			replaceComponentTagBody(markupStream, openTag, Strings
+					.toMultilineMarkup(stackTraceAsString));
+		}
+		else if (!parseGeneratedMarkup)
+		{
+			// check that no components have been added in case the generated
+			// markup should not be
+			// parsed
+			if (size() > 0)
+			{
+				throw new WicketRuntimeException(
+						"Components cannot be added if the generated markup should not be parsed.");
+			}
+
+			if (evaluatedTemplate == null)
+			{
+				// initialize evaluatedTemplate
+				getMarkupResourceStream(null, null);
+			}
+			replaceComponentTagBody(markupStream, openTag, evaluatedTemplate);
+		}
+		else
+		{
+			super.onComponentTagBody(markupStream, openTag);
+		}
+	}
+
+	/**
+	 * Either print or rethrow the throwable.
+	 * 
+	 * @param exception
+	 *            the cause
+	 * @param markupStream
+	 *            the markup stream
+	 * @param openTag
+	 *            the open tag
+	 */
+	private void onException(final Exception exception)
+	{
+		if (!throwFreemarkerExceptions)
+		{
+			// print the exception on the panel
+			stackTraceAsString = Strings.toString(exception);
+		}
+		else
+		{
+			// rethrow the exception
+			throw new WicketRuntimeException(exception);
+		}
+	}
+
+	/**
+	 * Gets whether to escape HTML characters.
+	 * 
+	 * @return whether to escape HTML characters. The default value is false.
+	 */
+	public void setEscapeHtml(boolean value)
+	{
+		this.escapeHtml = value;
+	}
+
+	/**
+	 * Evaluates the template and returns the result.
+	 * 
+	 * @param templateReader
+	 *            used to read the template
+	 * @return the result of evaluating the velocity template
+	 */
+	private String evaluateFreemarkerTemplate(Template template)
+	{
+		if (evaluatedTemplate == null)
+		{
+			// Get model as a map
+			final Map<String, Object> map = (Map<String, Object>)getDefaultModelObject();
+
+			// create a writer for capturing the Velocity output
+			StringWriter writer = new StringWriter();
+
+			// string to be used as the template name for log messages in case
+			// of error
+			try
+			{
+				// execute the Freemarker script and capture the output in writer
+				Freemarker.evaluate(template, map, writer);
+
+				// replace the tag's body the Freemarker output
+				evaluatedTemplate = writer.toString();
+
+				if (escapeHtml)
+				{
+					// encode the result in order to get valid html output that
+					// does not break the rest of the page
+					evaluatedTemplate = Strings.escapeMarkup(evaluatedTemplate).toString();
+				}
+				return evaluatedTemplate;
+			}
+			catch (IOException e)
+			{
+				onException(e);
+			}
+			catch (TemplateException e)
+			{
+				onException(e);
+			}
+			return null;
+		}
+		return evaluatedTemplate;
+	}
+
+	/**
+	 * 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)
+	{
+		this.parseGeneratedMarkup = value;
+	}
+
+	/**
+	 * Whether any Freemarker exception should be trapped and displayed on the panel (false) or thrown
+	 * up to be handled by the exception mechanism of Wicket (true). The default is false, which
+	 * traps and displays any exception without having consequences for the other components on the
+	 * page.
+	 * <p>
+	 * Trapping these exceptions without disturbing the other components is especially useful in CMS
+	 * like applications, where 'normal' users are allowed to do basic scripting. On errors, you
+	 * 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)
+	{
+		this.throwFreemarkerExceptions = value;
+	}
+
+	/**
+	 * @see org.apache.wicket.markup.IMarkupResourceStreamProvider#getMarkupResourceStream(org.apache
+	 *      .wicket.MarkupContainer, java.lang.Class)
+	 */
+	public final IResourceStream getMarkupResourceStream(MarkupContainer container,
+			Class< ? > containerClass)
+	{
+		Template template = getTemplate();
+		if (template == null)
+		{
+			throw new WicketRuntimeException("could not find Freemarker template for panel: " + this);
+		}
+
+		// evaluate the template and return a new StringResourceStream
+		StringBuffer sb = new StringBuffer();
+		sb.append("<wicket:panel>");
+		sb.append(evaluateFreemarkerTemplate(template));
+		sb.append("</wicket:panel>");
+		return new StringResourceStream(sb.toString());
+	}
+
+	/**
+	 * @see org.apache.wicket.markup.IMarkupCacheKeyProvider#getCacheKey(org.apache.wicket.
+	 *      MarkupContainer, java.lang.Class)
+	 */
+	public final String getCacheKey(MarkupContainer container, Class< ? > containerClass)
+	{
+		// don't cache the evaluated template
+		return null;
+	}
+
+	/**
+	 * @see org.apache.wicket.Component#onDetach()
+	 */
+	@Override
+	protected void onDetach()
+	{
+		super.onDetach();
+		stackTraceAsString = null;
+		evaluatedTemplate = null;
+	}
+}
diff --git a/src/main/java/com/gitblit/wicket/freemarker/templates/FilterableProjectList.fm b/src/main/java/com/gitblit/wicket/freemarker/templates/FilterableProjectList.fm
new file mode 100644
index 0000000..691f089
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/freemarker/templates/FilterableProjectList.fm
@@ -0,0 +1,15 @@
+<div ng-controller="${ngCtrl}" style="border: 1px solid #ddd;border-radius: 4px;margin-bottom: 20px;">
+	<div class="header" style="padding: 5px;border: none;"><i class="icon-folder-close"></i> <span wicket:id="${ngList}Title"></span>
+		<div style="padding: 5px 0px 0px;">
+			<input type="text" ng-model="query.n" class="input-large" wicket:message="placeholder:gb.filter" style="border-radius: 14px; padding: 3px 14px;margin: 0px;"></input>
+		</div>
+	</div>
+		
+	<div ng-repeat="item in ${ngList} | filter:query" style="padding: 3px;border-top: 1px solid #ddd;">
+		<a href="project/{{item.p}}" title="{{item.i}}"><b>{{item.n}}</b></a>
+		<span class="link hidden-tablet hidden-phone" style="color: #bbb;" title="{{item.d}}">{{item.t}}</span>
+		<span class="pull-right">
+			<span style="padding: 0px 5px;color: #888;font-weight:bold;vertical-align:middle;" wicket:message="title:gb.repositories">{{item.c | number}}</span>
+		</span>
+	</div>
+</div>
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/freemarker/templates/FilterableRepositoryList.fm b/src/main/java/com/gitblit/wicket/freemarker/templates/FilterableRepositoryList.fm
new file mode 100644
index 0000000..cf1b7a8
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/freemarker/templates/FilterableRepositoryList.fm
@@ -0,0 +1,19 @@
+<div ng-controller="${ngCtrl}" style="border: 1px solid #ddd;border-radius: 4px;">
+	<div class="header" style="padding: 5px;border: none;"><i wicket:id="${ngList}Icon"></i> <span wicket:id="${ngList}Title"></span>
+		<div class="hidden-phone pull-right">
+			<span wicket:id="${ngList}Button"></span>
+		</div>
+		<div style="padding: 5px 0px 0px;">
+			<input type="text" ng-model="query.r" class="input-large" wicket:message="placeholder:gb.filter" style="border-radius: 14px; padding: 3px 14px;margin: 0px;"></input>
+		</div>
+	</div>
+	
+	<div ng-repeat="item in ${ngList} | filter:query" style="padding: 3px;border-top: 1px solid #ddd;">
+		<b><span class="repositorySwatch" style="background-color:{{item.c}};"><span ng-show="item.wc">!</span><span ng-show="!item.wc">&nbsp;</span></span></b>
+		<a href="summary/?r={{item.r}}" title="{{item.i}}">{{item.p}}<b>{{item.n}}</b></a>
+		<span class="link hidden-tablet hidden-phone" style="color: #bbb;" title="{{item.d}}">{{item.t}}</span>
+		<span ng-show="item.s" class="pull-right">
+			<span style="padding: 0px 5px;color: #888;font-weight:bold;vertical-align:middle;">{{item.s | number}} <i style="vertical-align:baseline;" class="iconic-star"></i></span>
+		</span>
+	</div>		
+</div>
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/ng/NgController.java b/src/main/java/com/gitblit/wicket/ng/NgController.java
new file mode 100644
index 0000000..628c0b6
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/ng/NgController.java
@@ -0,0 +1,80 @@
+/*
+ Copyright 2013 gitblit.com.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ */
+
+package com.gitblit.wicket.ng;
+
+import java.text.MessageFormat;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.wicket.ResourceReference;
+import org.apache.wicket.markup.html.IHeaderContributor;
+import org.apache.wicket.markup.html.IHeaderResponse;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+
+/**
+ * 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);
+	}
+
+	@Override
+	public void renderHead(IHeaderResponse response) {
+		// add Google AngularJS reference
+		response.renderJavascriptReference(new ResourceReference(NgController.class, "angular.js"));
+
+		Gson gson = new GsonBuilder().create();
+
+		StringBuilder sb = new StringBuilder();
+		line(sb, MessageFormat.format("<!-- AngularJS {0} data controller -->", name));
+		line(sb, MessageFormat.format("function {0}($scope) '{'", name));
+		for (Map.Entry<String, Object> entry : variables.entrySet()) {
+			String var = entry.getKey();
+			Object o = entry.getValue();
+			String json = gson.toJson(o);
+			line(sb, MessageFormat.format("\t$scope.{0} = {1};", var, json));
+		}
+		line(sb, "}");
+		
+		response.renderJavascript(sb.toString(), null);
+	}
+
+	private void line(StringBuilder sb, String line) {
+		sb.append(line);
+		sb.append('\n');
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/ng/angular.js b/src/main/java/com/gitblit/wicket/ng/angular.js
new file mode 100644
index 0000000..472d7ff
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/ng/angular.js
@@ -0,0 +1,163 @@
+/*
+ AngularJS v1.0.7
+ (c) 2010-2012 Google, Inc. http://angularjs.org
+ License: MIT
+*/
+(function(P,T,q){'use strict';function m(b,a,c){var d;if(b)if(H(b))for(d in b)d!="prototype"&&d!="length"&&d!="name"&&b.hasOwnProperty(d)&&a.call(c,b[d],d);else if(b.forEach&&b.forEach!==m)b.forEach(a,c);else if(!b||typeof b.length!=="number"?0:typeof b.hasOwnProperty!="function"&&typeof b.constructor!="function"||b instanceof K||ca&&b instanceof ca||wa.call(b)!=="[object Object]"||typeof b.callee==="function")for(d=0;d<b.length;d++)a.call(c,b[d],d);else for(d in b)b.hasOwnProperty(d)&&a.call(c,b[d],
+d);return b}function mb(b){var a=[],c;for(c in b)b.hasOwnProperty(c)&&a.push(c);return a.sort()}function fc(b,a,c){for(var d=mb(b),e=0;e<d.length;e++)a.call(c,b[d[e]],d[e]);return d}function nb(b){return function(a,c){b(c,a)}}function xa(){for(var b=aa.length,a;b;){b--;a=aa[b].charCodeAt(0);if(a==57)return aa[b]="A",aa.join("");if(a==90)aa[b]="0";else return aa[b]=String.fromCharCode(a+1),aa.join("")}aa.unshift("0");return aa.join("")}function ob(b,a){a?b.$$hashKey=a:delete b.$$hashKey}function v(b){var a=
+b.$$hashKey;m(arguments,function(a){a!==b&&m(a,function(a,c){b[c]=a})});ob(b,a);return b}function G(b){return parseInt(b,10)}function ya(b,a){return v(new (v(function(){},{prototype:b})),a)}function C(){}function ma(b){return b}function I(b){return function(){return b}}function w(b){return typeof b=="undefined"}function y(b){return typeof b!="undefined"}function L(b){return b!=null&&typeof b=="object"}function B(b){return typeof b=="string"}function Qa(b){return typeof b=="number"}function na(b){return wa.apply(b)==
+"[object Date]"}function E(b){return wa.apply(b)=="[object Array]"}function H(b){return typeof b=="function"}function oa(b){return b&&b.document&&b.location&&b.alert&&b.setInterval}function Q(b){return B(b)?b.replace(/^\s*/,"").replace(/\s*$/,""):b}function gc(b){return b&&(b.nodeName||b.bind&&b.find)}function Ra(b,a,c){var d=[];m(b,function(b,g,h){d.push(a.call(c,b,g,h))});return d}function za(b,a){if(b.indexOf)return b.indexOf(a);for(var c=0;c<b.length;c++)if(a===b[c])return c;return-1}function Sa(b,
+a){var c=za(b,a);c>=0&&b.splice(c,1);return a}function U(b,a){if(oa(b)||b&&b.$evalAsync&&b.$watch)throw Error("Can't copy Window or Scope");if(a){if(b===a)throw Error("Can't copy equivalent objects or arrays");if(E(b))for(var c=a.length=0;c<b.length;c++)a.push(U(b[c]));else{c=a.$$hashKey;m(a,function(b,c){delete a[c]});for(var d in b)a[d]=U(b[d]);ob(a,c)}}else(a=b)&&(E(b)?a=U(b,[]):na(b)?a=new Date(b.getTime()):L(b)&&(a=U(b,{})));return a}function hc(b,a){var a=a||{},c;for(c in b)b.hasOwnProperty(c)&&
+c.substr(0,2)!=="$$"&&(a[c]=b[c]);return a}function fa(b,a){if(b===a)return!0;if(b===null||a===null)return!1;if(b!==b&&a!==a)return!0;var c=typeof b,d;if(c==typeof a&&c=="object")if(E(b)){if((c=b.length)==a.length){for(d=0;d<c;d++)if(!fa(b[d],a[d]))return!1;return!0}}else if(na(b))return na(a)&&b.getTime()==a.getTime();else{if(b&&b.$evalAsync&&b.$watch||a&&a.$evalAsync&&a.$watch||oa(b)||oa(a))return!1;c={};for(d in b)if(!(d.charAt(0)==="$"||H(b[d]))){if(!fa(b[d],a[d]))return!1;c[d]=!0}for(d in a)if(!c[d]&&
+d.charAt(0)!=="$"&&a[d]!==q&&!H(a[d]))return!1;return!0}return!1}function Ta(b,a){var c=arguments.length>2?ha.call(arguments,2):[];return H(a)&&!(a instanceof RegExp)?c.length?function(){return arguments.length?a.apply(b,c.concat(ha.call(arguments,0))):a.apply(b,c)}:function(){return arguments.length?a.apply(b,arguments):a.call(b)}:a}function ic(b,a){var c=a;/^\$+/.test(b)?c=q:oa(a)?c="$WINDOW":a&&T===a?c="$DOCUMENT":a&&a.$evalAsync&&a.$watch&&(c="$SCOPE");return c}function da(b,a){return JSON.stringify(b,
+ic,a?"  ":null)}function pb(b){return B(b)?JSON.parse(b):b}function Ua(b){b&&b.length!==0?(b=z(""+b),b=!(b=="f"||b=="0"||b=="false"||b=="no"||b=="n"||b=="[]")):b=!1;return b}function pa(b){b=u(b).clone();try{b.html("")}catch(a){}var c=u("<div>").append(b).html();try{return b[0].nodeType===3?z(c):c.match(/^(<[^>]+>)/)[1].replace(/^<([\w\-]+)/,function(a,b){return"<"+z(b)})}catch(d){return z(c)}}function Va(b){var a={},c,d;m((b||"").split("&"),function(b){b&&(c=b.split("="),d=decodeURIComponent(c[0]),
+a[d]=y(c[1])?decodeURIComponent(c[1]):!0)});return a}function qb(b){var a=[];m(b,function(b,d){a.push(Wa(d,!0)+(b===!0?"":"="+Wa(b,!0)))});return a.length?a.join("&"):""}function Xa(b){return Wa(b,!0).replace(/%26/gi,"&").replace(/%3D/gi,"=").replace(/%2B/gi,"+")}function Wa(b,a){return encodeURIComponent(b).replace(/%40/gi,"@").replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%20/g,a?"%20":"+")}function jc(b,a){function c(a){a&&d.push(a)}var d=[b],e,g,h=["ng:app","ng-app","x-ng-app",
+"data-ng-app"],f=/\sng[:\-]app(:\s*([\w\d_]+);?)?\s/;m(h,function(a){h[a]=!0;c(T.getElementById(a));a=a.replace(":","\\:");b.querySelectorAll&&(m(b.querySelectorAll("."+a),c),m(b.querySelectorAll("."+a+"\\:"),c),m(b.querySelectorAll("["+a+"]"),c))});m(d,function(a){if(!e){var b=f.exec(" "+a.className+" ");b?(e=a,g=(b[2]||"").replace(/\s+/g,",")):m(a.attributes,function(b){if(!e&&h[b.name])e=a,g=b.value})}});e&&a(e,g?[g]:[])}function rb(b,a){var c=function(){b=u(b);a=a||[];a.unshift(["$provide",function(a){a.value("$rootElement",
+b)}]);a.unshift("ng");var c=sb(a);c.invoke(["$rootScope","$rootElement","$compile","$injector",function(a,b,c,d){a.$apply(function(){b.data("$injector",d);c(b)(a)})}]);return c},d=/^NG_DEFER_BOOTSTRAP!/;if(P&&!d.test(P.name))return c();P.name=P.name.replace(d,"");Ya.resumeBootstrap=function(b){m(b,function(b){a.push(b)});c()}}function Za(b,a){a=a||"_";return b.replace(kc,function(b,d){return(d?a:"")+b.toLowerCase()})}function $a(b,a,c){if(!b)throw Error("Argument '"+(a||"?")+"' is "+(c||"required"));
+return b}function qa(b,a,c){c&&E(b)&&(b=b[b.length-1]);$a(H(b),a,"not a function, got "+(b&&typeof b=="object"?b.constructor.name||"Object":typeof b));return b}function lc(b){function a(a,b,e){return a[b]||(a[b]=e())}return a(a(b,"angular",Object),"module",function(){var b={};return function(d,e,g){e&&b.hasOwnProperty(d)&&(b[d]=null);return a(b,d,function(){function a(c,d,e){return function(){b[e||"push"]([c,d,arguments]);return k}}if(!e)throw Error("No module: "+d);var b=[],c=[],j=a("$injector",
+"invoke"),k={_invokeQueue:b,_runBlocks:c,requires:e,name:d,provider:a("$provide","provider"),factory:a("$provide","factory"),service:a("$provide","service"),value:a("$provide","value"),constant:a("$provide","constant","unshift"),filter:a("$filterProvider","register"),controller:a("$controllerProvider","register"),directive:a("$compileProvider","directive"),config:j,run:function(a){c.push(a);return this}};g&&j(g);return k})}})}function tb(b){return b.replace(mc,function(a,b,d,e){return e?d.toUpperCase():
+d}).replace(nc,"Moz$1")}function ab(b,a){function c(){var e;for(var b=[this],c=a,h,f,i,j,k,l;b.length;){h=b.shift();f=0;for(i=h.length;f<i;f++){j=u(h[f]);c?j.triggerHandler("$destroy"):c=!c;k=0;for(e=(l=j.children()).length,j=e;k<j;k++)b.push(ca(l[k]))}}return d.apply(this,arguments)}var d=ca.fn[b],d=d.$original||d;c.$original=d;ca.fn[b]=c}function K(b){if(b instanceof K)return b;if(!(this instanceof K)){if(B(b)&&b.charAt(0)!="<")throw Error("selectors not implemented");return new K(b)}if(B(b)){var a=
+T.createElement("div");a.innerHTML="<div>&#160;</div>"+b;a.removeChild(a.firstChild);bb(this,a.childNodes);this.remove()}else bb(this,b)}function cb(b){return b.cloneNode(!0)}function ra(b){ub(b);for(var a=0,b=b.childNodes||[];a<b.length;a++)ra(b[a])}function vb(b,a,c){var d=ba(b,"events");ba(b,"handle")&&(w(a)?m(d,function(a,c){db(b,c,a);delete d[c]}):w(c)?(db(b,a,d[a]),delete d[a]):Sa(d[a],c))}function ub(b){var a=b[Aa],c=Ba[a];c&&(c.handle&&(c.events.$destroy&&c.handle({},"$destroy"),vb(b)),delete Ba[a],
+b[Aa]=q)}function ba(b,a,c){var d=b[Aa],d=Ba[d||-1];if(y(c))d||(b[Aa]=d=++oc,d=Ba[d]={}),d[a]=c;else return d&&d[a]}function wb(b,a,c){var d=ba(b,"data"),e=y(c),g=!e&&y(a),h=g&&!L(a);!d&&!h&&ba(b,"data",d={});if(e)d[a]=c;else if(g)if(h)return d&&d[a];else v(d,a);else return d}function Ca(b,a){return(" "+b.className+" ").replace(/[\n\t]/g," ").indexOf(" "+a+" ")>-1}function xb(b,a){a&&m(a.split(" "),function(a){b.className=Q((" "+b.className+" ").replace(/[\n\t]/g," ").replace(" "+Q(a)+" "," "))})}
+function yb(b,a){a&&m(a.split(" "),function(a){if(!Ca(b,a))b.className=Q(b.className+" "+Q(a))})}function bb(b,a){if(a)for(var a=!a.nodeName&&y(a.length)&&!oa(a)?a:[a],c=0;c<a.length;c++)b.push(a[c])}function zb(b,a){return Da(b,"$"+(a||"ngController")+"Controller")}function Da(b,a,c){b=u(b);for(b[0].nodeType==9&&(b=b.find("html"));b.length;){if(c=b.data(a))return c;b=b.parent()}}function Ab(b,a){var c=Ea[a.toLowerCase()];return c&&Bb[b.nodeName]&&c}function pc(b,a){var c=function(c,e){if(!c.preventDefault)c.preventDefault=
+function(){c.returnValue=!1};if(!c.stopPropagation)c.stopPropagation=function(){c.cancelBubble=!0};if(!c.target)c.target=c.srcElement||T;if(w(c.defaultPrevented)){var g=c.preventDefault;c.preventDefault=function(){c.defaultPrevented=!0;g.call(c)};c.defaultPrevented=!1}c.isDefaultPrevented=function(){return c.defaultPrevented};m(a[e||c.type],function(a){a.call(b,c)});Z<=8?(c.preventDefault=null,c.stopPropagation=null,c.isDefaultPrevented=null):(delete c.preventDefault,delete c.stopPropagation,delete c.isDefaultPrevented)};
+c.elem=b;return c}function ga(b){var a=typeof b,c;if(a=="object"&&b!==null)if(typeof(c=b.$$hashKey)=="function")c=b.$$hashKey();else{if(c===q)c=b.$$hashKey=xa()}else c=b;return a+":"+c}function Fa(b){m(b,this.put,this)}function eb(){}function Cb(b){var a,c;if(typeof b=="function"){if(!(a=b.$inject))a=[],c=b.toString().replace(qc,""),c=c.match(rc),m(c[1].split(sc),function(b){b.replace(tc,function(b,c,d){a.push(d)})}),b.$inject=a}else E(b)?(c=b.length-1,qa(b[c],"fn"),a=b.slice(0,c)):qa(b,"fn",!0);
+return a}function sb(b){function a(a){return function(b,c){if(L(b))m(b,nb(a));else return a(b,c)}}function c(a,b){if(H(b)||E(b))b=l.instantiate(b);if(!b.$get)throw Error("Provider "+a+" must define $get factory method.");return k[a+f]=b}function d(a,b){return c(a,{$get:b})}function e(a){var b=[];m(a,function(a){if(!j.get(a))if(j.put(a,!0),B(a)){var c=sa(a);b=b.concat(e(c.requires)).concat(c._runBlocks);try{for(var d=c._invokeQueue,c=0,f=d.length;c<f;c++){var g=d[c],h=g[0]=="$injector"?l:l.get(g[0]);
+h[g[1]].apply(h,g[2])}}catch(p){throw p.message&&(p.message+=" from "+a),p;}}else if(H(a))try{b.push(l.invoke(a))}catch(i){throw i.message&&(i.message+=" from "+a),i;}else if(E(a))try{b.push(l.invoke(a))}catch(o){throw o.message&&(o.message+=" from "+String(a[a.length-1])),o;}else qa(a,"module")});return b}function g(a,b){function c(d){if(typeof d!=="string")throw Error("Service name expected");if(a.hasOwnProperty(d)){if(a[d]===h)throw Error("Circular dependency: "+i.join(" <- "));return a[d]}else try{return i.unshift(d),
+a[d]=h,a[d]=b(d)}finally{i.shift()}}function d(a,b,e){var f=[],j=Cb(a),g,h,i;h=0;for(g=j.length;h<g;h++)i=j[h],f.push(e&&e.hasOwnProperty(i)?e[i]:c(i));a.$inject||(a=a[g]);switch(b?-1:f.length){case 0:return a();case 1:return a(f[0]);case 2:return a(f[0],f[1]);case 3:return a(f[0],f[1],f[2]);case 4:return a(f[0],f[1],f[2],f[3]);case 5:return a(f[0],f[1],f[2],f[3],f[4]);case 6:return a(f[0],f[1],f[2],f[3],f[4],f[5]);case 7:return a(f[0],f[1],f[2],f[3],f[4],f[5],f[6]);case 8:return a(f[0],f[1],f[2],
+f[3],f[4],f[5],f[6],f[7]);case 9:return a(f[0],f[1],f[2],f[3],f[4],f[5],f[6],f[7],f[8]);case 10:return a(f[0],f[1],f[2],f[3],f[4],f[5],f[6],f[7],f[8],f[9]);default:return a.apply(b,f)}}return{invoke:d,instantiate:function(a,b){var c=function(){},e;c.prototype=(E(a)?a[a.length-1]:a).prototype;c=new c;e=d(a,c,b);return L(e)?e:c},get:c,annotate:Cb}}var h={},f="Provider",i=[],j=new Fa,k={$provide:{provider:a(c),factory:a(d),service:a(function(a,b){return d(a,["$injector",function(a){return a.instantiate(b)}])}),
+value:a(function(a,b){return d(a,I(b))}),constant:a(function(a,b){k[a]=b;n[a]=b}),decorator:function(a,b){var c=l.get(a+f),d=c.$get;c.$get=function(){var a=o.invoke(d,c);return o.invoke(b,null,{$delegate:a})}}}},l=g(k,function(){throw Error("Unknown provider: "+i.join(" <- "));}),n={},o=n.$injector=g(n,function(a){a=l.get(a+f);return o.invoke(a.$get,a)});m(e(b),function(a){o.invoke(a||C)});return o}function uc(){var b=!0;this.disableAutoScrolling=function(){b=!1};this.$get=["$window","$location",
+"$rootScope",function(a,c,d){function e(a){var b=null;m(a,function(a){!b&&z(a.nodeName)==="a"&&(b=a)});return b}function g(){var b=c.hash(),d;b?(d=h.getElementById(b))?d.scrollIntoView():(d=e(h.getElementsByName(b)))?d.scrollIntoView():b==="top"&&a.scrollTo(0,0):a.scrollTo(0,0)}var h=a.document;b&&d.$watch(function(){return c.hash()},function(){d.$evalAsync(g)});return g}]}function vc(b,a,c,d){function e(a){try{a.apply(null,ha.call(arguments,1))}finally{if(p--,p===0)for(;s.length;)try{s.pop()()}catch(b){c.error(b)}}}
+function g(a,b){(function V(){m(t,function(a){a()});x=b(V,a)})()}function h(){M!=f.url()&&(M=f.url(),m(N,function(a){a(f.url())}))}var f=this,i=a[0],j=b.location,k=b.history,l=b.setTimeout,n=b.clearTimeout,o={};f.isMock=!1;var p=0,s=[];f.$$completeOutstandingRequest=e;f.$$incOutstandingRequestCount=function(){p++};f.notifyWhenNoOutstandingRequests=function(a){m(t,function(a){a()});p===0?a():s.push(a)};var t=[],x;f.addPollFn=function(a){w(x)&&g(100,l);t.push(a);return a};var M=j.href,A=a.find("base");
+f.url=function(a,b){if(a){if(M!=a)return M=a,d.history?b?k.replaceState(null,"",a):(k.pushState(null,"",a),A.attr("href",A.attr("href"))):b?j.replace(a):j.href=a,f}else return j.href.replace(/%27/g,"'")};var N=[],J=!1;f.onUrlChange=function(a){J||(d.history&&u(b).bind("popstate",h),d.hashchange?u(b).bind("hashchange",h):f.addPollFn(h),J=!0);N.push(a);return a};f.baseHref=function(){var a=A.attr("href");return a?a.replace(/^https?\:\/\/[^\/]*/,""):""};var r={},$="",R=f.baseHref();f.cookies=function(a,
+b){var d,e,f,j;if(a)if(b===q)i.cookie=escape(a)+"=;path="+R+";expires=Thu, 01 Jan 1970 00:00:00 GMT";else{if(B(b))d=(i.cookie=escape(a)+"="+escape(b)+";path="+R).length+1,d>4096&&c.warn("Cookie '"+a+"' possibly not set or overflowed because it was too large ("+d+" > 4096 bytes)!")}else{if(i.cookie!==$){$=i.cookie;d=$.split("; ");r={};for(f=0;f<d.length;f++)e=d[f],j=e.indexOf("="),j>0&&(a=unescape(e.substring(0,j)),r[a]===q&&(r[a]=unescape(e.substring(j+1))))}return r}};f.defer=function(a,b){var c;
+p++;c=l(function(){delete o[c];e(a)},b||0);o[c]=!0;return c};f.defer.cancel=function(a){return o[a]?(delete o[a],n(a),e(C),!0):!1}}function wc(){this.$get=["$window","$log","$sniffer","$document",function(b,a,c,d){return new vc(b,d,a,c)}]}function xc(){this.$get=function(){function b(b,d){function e(a){if(a!=l){if(n){if(n==a)n=a.n}else n=a;g(a.n,a.p);g(a,l);l=a;l.n=null}}function g(a,b){if(a!=b){if(a)a.p=b;if(b)b.n=a}}if(b in a)throw Error("cacheId "+b+" taken");var h=0,f=v({},d,{id:b}),i={},j=d&&
+d.capacity||Number.MAX_VALUE,k={},l=null,n=null;return a[b]={put:function(a,b){var c=k[a]||(k[a]={key:a});e(c);w(b)||(a in i||h++,i[a]=b,h>j&&this.remove(n.key))},get:function(a){var b=k[a];if(b)return e(b),i[a]},remove:function(a){var b=k[a];if(b){if(b==l)l=b.p;if(b==n)n=b.n;g(b.n,b.p);delete k[a];delete i[a];h--}},removeAll:function(){i={};h=0;k={};l=n=null},destroy:function(){k=f=i=null;delete a[b]},info:function(){return v({},f,{size:h})}}}var a={};b.info=function(){var b={};m(a,function(a,e){b[e]=
+a.info()});return b};b.get=function(b){return a[b]};return b}}function yc(){this.$get=["$cacheFactory",function(b){return b("templates")}]}function Db(b){var a={},c="Directive",d=/^\s*directive\:\s*([\d\w\-_]+)\s+(.*)$/,e=/(([\d\w\-_]+)(?:\:([^;]+))?;?)/,g="Template must have exactly one root element. was: ",h=/^\s*(https?|ftp|mailto|file):/;this.directive=function i(d,e){B(d)?($a(e,"directive"),a.hasOwnProperty(d)||(a[d]=[],b.factory(d+c,["$injector","$exceptionHandler",function(b,c){var e=[];m(a[d],
+function(a){try{var g=b.invoke(a);if(H(g))g={compile:I(g)};else if(!g.compile&&g.link)g.compile=I(g.link);g.priority=g.priority||0;g.name=g.name||d;g.require=g.require||g.controller&&g.name;g.restrict=g.restrict||"A";e.push(g)}catch(h){c(h)}});return e}])),a[d].push(e)):m(d,nb(i));return this};this.urlSanitizationWhitelist=function(a){return y(a)?(h=a,this):h};this.$get=["$injector","$interpolate","$exceptionHandler","$http","$templateCache","$parse","$controller","$rootScope","$document",function(b,
+j,k,l,n,o,p,s,t){function x(a,b,c){a instanceof u||(a=u(a));m(a,function(b,c){b.nodeType==3&&b.nodeValue.match(/\S+/)&&(a[c]=u(b).wrap("<span></span>").parent()[0])});var d=A(a,b,a,c);return function(b,c){$a(b,"scope");for(var e=c?ua.clone.call(a):a,j=0,g=e.length;j<g;j++){var h=e[j];(h.nodeType==1||h.nodeType==9)&&e.eq(j).data("$scope",b)}M(e,"ng-scope");c&&c(e,b);d&&d(b,e,e);return e}}function M(a,b){try{a.addClass(b)}catch(c){}}function A(a,b,c,d){function e(a,c,d,g){var h,i,k,p,o,l,n,t=[];o=0;
+for(l=c.length;o<l;o++)t.push(c[o]);n=o=0;for(l=j.length;o<l;n++)i=t[n],c=j[o++],h=j[o++],c?(c.scope?(k=a.$new(L(c.scope)),u(i).data("$scope",k)):k=a,(p=c.transclude)||!g&&b?c(h,k,i,d,function(b){return function(c){var d=a.$new();d.$$transcluded=!0;return b(d,c).bind("$destroy",Ta(d,d.$destroy))}}(p||b)):c(h,k,i,q,g)):h&&h(a,i.childNodes,q,g)}for(var j=[],g,h,i,k=0;k<a.length;k++)h=new ia,g=N(a[k],[],h,d),h=(g=g.length?J(g,a[k],h,b,c):null)&&g.terminal||!a[k].childNodes||!a[k].childNodes.length?null:
+A(a[k].childNodes,g?g.transclude:b),j.push(g),j.push(h),i=i||g||h;return i?e:null}function N(a,b,c,g){var j=c.$attr,h;switch(a.nodeType){case 1:r(b,ea(fb(a).toLowerCase()),"E",g);var i,k,o;h=a.attributes;for(var p=0,l=h&&h.length;p<l;p++)if(i=h[p],i.specified)k=i.name,o=ea(k.toLowerCase()),j[o]=k,c[o]=i=Q(Z&&k=="href"?decodeURIComponent(a.getAttribute(k,2)):i.value),Ab(a,o)&&(c[o]=!0),V(a,b,i,o),r(b,o,"A",g);a=a.className;if(B(a)&&a!=="")for(;h=e.exec(a);)o=ea(h[2]),r(b,o,"C",g)&&(c[o]=Q(h[3])),a=
+a.substr(h.index+h[0].length);break;case 3:y(b,a.nodeValue);break;case 8:try{if(h=d.exec(a.nodeValue))o=ea(h[1]),r(b,o,"M",g)&&(c[o]=Q(h[2]))}catch(n){}}b.sort(F);return b}function J(a,b,c,d,e){function j(a,b){if(a)a.require=r.require,n.push(a);if(b)b.require=r.require,t.push(b)}function h(a,b){var c,d="data",e=!1;if(B(a)){for(;(c=a.charAt(0))=="^"||c=="?";)a=a.substr(1),c=="^"&&(d="inheritedData"),e=e||c=="?";c=b[d]("$"+a+"Controller");if(!c&&!e)throw Error("No controller: "+a);}else E(a)&&(c=[],
+m(a,function(a){c.push(h(a,b))}));return c}function i(a,d,e,g,j){var l,s,r,D,M;l=b===e?c:hc(c,new ia(u(e),c.$attr));s=l.$$element;if(J){var zc=/^\s*([@=&])\s*(\w*)\s*$/,x=d.$parent||d;m(J.scope,function(a,b){var c=a.match(zc)||[],e=c[2]||b,c=c[1],g,j,h;d.$$isolateBindings[b]=c+e;switch(c){case "@":l.$observe(e,function(a){d[b]=a});l.$$observers[e].$$scope=x;break;case "=":j=o(l[e]);h=j.assign||function(){g=d[b]=j(x);throw Error(Eb+l[e]+" (directive: "+J.name+")");};g=d[b]=j(x);d.$watch(function(){var a=
+j(x);a!==d[b]&&(a!==g?g=d[b]=a:h(x,a=g=d[b]));return a});break;case "&":j=o(l[e]);d[b]=function(a){return j(x,a)};break;default:throw Error("Invalid isolate scope definition for directive "+J.name+": "+a);}})}y&&m(y,function(a){var b={$scope:d,$element:s,$attrs:l,$transclude:j};M=a.controller;M=="@"&&(M=l[a.name]);s.data("$"+a.name+"Controller",p(M,b))});g=0;for(r=n.length;g<r;g++)try{D=n[g],D(d,s,l,D.require&&h(D.require,s))}catch(A){k(A,pa(s))}a&&a(d,e.childNodes,q,j);g=0;for(r=t.length;g<r;g++)try{D=
+t[g],D(d,s,l,D.require&&h(D.require,s))}catch(Ac){k(Ac,pa(s))}}for(var l=-Number.MAX_VALUE,n=[],t=[],s=null,J=null,A=null,D=c.$$element=u(b),r,F,W,ja,V=d,y,w,Y,v=0,z=a.length;v<z;v++){r=a[v];W=q;if(l>r.priority)break;if(Y=r.scope)ta("isolated scope",J,r,D),L(Y)&&(M(D,"ng-isolate-scope"),J=r),M(D,"ng-scope"),s=s||r;F=r.name;if(Y=r.controller)y=y||{},ta("'"+F+"' controller",y[F],r,D),y[F]=r;if(Y=r.transclude)ta("transclusion",ja,r,D),ja=r,l=r.priority,Y=="element"?(W=u(b),D=c.$$element=u(T.createComment(" "+
+F+": "+c[F]+" ")),b=D[0],C(e,u(W[0]),b),V=x(W,d,l)):(W=u(cb(b)).contents(),D.html(""),V=x(W,d));if(Y=r.template)if(ta("template",A,r,D),A=r,Y=Fb(Y),r.replace){W=u("<div>"+Q(Y)+"</div>").contents();b=W[0];if(W.length!=1||b.nodeType!==1)throw Error(g+Y);C(e,D,b);F={$attr:{}};a=a.concat(N(b,a.splice(v+1,a.length-(v+1)),F));$(c,F);z=a.length}else D.html(Y);if(r.templateUrl)ta("template",A,r,D),A=r,i=R(a.splice(v,a.length-v),i,D,c,e,r.replace,V),z=a.length;else if(r.compile)try{w=r.compile(D,c,V),H(w)?
+j(null,w):w&&j(w.pre,w.post)}catch(G){k(G,pa(D))}if(r.terminal)i.terminal=!0,l=Math.max(l,r.priority)}i.scope=s&&s.scope;i.transclude=ja&&V;return i}function r(d,e,g,j){var h=!1;if(a.hasOwnProperty(e))for(var o,e=b.get(e+c),l=0,p=e.length;l<p;l++)try{if(o=e[l],(j===q||j>o.priority)&&o.restrict.indexOf(g)!=-1)d.push(o),h=!0}catch(n){k(n)}return h}function $(a,b){var c=b.$attr,d=a.$attr,e=a.$$element;m(a,function(d,e){e.charAt(0)!="$"&&(b[e]&&(d+=(e==="style"?";":" ")+b[e]),a.$set(e,d,!0,c[e]))});m(b,
+function(b,g){g=="class"?(M(e,b),a["class"]=(a["class"]?a["class"]+" ":"")+b):g=="style"?e.attr("style",e.attr("style")+";"+b):g.charAt(0)!="$"&&!a.hasOwnProperty(g)&&(a[g]=b,d[g]=c[g])})}function R(a,b,c,d,e,j,h){var i=[],k,o,p=c[0],t=a.shift(),s=v({},t,{controller:null,templateUrl:null,transclude:null,scope:null});c.html("");l.get(t.templateUrl,{cache:n}).success(function(l){var n,t,l=Fb(l);if(j){t=u("<div>"+Q(l)+"</div>").contents();n=t[0];if(t.length!=1||n.nodeType!==1)throw Error(g+l);l={$attr:{}};
+C(e,c,n);N(n,a,l);$(d,l)}else n=p,c.html(l);a.unshift(s);k=J(a,n,d,h);for(o=A(c[0].childNodes,h);i.length;){var r=i.pop(),l=i.pop();t=i.pop();var ia=i.pop(),D=n;t!==p&&(D=cb(n),C(l,u(t),D));k(function(){b(o,ia,D,e,r)},ia,D,e,r)}i=null}).error(function(a,b,c,d){throw Error("Failed to load template: "+d.url);});return function(a,c,d,e,g){i?(i.push(c),i.push(d),i.push(e),i.push(g)):k(function(){b(o,c,d,e,g)},c,d,e,g)}}function F(a,b){return b.priority-a.priority}function ta(a,b,c,d){if(b)throw Error("Multiple directives ["+
+b.name+", "+c.name+"] asking for "+a+" on: "+pa(d));}function y(a,b){var c=j(b,!0);c&&a.push({priority:0,compile:I(function(a,b){var d=b.parent(),e=d.data("$binding")||[];e.push(c);M(d.data("$binding",e),"ng-binding");a.$watch(c,function(a){b[0].nodeValue=a})})})}function V(a,b,c,d){var e=j(c,!0);e&&b.push({priority:100,compile:I(function(a,b,c){b=c.$$observers||(c.$$observers={});d==="class"&&(e=j(c[d],!0));c[d]=q;(b[d]||(b[d]=[])).$$inter=!0;(c.$$observers&&c.$$observers[d].$$scope||a).$watch(e,
+function(a){c.$set(d,a)})})})}function C(a,b,c){var d=b[0],e=d.parentNode,g,j;if(a){g=0;for(j=a.length;g<j;g++)if(a[g]==d){a[g]=c;break}}e&&e.replaceChild(c,d);c[u.expando]=d[u.expando];b[0]=c}var ia=function(a,b){this.$$element=a;this.$attr=b||{}};ia.prototype={$normalize:ea,$set:function(a,b,c,d){var e=Ab(this.$$element[0],a),g=this.$$observers;e&&(this.$$element.prop(a,b),d=e);this[a]=b;d?this.$attr[a]=d:(d=this.$attr[a])||(this.$attr[a]=d=Za(a,"-"));if(fb(this.$$element[0])==="A"&&a==="href")D.setAttribute("href",
+b),e=D.href,e.match(h)||(this[a]=b="unsafe:"+e);c!==!1&&(b===null||b===q?this.$$element.removeAttr(d):this.$$element.attr(d,b));g&&m(g[a],function(a){try{a(b)}catch(c){k(c)}})},$observe:function(a,b){var c=this,d=c.$$observers||(c.$$observers={}),e=d[a]||(d[a]=[]);e.push(b);s.$evalAsync(function(){e.$$inter||b(c[a])});return b}};var D=t[0].createElement("a"),W=j.startSymbol(),ja=j.endSymbol(),Fb=W=="{{"||ja=="}}"?ma:function(a){return a.replace(/\{\{/g,W).replace(/}}/g,ja)};return x}]}function ea(b){return tb(b.replace(Bc,
+""))}function Cc(){var b={};this.register=function(a,c){L(a)?v(b,a):b[a]=c};this.$get=["$injector","$window",function(a,c){return function(d,e){if(B(d)){var g=d,d=b.hasOwnProperty(g)?b[g]:gb(e.$scope,g,!0)||gb(c,g,!0);qa(d,g,!0)}return a.instantiate(d,e)}}]}function Dc(){this.$get=["$window",function(b){return u(b.document)}]}function Ec(){this.$get=["$log",function(b){return function(a,c){b.error.apply(b,arguments)}}]}function Fc(){var b="{{",a="}}";this.startSymbol=function(a){return a?(b=a,this):
+b};this.endSymbol=function(b){return b?(a=b,this):a};this.$get=["$parse",function(c){function d(d,f){for(var i,j,k=0,l=[],n=d.length,o=!1,p=[];k<n;)(i=d.indexOf(b,k))!=-1&&(j=d.indexOf(a,i+e))!=-1?(k!=i&&l.push(d.substring(k,i)),l.push(k=c(o=d.substring(i+e,j))),k.exp=o,k=j+g,o=!0):(k!=n&&l.push(d.substring(k)),k=n);if(!(n=l.length))l.push(""),n=1;if(!f||o)return p.length=n,k=function(a){for(var b=0,c=n,d;b<c;b++){if(typeof(d=l[b])=="function")d=d(a),d==null||d==q?d="":typeof d!="string"&&(d=da(d));
+p[b]=d}return p.join("")},k.exp=d,k.parts=l,k}var e=b.length,g=a.length;d.startSymbol=function(){return b};d.endSymbol=function(){return a};return d}]}function Gb(b){for(var b=b.split("/"),a=b.length;a--;)b[a]=Xa(b[a]);return b.join("/")}function va(b,a){var c=Hb.exec(b),c={protocol:c[1],host:c[3],port:G(c[5])||Ib[c[1]]||null,path:c[6]||"/",search:c[8],hash:c[10]};if(a)a.$$protocol=c.protocol,a.$$host=c.host,a.$$port=c.port;return c}function ka(b,a,c){return b+"://"+a+(c==Ib[b]?"":":"+c)}function Gc(b,
+a,c){var d=va(b);return decodeURIComponent(d.path)!=a||w(d.hash)||d.hash.indexOf(c)!==0?b:ka(d.protocol,d.host,d.port)+a.substr(0,a.lastIndexOf("/"))+d.hash.substr(c.length)}function Hc(b,a,c){var d=va(b);if(decodeURIComponent(d.path)==a&&!w(d.hash)&&d.hash.indexOf(c)===0)return b;else{var e=d.search&&"?"+d.search||"",g=d.hash&&"#"+d.hash||"",h=a.substr(0,a.lastIndexOf("/")),f=d.path.substr(h.length);if(d.path.indexOf(h)!==0)throw Error('Invalid url "'+b+'", missing path prefix "'+h+'" !');return ka(d.protocol,
+d.host,d.port)+a+"#"+c+f+e+g}}function hb(b,a,c){a=a||"";this.$$parse=function(b){var c=va(b,this);if(c.path.indexOf(a)!==0)throw Error('Invalid url "'+b+'", missing path prefix "'+a+'" !');this.$$path=decodeURIComponent(c.path.substr(a.length));this.$$search=Va(c.search);this.$$hash=c.hash&&decodeURIComponent(c.hash)||"";this.$$compose()};this.$$compose=function(){var b=qb(this.$$search),c=this.$$hash?"#"+Xa(this.$$hash):"";this.$$url=Gb(this.$$path)+(b?"?"+b:"")+c;this.$$absUrl=ka(this.$$protocol,
+this.$$host,this.$$port)+a+this.$$url};this.$$rewriteAppUrl=function(a){if(a.indexOf(c)==0)return a};this.$$parse(b)}function Ga(b,a,c){var d;this.$$parse=function(b){var c=va(b,this);if(c.hash&&c.hash.indexOf(a)!==0)throw Error('Invalid url "'+b+'", missing hash prefix "'+a+'" !');d=c.path+(c.search?"?"+c.search:"");c=Ic.exec((c.hash||"").substr(a.length));this.$$path=c[1]?(c[1].charAt(0)=="/"?"":"/")+decodeURIComponent(c[1]):"";this.$$search=Va(c[3]);this.$$hash=c[5]&&decodeURIComponent(c[5])||
+"";this.$$compose()};this.$$compose=function(){var b=qb(this.$$search),c=this.$$hash?"#"+Xa(this.$$hash):"";this.$$url=Gb(this.$$path)+(b?"?"+b:"")+c;this.$$absUrl=ka(this.$$protocol,this.$$host,this.$$port)+d+(this.$$url?"#"+a+this.$$url:"")};this.$$rewriteAppUrl=function(a){if(a.indexOf(c)==0)return a};this.$$parse(b)}function Jb(b,a,c,d){Ga.apply(this,arguments);this.$$rewriteAppUrl=function(b){if(b.indexOf(c)==0)return c+d+"#"+a+b.substr(c.length)}}function Ha(b){return function(){return this[b]}}
+function Kb(b,a){return function(c){if(w(c))return this[b];this[b]=a(c);this.$$compose();return this}}function Jc(){var b="",a=!1;this.hashPrefix=function(a){return y(a)?(b=a,this):b};this.html5Mode=function(b){return y(b)?(a=b,this):a};this.$get=["$rootScope","$browser","$sniffer","$rootElement",function(c,d,e,g){function h(a){c.$broadcast("$locationChangeSuccess",f.absUrl(),a)}var f,i,j,k=d.url(),l=va(k);a?(i=d.baseHref()||"/",j=i.substr(0,i.lastIndexOf("/")),l=ka(l.protocol,l.host,l.port)+j+"/",
+f=e.history?new hb(Gc(k,i,b),j,l):new Jb(Hc(k,i,b),b,l,i.substr(j.length+1))):(l=ka(l.protocol,l.host,l.port)+(l.path||"")+(l.search?"?"+l.search:"")+"#"+b+"/",f=new Ga(k,b,l));g.bind("click",function(a){if(!a.ctrlKey&&!(a.metaKey||a.which==2)){for(var b=u(a.target);z(b[0].nodeName)!=="a";)if(b[0]===g[0]||!(b=b.parent())[0])return;var d=b.prop("href"),e=f.$$rewriteAppUrl(d);d&&!b.attr("target")&&e&&(f.$$parse(e),c.$apply(),a.preventDefault(),P.angular["ff-684208-preventDefault"]=!0)}});f.absUrl()!=
+k&&d.url(f.absUrl(),!0);d.onUrlChange(function(a){f.absUrl()!=a&&(c.$broadcast("$locationChangeStart",a,f.absUrl()).defaultPrevented?d.url(f.absUrl()):(c.$evalAsync(function(){var b=f.absUrl();f.$$parse(a);h(b)}),c.$$phase||c.$digest()))});var n=0;c.$watch(function(){var a=d.url(),b=f.$$replace;if(!n||a!=f.absUrl())n++,c.$evalAsync(function(){c.$broadcast("$locationChangeStart",f.absUrl(),a).defaultPrevented?f.$$parse(a):(d.url(f.absUrl(),b),h(a))});f.$$replace=!1;return n});return f}]}function Kc(){this.$get=
+["$window",function(b){function a(a){a instanceof Error&&(a.stack?a=a.message&&a.stack.indexOf(a.message)===-1?"Error: "+a.message+"\n"+a.stack:a.stack:a.sourceURL&&(a=a.message+"\n"+a.sourceURL+":"+a.line));return a}function c(c){var e=b.console||{},g=e[c]||e.log||C;return g.apply?function(){var b=[];m(arguments,function(c){b.push(a(c))});return g.apply(e,b)}:function(a,b){g(a,b)}}return{log:c("log"),warn:c("warn"),info:c("info"),error:c("error")}}]}function Lc(b,a){function c(a){return a.indexOf(t)!=
+-1}function d(){return p+1<b.length?b.charAt(p+1):!1}function e(a){return"0"<=a&&a<="9"}function g(a){return a==" "||a=="\r"||a=="\t"||a=="\n"||a=="\u000b"||a=="\u00a0"}function h(a){return"a"<=a&&a<="z"||"A"<=a&&a<="Z"||"_"==a||a=="$"}function f(a){return a=="-"||a=="+"||e(a)}function i(a,c,d){d=d||p;throw Error("Lexer Error: "+a+" at column"+(y(c)?"s "+c+"-"+p+" ["+b.substring(c,d)+"]":" "+d)+" in expression ["+b+"].");}function j(){for(var a="",c=p;p<b.length;){var g=z(b.charAt(p));if(g=="."||
+e(g))a+=g;else{var j=d();if(g=="e"&&f(j))a+=g;else if(f(g)&&j&&e(j)&&a.charAt(a.length-1)=="e")a+=g;else if(f(g)&&(!j||!e(j))&&a.charAt(a.length-1)=="e")i("Invalid exponent");else break}p++}a*=1;n.push({index:c,text:a,json:!0,fn:function(){return a}})}function k(){for(var c="",d=p,f,j,i,k;p<b.length;){k=b.charAt(p);if(k=="."||h(k)||e(k))k=="."&&(f=p),c+=k;else break;p++}if(f)for(j=p;j<b.length;){k=b.charAt(j);if(k=="("){i=c.substr(f-d+1);c=c.substr(0,f-d);p=j;break}if(g(k))j++;else break}d={index:d,
+text:c};if(Ia.hasOwnProperty(c))d.fn=d.json=Ia[c];else{var l=Lb(c,a);d.fn=v(function(a,b){return l(a,b)},{assign:function(a,b){return Mb(a,c,b)}})}n.push(d);i&&(n.push({index:f,text:".",json:!1}),n.push({index:f+1,text:i,json:!1}))}function l(a){var c=p;p++;for(var d="",e=a,f=!1;p<b.length;){var g=b.charAt(p);e+=g;if(f)g=="u"?(g=b.substring(p+1,p+5),g.match(/[\da-f]{4}/i)||i("Invalid unicode escape [\\u"+g+"]"),p+=4,d+=String.fromCharCode(parseInt(g,16))):(f=Mc[g],d+=f?f:g),f=!1;else if(g=="\\")f=
+!0;else if(g==a){p++;n.push({index:c,text:e,string:d,json:!0,fn:function(){return d}});return}else d+=g;p++}i("Unterminated quote",c)}for(var n=[],o,p=0,s=[],t,x=":";p<b.length;){t=b.charAt(p);if(c("\"'"))l(t);else if(e(t)||c(".")&&e(d()))j();else if(h(t)){if(k(),"{,".indexOf(x)!=-1&&s[0]=="{"&&(o=n[n.length-1]))o.json=o.text.indexOf(".")==-1}else if(c("(){}[].,;:"))n.push({index:p,text:t,json:":[,".indexOf(x)!=-1&&c("{[")||c("}]:,")}),c("{[")&&s.unshift(t),c("}]")&&s.shift(),p++;else if(g(t)){p++;
+continue}else{var m=t+d(),A=Ia[t],N=Ia[m];N?(n.push({index:p,text:m,fn:N}),p+=2):A?(n.push({index:p,text:t,fn:A,json:"[,:".indexOf(x)!=-1&&c("+-")}),p+=1):i("Unexpected next character ",p,p+1)}x=t}return n}function Nc(b,a,c,d){function e(a,c){throw Error("Syntax Error: Token '"+c.text+"' "+a+" at column "+(c.index+1)+" of the expression ["+b+"] starting at ["+b.substring(c.index)+"].");}function g(){if(R.length===0)throw Error("Unexpected end of expression: "+b);return R[0]}function h(a,b,c,d){if(R.length>
+0){var e=R[0],f=e.text;if(f==a||f==b||f==c||f==d||!a&&!b&&!c&&!d)return e}return!1}function f(b,c,d,f){return(b=h(b,c,d,f))?(a&&!b.json&&e("is not valid json",b),R.shift(),b):!1}function i(a){f(a)||e("is unexpected, expecting ["+a+"]",h())}function j(a,b){return function(c,d){return a(c,d,b)}}function k(a,b,c){return function(d,e){return b(d,e,a,c)}}function l(){for(var a=[];;)if(R.length>0&&!h("}",")",";","]")&&a.push(w()),!f(";"))return a.length==1?a[0]:function(b,c){for(var d,e=0;e<a.length;e++){var f=
+a[e];f&&(d=f(b,c))}return d}}function n(){for(var a=f(),b=c(a.text),d=[];;)if(a=f(":"))d.push(F());else{var e=function(a,c,e){for(var e=[e],f=0;f<d.length;f++)e.push(d[f](a,c));return b.apply(a,e)};return function(){return e}}}function o(){for(var a=p(),b;;)if(b=f("||"))a=k(a,b.fn,p());else return a}function p(){var a=s(),b;if(b=f("&&"))a=k(a,b.fn,p());return a}function s(){var a=t(),b;if(b=f("==","!="))a=k(a,b.fn,s());return a}function t(){var a;a=x();for(var b;b=f("+","-");)a=k(a,b.fn,x());if(b=
+f("<",">","<=",">="))a=k(a,b.fn,t());return a}function x(){for(var a=m(),b;b=f("*","/","%");)a=k(a,b.fn,m());return a}function m(){var a;return f("+")?A():(a=f("-"))?k(r,a.fn,m()):(a=f("!"))?j(a.fn,m()):A()}function A(){var a;if(f("("))a=w(),i(")");else if(f("["))a=N();else if(f("{"))a=J();else{var b=f();(a=b.fn)||e("not a primary expression",b)}for(var c;b=f("(","[",".");)b.text==="("?(a=y(a,c),c=null):b.text==="["?(c=a,a=V(a)):b.text==="."?(c=a,a=u(a)):e("IMPOSSIBLE");return a}function N(){var a=
+[];if(g().text!="]"){do a.push(F());while(f(","))}i("]");return function(b,c){for(var d=[],e=0;e<a.length;e++)d.push(a[e](b,c));return d}}function J(){var a=[];if(g().text!="}"){do{var b=f(),b=b.string||b.text;i(":");var c=F();a.push({key:b,value:c})}while(f(","))}i("}");return function(b,c){for(var d={},e=0;e<a.length;e++){var f=a[e];d[f.key]=f.value(b,c)}return d}}var r=I(0),$,R=Lc(b,d),F=function(){var a=o(),c,d;return(d=f("="))?(a.assign||e("implies assignment but ["+b.substring(0,d.index)+"] can not be assigned to",
+d),c=o(),function(b,d){return a.assign(b,c(b,d),d)}):a},y=function(a,b){var c=[];if(g().text!=")"){do c.push(F());while(f(","))}i(")");return function(d,e){for(var f=[],g=b?b(d,e):d,j=0;j<c.length;j++)f.push(c[j](d,e));j=a(d,e,g)||C;return j.apply?j.apply(g,f):j(f[0],f[1],f[2],f[3],f[4])}},u=function(a){var b=f().text,c=Lb(b,d);return v(function(b,d,e){return c(e||a(b,d),d)},{assign:function(c,d,e){return Mb(a(c,e),b,d)}})},V=function(a){var b=F();i("]");return v(function(c,d){var e=a(c,d),f=b(c,
+d),g;if(!e)return q;if((e=e[f])&&e.then){g=e;if(!("$$v"in e))g.$$v=q,g.then(function(a){g.$$v=a});e=e.$$v}return e},{assign:function(c,d,e){return a(c,e)[b(c,e)]=d}})},w=function(){for(var a=F(),b;;)if(b=f("|"))a=k(a,b.fn,n());else return a};a?(F=o,y=u=V=w=function(){e("is not valid json",{text:b,index:0})},$=A()):$=l();R.length!==0&&e("is an unexpected token",R[0]);return $}function Mb(b,a,c){for(var a=a.split("."),d=0;a.length>1;d++){var e=a.shift(),g=b[e];g||(g={},b[e]=g);b=g}return b[a.shift()]=
+c}function gb(b,a,c){if(!a)return b;for(var a=a.split("."),d,e=b,g=a.length,h=0;h<g;h++)d=a[h],b&&(b=(e=b)[d]);return!c&&H(b)?Ta(e,b):b}function Nb(b,a,c,d,e){return function(g,h){var f=h&&h.hasOwnProperty(b)?h:g,i;if(f===null||f===q)return f;if((f=f[b])&&f.then){if(!("$$v"in f))i=f,i.$$v=q,i.then(function(a){i.$$v=a});f=f.$$v}if(!a||f===null||f===q)return f;if((f=f[a])&&f.then){if(!("$$v"in f))i=f,i.$$v=q,i.then(function(a){i.$$v=a});f=f.$$v}if(!c||f===null||f===q)return f;if((f=f[c])&&f.then){if(!("$$v"in
+f))i=f,i.$$v=q,i.then(function(a){i.$$v=a});f=f.$$v}if(!d||f===null||f===q)return f;if((f=f[d])&&f.then){if(!("$$v"in f))i=f,i.$$v=q,i.then(function(a){i.$$v=a});f=f.$$v}if(!e||f===null||f===q)return f;if((f=f[e])&&f.then){if(!("$$v"in f))i=f,i.$$v=q,i.then(function(a){i.$$v=a});f=f.$$v}return f}}function Lb(b,a){if(ib.hasOwnProperty(b))return ib[b];var c=b.split("."),d=c.length,e;if(a)e=d<6?Nb(c[0],c[1],c[2],c[3],c[4]):function(a,b){var e=0,g;do g=Nb(c[e++],c[e++],c[e++],c[e++],c[e++])(a,b),b=q,
+a=g;while(e<d);return g};else{var g="var l, fn, p;\n";m(c,function(a,b){g+="if(s === null || s === undefined) return s;\nl=s;\ns="+(b?"s":'((k&&k.hasOwnProperty("'+a+'"))?k:s)')+'["'+a+'"];\nif (s && s.then) {\n if (!("$$v" in s)) {\n p=s;\n p.$$v = undefined;\n p.then(function(v) {p.$$v=v;});\n}\n s=s.$$v\n}\n'});g+="return s;";e=Function("s","k",g);e.toString=function(){return g}}return ib[b]=e}function Oc(){var b={};this.$get=["$filter","$sniffer",function(a,c){return function(d){switch(typeof d){case "string":return b.hasOwnProperty(d)?
+b[d]:b[d]=Nc(d,!1,a,c.csp);case "function":return d;default:return C}}}]}function Pc(){this.$get=["$rootScope","$exceptionHandler",function(b,a){return Qc(function(a){b.$evalAsync(a)},a)}]}function Qc(b,a){function c(a){return a}function d(a){return h(a)}var e=function(){var f=[],i,j;return j={resolve:function(a){if(f){var c=f;f=q;i=g(a);c.length&&b(function(){for(var a,b=0,d=c.length;b<d;b++)a=c[b],i.then(a[0],a[1])})}},reject:function(a){j.resolve(h(a))},promise:{then:function(b,g){var j=e(),h=
+function(d){try{j.resolve((b||c)(d))}catch(e){a(e),j.reject(e)}},p=function(b){try{j.resolve((g||d)(b))}catch(c){a(c),j.reject(c)}};f?f.push([h,p]):i.then(h,p);return j.promise}}}},g=function(a){return a&&a.then?a:{then:function(c){var d=e();b(function(){d.resolve(c(a))});return d.promise}}},h=function(a){return{then:function(c,g){var h=e();b(function(){h.resolve((g||d)(a))});return h.promise}}};return{defer:e,reject:h,when:function(f,i,j){var k=e(),l,n=function(b){try{return(i||c)(b)}catch(d){return a(d),
+h(d)}},o=function(b){try{return(j||d)(b)}catch(c){return a(c),h(c)}};b(function(){g(f).then(function(a){l||(l=!0,k.resolve(g(a).then(n,o)))},function(a){l||(l=!0,k.resolve(o(a)))})});return k.promise},all:function(a){var b=e(),c=a.length,d=[];c?m(a,function(a,e){g(a).then(function(a){e in d||(d[e]=a,--c||b.resolve(d))},function(a){e in d||b.reject(a)})}):b.resolve(d);return b.promise}}}function Rc(){var b={};this.when=function(a,c){b[a]=v({reloadOnSearch:!0},c);if(a){var d=a[a.length-1]=="/"?a.substr(0,
+a.length-1):a+"/";b[d]={redirectTo:a}}return this};this.otherwise=function(a){this.when(null,a);return this};this.$get=["$rootScope","$location","$routeParams","$q","$injector","$http","$templateCache",function(a,c,d,e,g,h,f){function i(a,b){for(var b="^"+b.replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&")+"$",c="",d=[],e={},f=/:(\w+)/g,g,j=0;(g=f.exec(b))!==null;)c+=b.slice(j,g.index),c+="([^\\/]*)",d.push(g[1]),j=f.lastIndex;c+=b.substr(j);var h=a.match(RegExp(c));h&&m(d,function(a,b){e[a]=h[b+1]});return h?
+e:null}function j(){var b=k(),j=o.current;if(b&&j&&b.$$route===j.$$route&&fa(b.pathParams,j.pathParams)&&!b.reloadOnSearch&&!n)j.params=b.params,U(j.params,d),a.$broadcast("$routeUpdate",j);else if(b||j)n=!1,a.$broadcast("$routeChangeStart",b,j),(o.current=b)&&b.redirectTo&&(B(b.redirectTo)?c.path(l(b.redirectTo,b.params)).search(b.params).replace():c.url(b.redirectTo(b.pathParams,c.path(),c.search())).replace()),e.when(b).then(function(){if(b){var a=[],c=[],d;m(b.resolve||{},function(b,d){a.push(d);
+c.push(B(b)?g.get(b):g.invoke(b))});if(!y(d=b.template))if(y(d=b.templateUrl))d=h.get(d,{cache:f}).then(function(a){return a.data});y(d)&&(a.push("$template"),c.push(d));return e.all(c).then(function(b){var c={};m(b,function(b,d){c[a[d]]=b});return c})}}).then(function(c){if(b==o.current){if(b)b.locals=c,U(b.params,d);a.$broadcast("$routeChangeSuccess",b,j)}},function(c){b==o.current&&a.$broadcast("$routeChangeError",b,j,c)})}function k(){var a,d;m(b,function(b,e){if(!d&&(a=i(c.path(),e)))d=ya(b,
+{params:v({},c.search(),a),pathParams:a}),d.$$route=b});return d||b[null]&&ya(b[null],{params:{},pathParams:{}})}function l(a,b){var c=[];m((a||"").split(":"),function(a,d){if(d==0)c.push(a);else{var e=a.match(/(\w+)(.*)/),f=e[1];c.push(b[f]);c.push(e[2]||"");delete b[f]}});return c.join("")}var n=!1,o={routes:b,reload:function(){n=!0;a.$evalAsync(j)}};a.$on("$locationChangeSuccess",j);return o}]}function Sc(){this.$get=I({})}function Tc(){var b=10;this.digestTtl=function(a){arguments.length&&(b=
+a);return b};this.$get=["$injector","$exceptionHandler","$parse",function(a,c,d){function e(){this.$id=xa();this.$$phase=this.$parent=this.$$watchers=this.$$nextSibling=this.$$prevSibling=this.$$childHead=this.$$childTail=null;this["this"]=this.$root=this;this.$$destroyed=!1;this.$$asyncQueue=[];this.$$listeners={};this.$$isolateBindings={}}function g(a){if(i.$$phase)throw Error(i.$$phase+" already in progress");i.$$phase=a}function h(a,b){var c=d(a);qa(c,b);return c}function f(){}e.prototype={$new:function(a){if(H(a))throw Error("API-CHANGE: Use $controller to instantiate controllers.");
+a?(a=new e,a.$root=this.$root):(a=function(){},a.prototype=this,a=new a,a.$id=xa());a["this"]=a;a.$$listeners={};a.$parent=this;a.$$asyncQueue=[];a.$$watchers=a.$$nextSibling=a.$$childHead=a.$$childTail=null;a.$$prevSibling=this.$$childTail;this.$$childHead?this.$$childTail=this.$$childTail.$$nextSibling=a:this.$$childHead=this.$$childTail=a;return a},$watch:function(a,b,c){var d=h(a,"watch"),e=this.$$watchers,g={fn:b,last:f,get:d,exp:a,eq:!!c};if(!H(b)){var i=h(b||C,"listener");g.fn=function(a,b,
+c){i(c)}}if(!e)e=this.$$watchers=[];e.unshift(g);return function(){Sa(e,g)}},$digest:function(){var a,d,e,h,o,p,m,t=b,x,q=[],A,N;g("$digest");do{m=!1;x=this;do{for(o=x.$$asyncQueue;o.length;)try{x.$eval(o.shift())}catch(J){c(J)}if(h=x.$$watchers)for(p=h.length;p--;)try{if(a=h[p],(d=a.get(x))!==(e=a.last)&&!(a.eq?fa(d,e):typeof d=="number"&&typeof e=="number"&&isNaN(d)&&isNaN(e)))m=!0,a.last=a.eq?U(d):d,a.fn(d,e===f?d:e,x),t<5&&(A=4-t,q[A]||(q[A]=[]),N=H(a.exp)?"fn: "+(a.exp.name||a.exp.toString()):
+a.exp,N+="; newVal: "+da(d)+"; oldVal: "+da(e),q[A].push(N))}catch(r){c(r)}if(!(h=x.$$childHead||x!==this&&x.$$nextSibling))for(;x!==this&&!(h=x.$$nextSibling);)x=x.$parent}while(x=h);if(m&&!t--)throw i.$$phase=null,Error(b+" $digest() iterations reached. Aborting!\nWatchers fired in the last 5 iterations: "+da(q));}while(m||o.length);i.$$phase=null},$destroy:function(){if(!(i==this||this.$$destroyed)){var a=this.$parent;this.$broadcast("$destroy");this.$$destroyed=!0;if(a.$$childHead==this)a.$$childHead=
+this.$$nextSibling;if(a.$$childTail==this)a.$$childTail=this.$$prevSibling;if(this.$$prevSibling)this.$$prevSibling.$$nextSibling=this.$$nextSibling;if(this.$$nextSibling)this.$$nextSibling.$$prevSibling=this.$$prevSibling;this.$parent=this.$$nextSibling=this.$$prevSibling=this.$$childHead=this.$$childTail=null}},$eval:function(a,b){return d(a)(this,b)},$evalAsync:function(a){this.$$asyncQueue.push(a)},$apply:function(a){try{return g("$apply"),this.$eval(a)}catch(b){c(b)}finally{i.$$phase=null;try{i.$digest()}catch(d){throw c(d),
+d;}}},$on:function(a,b){var c=this.$$listeners[a];c||(this.$$listeners[a]=c=[]);c.push(b);return function(){c[za(c,b)]=null}},$emit:function(a,b){var d=[],e,f=this,g=!1,h={name:a,targetScope:f,stopPropagation:function(){g=!0},preventDefault:function(){h.defaultPrevented=!0},defaultPrevented:!1},i=[h].concat(ha.call(arguments,1)),m,q;do{e=f.$$listeners[a]||d;h.currentScope=f;m=0;for(q=e.length;m<q;m++)if(e[m])try{if(e[m].apply(null,i),g)return h}catch(A){c(A)}else e.splice(m,1),m--,q--;f=f.$parent}while(f);
+return h},$broadcast:function(a,b){var d=this,e=this,f={name:a,targetScope:this,preventDefault:function(){f.defaultPrevented=!0},defaultPrevented:!1},g=[f].concat(ha.call(arguments,1)),h,i;do{d=e;f.currentScope=d;e=d.$$listeners[a]||[];h=0;for(i=e.length;h<i;h++)if(e[h])try{e[h].apply(null,g)}catch(m){c(m)}else e.splice(h,1),h--,i--;if(!(e=d.$$childHead||d!==this&&d.$$nextSibling))for(;d!==this&&!(e=d.$$nextSibling);)d=d.$parent}while(d=e);return f}};var i=new e;return i}]}function Uc(){this.$get=
+["$window",function(b){var a={},c=G((/android (\d+)/.exec(z(b.navigator.userAgent))||[])[1]);return{history:!(!b.history||!b.history.pushState||c<4),hashchange:"onhashchange"in b&&(!b.document.documentMode||b.document.documentMode>7),hasEvent:function(c){if(c=="input"&&Z==9)return!1;if(w(a[c])){var e=b.document.createElement("div");a[c]="on"+c in e}return a[c]},csp:!1}}]}function Vc(){this.$get=I(P)}function Ob(b){var a={},c,d,e;if(!b)return a;m(b.split("\n"),function(b){e=b.indexOf(":");c=z(Q(b.substr(0,
+e)));d=Q(b.substr(e+1));c&&(a[c]?a[c]+=", "+d:a[c]=d)});return a}function Pb(b){var a=L(b)?b:q;return function(c){a||(a=Ob(b));return c?a[z(c)]||null:a}}function Qb(b,a,c){if(H(c))return c(b,a);m(c,function(c){b=c(b,a)});return b}function Wc(){var b=/^\s*(\[|\{[^\{])/,a=/[\}\]]\s*$/,c=/^\)\]\}',?\n/,d=this.defaults={transformResponse:[function(d){B(d)&&(d=d.replace(c,""),b.test(d)&&a.test(d)&&(d=pb(d,!0)));return d}],transformRequest:[function(a){return L(a)&&wa.apply(a)!=="[object File]"?da(a):a}],
+headers:{common:{Accept:"application/json, text/plain, */*","X-Requested-With":"XMLHttpRequest"},post:{"Content-Type":"application/json;charset=utf-8"},put:{"Content-Type":"application/json;charset=utf-8"}}},e=this.responseInterceptors=[];this.$get=["$httpBackend","$browser","$cacheFactory","$rootScope","$q","$injector",function(a,b,c,i,j,k){function l(a){function c(a){var b=v({},a,{data:Qb(a.data,a.headers,f)});return 200<=a.status&&a.status<300?b:j.reject(b)}a.method=la(a.method);var e=a.transformRequest||
+d.transformRequest,f=a.transformResponse||d.transformResponse,g=d.headers,g=v({"X-XSRF-TOKEN":b.cookies()["XSRF-TOKEN"]},g.common,g[z(a.method)],a.headers),e=Qb(a.data,Pb(g),e),i;w(a.data)&&delete g["Content-Type"];i=n(a,e,g);i=i.then(c,c);m(s,function(a){i=a(i)});i.success=function(b){i.then(function(c){b(c.data,c.status,c.headers,a)});return i};i.error=function(b){i.then(null,function(c){b(c.data,c.status,c.headers,a)});return i};return i}function n(b,c,d){function e(a,b,c){m&&(200<=a&&a<300?m.put(q,
+[a,b,Ob(c)]):m.remove(q));f(b,a,c);i.$apply()}function f(a,c,d){c=Math.max(c,0);(200<=c&&c<300?k.resolve:k.reject)({data:a,status:c,headers:Pb(d),config:b})}function h(){var a=za(l.pendingRequests,b);a!==-1&&l.pendingRequests.splice(a,1)}var k=j.defer(),n=k.promise,m,s,q=o(b.url,b.params);l.pendingRequests.push(b);n.then(h,h);b.cache&&b.method=="GET"&&(m=L(b.cache)?b.cache:p);if(m)if(s=m.get(q))if(s.then)return s.then(h,h),s;else E(s)?f(s[1],s[0],U(s[2])):f(s,200,{});else m.put(q,n);s||a(b.method,
+q,c,e,d,b.timeout,b.withCredentials);return n}function o(a,b){if(!b)return a;var c=[];fc(b,function(a,b){a==null||a==q||(L(a)&&(a=da(a)),c.push(encodeURIComponent(b)+"="+encodeURIComponent(a)))});return a+(a.indexOf("?")==-1?"?":"&")+c.join("&")}var p=c("$http"),s=[];m(e,function(a){s.push(B(a)?k.get(a):k.invoke(a))});l.pendingRequests=[];(function(a){m(arguments,function(a){l[a]=function(b,c){return l(v(c||{},{method:a,url:b}))}})})("get","delete","head","jsonp");(function(a){m(arguments,function(a){l[a]=
+function(b,c,d){return l(v(d||{},{method:a,url:b,data:c}))}})})("post","put");l.defaults=d;return l}]}function Xc(){this.$get=["$browser","$window","$document",function(b,a,c){return Yc(b,Zc,b.defer,a.angular.callbacks,c[0],a.location.protocol.replace(":",""))}]}function Yc(b,a,c,d,e,g){function h(a,b){var c=e.createElement("script"),d=function(){e.body.removeChild(c);b&&b()};c.type="text/javascript";c.src=a;Z?c.onreadystatechange=function(){/loaded|complete/.test(c.readyState)&&d()}:c.onload=c.onerror=
+d;e.body.appendChild(c)}return function(e,i,j,k,l,n,o){function p(a,c,d,e){c=(i.match(Hb)||["",g])[1]=="file"?d?200:404:c;a(c==1223?204:c,d,e);b.$$completeOutstandingRequest(C)}b.$$incOutstandingRequestCount();i=i||b.url();if(z(e)=="jsonp"){var s="_"+(d.counter++).toString(36);d[s]=function(a){d[s].data=a};h(i.replace("JSON_CALLBACK","angular.callbacks."+s),function(){d[s].data?p(k,200,d[s].data):p(k,-2);delete d[s]})}else{var t=new a;t.open(e,i,!0);m(l,function(a,b){a&&t.setRequestHeader(b,a)});
+var q;t.onreadystatechange=function(){if(t.readyState==4){var a=t.getAllResponseHeaders(),b=["Cache-Control","Content-Language","Content-Type","Expires","Last-Modified","Pragma"];a||(a="",m(b,function(b){var c=t.getResponseHeader(b);c&&(a+=b+": "+c+"\n")}));p(k,q||t.status,t.responseText,a)}};if(o)t.withCredentials=!0;t.send(j||"");n>0&&c(function(){q=-1;t.abort()},n)}}}function $c(){this.$get=function(){return{id:"en-us",NUMBER_FORMATS:{DECIMAL_SEP:".",GROUP_SEP:",",PATTERNS:[{minInt:1,minFrac:0,
+maxFrac:3,posPre:"",posSuf:"",negPre:"-",negSuf:"",gSize:3,lgSize:3},{minInt:1,minFrac:2,maxFrac:2,posPre:"\u00a4",posSuf:"",negPre:"(\u00a4",negSuf:")",gSize:3,lgSize:3}],CURRENCY_SYM:"$"},DATETIME_FORMATS:{MONTH:"January,February,March,April,May,June,July,August,September,October,November,December".split(","),SHORTMONTH:"Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec".split(","),DAY:"Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday".split(","),SHORTDAY:"Sun,Mon,Tue,Wed,Thu,Fri,Sat".split(","),
+AMPMS:["AM","PM"],medium:"MMM d, y h:mm:ss a","short":"M/d/yy h:mm a",fullDate:"EEEE, MMMM d, y",longDate:"MMMM d, y",mediumDate:"MMM d, y",shortDate:"M/d/yy",mediumTime:"h:mm:ss a",shortTime:"h:mm a"},pluralCat:function(b){return b===1?"one":"other"}}}}function ad(){this.$get=["$rootScope","$browser","$q","$exceptionHandler",function(b,a,c,d){function e(e,f,i){var j=c.defer(),k=j.promise,l=y(i)&&!i,f=a.defer(function(){try{j.resolve(e())}catch(a){j.reject(a),d(a)}l||b.$apply()},f),i=function(){delete g[k.$$timeoutId]};
+k.$$timeoutId=f;g[f]=j;k.then(i,i);return k}var g={};e.cancel=function(b){return b&&b.$$timeoutId in g?(g[b.$$timeoutId].reject("canceled"),a.defer.cancel(b.$$timeoutId)):!1};return e}]}function Rb(b){function a(a,e){return b.factory(a+c,e)}var c="Filter";this.register=a;this.$get=["$injector",function(a){return function(b){return a.get(b+c)}}];a("currency",Sb);a("date",Tb);a("filter",bd);a("json",cd);a("limitTo",dd);a("lowercase",ed);a("number",Ub);a("orderBy",Vb);a("uppercase",fd)}function bd(){return function(b,
+a){if(!E(b))return b;var c=[];c.check=function(a){for(var b=0;b<c.length;b++)if(!c[b](a))return!1;return!0};var d=function(a,b){if(b.charAt(0)==="!")return!d(a,b.substr(1));switch(typeof a){case "boolean":case "number":case "string":return(""+a).toLowerCase().indexOf(b)>-1;case "object":for(var c in a)if(c.charAt(0)!=="$"&&d(a[c],b))return!0;return!1;case "array":for(c=0;c<a.length;c++)if(d(a[c],b))return!0;return!1;default:return!1}};switch(typeof a){case "boolean":case "number":case "string":a=
+{$:a};case "object":for(var e in a)e=="$"?function(){var b=(""+a[e]).toLowerCase();b&&c.push(function(a){return d(a,b)})}():function(){var b=e,f=(""+a[e]).toLowerCase();f&&c.push(function(a){return d(gb(a,b),f)})}();break;case "function":c.push(a);break;default:return b}for(var g=[],h=0;h<b.length;h++){var f=b[h];c.check(f)&&g.push(f)}return g}}function Sb(b){var a=b.NUMBER_FORMATS;return function(b,d){if(w(d))d=a.CURRENCY_SYM;return Wb(b,a.PATTERNS[1],a.GROUP_SEP,a.DECIMAL_SEP,2).replace(/\u00A4/g,
+d)}}function Ub(b){var a=b.NUMBER_FORMATS;return function(b,d){return Wb(b,a.PATTERNS[0],a.GROUP_SEP,a.DECIMAL_SEP,d)}}function Wb(b,a,c,d,e){if(isNaN(b)||!isFinite(b))return"";var g=b<0,b=Math.abs(b),h=b+"",f="",i=[],j=!1;if(h.indexOf("e")!==-1){var k=h.match(/([\d\.]+)e(-?)(\d+)/);k&&k[2]=="-"&&k[3]>e+1?h="0":(f=h,j=!0)}if(!j){h=(h.split(Xb)[1]||"").length;w(e)&&(e=Math.min(Math.max(a.minFrac,h),a.maxFrac));var h=Math.pow(10,e),b=Math.round(b*h)/h,b=(""+b).split(Xb),h=b[0],b=b[1]||"",j=0,k=a.lgSize,
+l=a.gSize;if(h.length>=k+l)for(var j=h.length-k,n=0;n<j;n++)(j-n)%l===0&&n!==0&&(f+=c),f+=h.charAt(n);for(n=j;n<h.length;n++)(h.length-n)%k===0&&n!==0&&(f+=c),f+=h.charAt(n);for(;b.length<e;)b+="0";e&&e!=="0"&&(f+=d+b.substr(0,e))}i.push(g?a.negPre:a.posPre);i.push(f);i.push(g?a.negSuf:a.posSuf);return i.join("")}function jb(b,a,c){var d="";b<0&&(d="-",b=-b);for(b=""+b;b.length<a;)b="0"+b;c&&(b=b.substr(b.length-a));return d+b}function O(b,a,c,d){c=c||0;return function(e){e=e["get"+b]();if(c>0||e>
+-c)e+=c;e===0&&c==-12&&(e=12);return jb(e,a,d)}}function Ja(b,a){return function(c,d){var e=c["get"+b](),g=la(a?"SHORT"+b:b);return d[g][e]}}function Tb(b){function a(a){var b;if(b=a.match(c)){var a=new Date(0),g=0,h=0;b[9]&&(g=G(b[9]+b[10]),h=G(b[9]+b[11]));a.setUTCFullYear(G(b[1]),G(b[2])-1,G(b[3]));a.setUTCHours(G(b[4]||0)-g,G(b[5]||0)-h,G(b[6]||0),G(b[7]||0))}return a}var c=/^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/;return function(c,
+e){var g="",h=[],f,i,e=e||"mediumDate",e=b.DATETIME_FORMATS[e]||e;B(c)&&(c=gd.test(c)?G(c):a(c));Qa(c)&&(c=new Date(c));if(!na(c))return c;for(;e;)(i=hd.exec(e))?(h=h.concat(ha.call(i,1)),e=h.pop()):(h.push(e),e=null);m(h,function(a){f=id[a];g+=f?f(c,b.DATETIME_FORMATS):a.replace(/(^'|'$)/g,"").replace(/''/g,"'")});return g}}function cd(){return function(b){return da(b,!0)}}function dd(){return function(b,a){if(!(b instanceof Array))return b;var a=G(a),c=[],d,e;if(!b||!(b instanceof Array))return c;
+a>b.length?a=b.length:a<-b.length&&(a=-b.length);a>0?(d=0,e=a):(d=b.length+a,e=b.length);for(;d<e;d++)c.push(b[d]);return c}}function Vb(b){return function(a,c,d){function e(a,b){return Ua(b)?function(b,c){return a(c,b)}:a}if(!E(a))return a;if(!c)return a;for(var c=E(c)?c:[c],c=Ra(c,function(a){var c=!1,d=a||ma;if(B(a)){if(a.charAt(0)=="+"||a.charAt(0)=="-")c=a.charAt(0)=="-",a=a.substring(1);d=b(a)}return e(function(a,b){var c;c=d(a);var e=d(b),f=typeof c,g=typeof e;f==g?(f=="string"&&(c=c.toLowerCase()),
+f=="string"&&(e=e.toLowerCase()),c=c===e?0:c<e?-1:1):c=f<g?-1:1;return c},c)}),g=[],h=0;h<a.length;h++)g.push(a[h]);return g.sort(e(function(a,b){for(var d=0;d<c.length;d++){var e=c[d](a,b);if(e!==0)return e}return 0},d))}}function S(b){H(b)&&(b={link:b});b.restrict=b.restrict||"AC";return I(b)}function Yb(b,a){function c(a,c){c=c?"-"+Za(c,"-"):"";b.removeClass((a?Ka:La)+c).addClass((a?La:Ka)+c)}var d=this,e=b.parent().controller("form")||Ma,g=0,h=d.$error={};d.$name=a.name;d.$dirty=!1;d.$pristine=
+!0;d.$valid=!0;d.$invalid=!1;e.$addControl(d);b.addClass(Na);c(!0);d.$addControl=function(a){a.$name&&!d.hasOwnProperty(a.$name)&&(d[a.$name]=a)};d.$removeControl=function(a){a.$name&&d[a.$name]===a&&delete d[a.$name];m(h,function(b,c){d.$setValidity(c,!0,a)})};d.$setValidity=function(a,b,j){var k=h[a];if(b){if(k&&(Sa(k,j),!k.length)){g--;if(!g)c(b),d.$valid=!0,d.$invalid=!1;h[a]=!1;c(!0,a);e.$setValidity(a,!0,d)}}else{g||c(b);if(k){if(za(k,j)!=-1)return}else h[a]=k=[],g++,c(!1,a),e.$setValidity(a,
+!1,d);k.push(j);d.$valid=!1;d.$invalid=!0}};d.$setDirty=function(){b.removeClass(Na).addClass(Zb);d.$dirty=!0;d.$pristine=!1;e.$setDirty()}}function X(b){return w(b)||b===""||b===null||b!==b}function Oa(b,a,c,d,e,g){var h=function(){var c=Q(a.val());d.$viewValue!==c&&b.$apply(function(){d.$setViewValue(c)})};if(e.hasEvent("input"))a.bind("input",h);else{var f,i=function(){f||(f=g.defer(function(){h();f=null}))};a.bind("keydown",function(a){a=a.keyCode;a===91||15<a&&a<19||37<=a&&a<=40||i()});a.bind("change",
+h);e.hasEvent("paste")&&a.bind("paste cut",i)}d.$render=function(){a.val(X(d.$viewValue)?"":d.$viewValue)};var j=c.ngPattern,k=function(a,b){return X(b)||a.test(b)?(d.$setValidity("pattern",!0),b):(d.$setValidity("pattern",!1),q)};j&&(j.match(/^\/(.*)\/$/)?(j=RegExp(j.substr(1,j.length-2)),e=function(a){return k(j,a)}):e=function(a){var c=b.$eval(j);if(!c||!c.test)throw Error("Expected "+j+" to be a RegExp but was "+c);return k(c,a)},d.$formatters.push(e),d.$parsers.push(e));if(c.ngMinlength){var l=
+G(c.ngMinlength),e=function(a){return!X(a)&&a.length<l?(d.$setValidity("minlength",!1),q):(d.$setValidity("minlength",!0),a)};d.$parsers.push(e);d.$formatters.push(e)}if(c.ngMaxlength){var n=G(c.ngMaxlength),c=function(a){return!X(a)&&a.length>n?(d.$setValidity("maxlength",!1),q):(d.$setValidity("maxlength",!0),a)};d.$parsers.push(c);d.$formatters.push(c)}}function kb(b,a){b="ngClass"+b;return S(function(c,d,e){function g(b){if(a===!0||c.$index%2===a)i&&!fa(b,i)&&h(i),f(b);i=U(b)}function h(a){L(a)&&
+!E(a)&&(a=Ra(a,function(a,b){if(a)return b}));d.removeClass(E(a)?a.join(" "):a)}function f(a){L(a)&&!E(a)&&(a=Ra(a,function(a,b){if(a)return b}));a&&d.addClass(E(a)?a.join(" "):a)}var i=q;c.$watch(e[b],g,!0);e.$observe("class",function(){var a=c.$eval(e[b]);g(a,a)});b!=="ngClass"&&c.$watch("$index",function(d,g){var i=d&1;i!==g&1&&(i===a?f(c.$eval(e[b])):h(c.$eval(e[b])))})})}var z=function(b){return B(b)?b.toLowerCase():b},la=function(b){return B(b)?b.toUpperCase():b},Z=G((/msie (\d+)/.exec(z(navigator.userAgent))||
+[])[1]),u,ca,ha=[].slice,Pa=[].push,wa=Object.prototype.toString,Ya=P.angular||(P.angular={}),sa,fb,aa=["0","0","0"];C.$inject=[];ma.$inject=[];fb=Z<9?function(b){b=b.nodeName?b:b[0];return b.scopeName&&b.scopeName!="HTML"?la(b.scopeName+":"+b.nodeName):b.nodeName}:function(b){return b.nodeName?b.nodeName:b[0].nodeName};var kc=/[A-Z]/g,jd={full:"1.0.7",major:1,minor:0,dot:7,codeName:"monochromatic-rainbow"},Ba=K.cache={},Aa=K.expando="ng-"+(new Date).getTime(),oc=1,$b=P.document.addEventListener?
+function(b,a,c){b.addEventListener(a,c,!1)}:function(b,a,c){b.attachEvent("on"+a,c)},db=P.document.removeEventListener?function(b,a,c){b.removeEventListener(a,c,!1)}:function(b,a,c){b.detachEvent("on"+a,c)},mc=/([\:\-\_]+(.))/g,nc=/^moz([A-Z])/,ua=K.prototype={ready:function(b){function a(){c||(c=!0,b())}var c=!1;this.bind("DOMContentLoaded",a);K(P).bind("load",a)},toString:function(){var b=[];m(this,function(a){b.push(""+a)});return"["+b.join(", ")+"]"},eq:function(b){return b>=0?u(this[b]):u(this[this.length+
+b])},length:0,push:Pa,sort:[].sort,splice:[].splice},Ea={};m("multiple,selected,checked,disabled,readOnly,required".split(","),function(b){Ea[z(b)]=b});var Bb={};m("input,select,option,textarea,button,form".split(","),function(b){Bb[la(b)]=!0});m({data:wb,inheritedData:Da,scope:function(b){return Da(b,"$scope")},controller:zb,injector:function(b){return Da(b,"$injector")},removeAttr:function(b,a){b.removeAttribute(a)},hasClass:Ca,css:function(b,a,c){a=tb(a);if(y(c))b.style[a]=c;else{var d;Z<=8&&(d=
+b.currentStyle&&b.currentStyle[a],d===""&&(d="auto"));d=d||b.style[a];Z<=8&&(d=d===""?q:d);return d}},attr:function(b,a,c){var d=z(a);if(Ea[d])if(y(c))c?(b[a]=!0,b.setAttribute(a,d)):(b[a]=!1,b.removeAttribute(d));else return b[a]||(b.attributes.getNamedItem(a)||C).specified?d:q;else if(y(c))b.setAttribute(a,c);else if(b.getAttribute)return b=b.getAttribute(a,2),b===null?q:b},prop:function(b,a,c){if(y(c))b[a]=c;else return b[a]},text:v(Z<9?function(b,a){if(b.nodeType==1){if(w(a))return b.innerText;
+b.innerText=a}else{if(w(a))return b.nodeValue;b.nodeValue=a}}:function(b,a){if(w(a))return b.textContent;b.textContent=a},{$dv:""}),val:function(b,a){if(w(a))return b.value;b.value=a},html:function(b,a){if(w(a))return b.innerHTML;for(var c=0,d=b.childNodes;c<d.length;c++)ra(d[c]);b.innerHTML=a}},function(b,a){K.prototype[a]=function(a,d){var e,g;if((b.length==2&&b!==Ca&&b!==zb?a:d)===q)if(L(a)){for(e=0;e<this.length;e++)if(b===wb)b(this[e],a);else for(g in a)b(this[e],g,a[g]);return this}else{if(this.length)return b(this[0],
+a,d)}else{for(e=0;e<this.length;e++)b(this[e],a,d);return this}return b.$dv}});m({removeData:ub,dealoc:ra,bind:function a(c,d,e){var g=ba(c,"events"),h=ba(c,"handle");g||ba(c,"events",g={});h||ba(c,"handle",h=pc(c,g));m(d.split(" "),function(d){var i=g[d];if(!i){if(d=="mouseenter"||d=="mouseleave"){var j=T.body.contains||T.body.compareDocumentPosition?function(a,c){var d=a.nodeType===9?a.documentElement:a,e=c&&c.parentNode;return a===e||!(!e||!(e.nodeType===1&&(d.contains?d.contains(e):a.compareDocumentPosition&&
+a.compareDocumentPosition(e)&16)))}:function(a,c){if(c)for(;c=c.parentNode;)if(c===a)return!0;return!1};g[d]=[];a(c,{mouseleave:"mouseout",mouseenter:"mouseover"}[d],function(a){var c=a.relatedTarget;(!c||c!==this&&!j(this,c))&&h(a,d)})}else $b(c,d,h),g[d]=[];i=g[d]}i.push(e)})},unbind:vb,replaceWith:function(a,c){var d,e=a.parentNode;ra(a);m(new K(c),function(c){d?e.insertBefore(c,d.nextSibling):e.replaceChild(c,a);d=c})},children:function(a){var c=[];m(a.childNodes,function(a){a.nodeType===1&&c.push(a)});
+return c},contents:function(a){return a.childNodes||[]},append:function(a,c){m(new K(c),function(c){a.nodeType===1&&a.appendChild(c)})},prepend:function(a,c){if(a.nodeType===1){var d=a.firstChild;m(new K(c),function(c){d?a.insertBefore(c,d):(a.appendChild(c),d=c)})}},wrap:function(a,c){var c=u(c)[0],d=a.parentNode;d&&d.replaceChild(c,a);c.appendChild(a)},remove:function(a){ra(a);var c=a.parentNode;c&&c.removeChild(a)},after:function(a,c){var d=a,e=a.parentNode;m(new K(c),function(a){e.insertBefore(a,
+d.nextSibling);d=a})},addClass:yb,removeClass:xb,toggleClass:function(a,c,d){w(d)&&(d=!Ca(a,c));(d?yb:xb)(a,c)},parent:function(a){return(a=a.parentNode)&&a.nodeType!==11?a:null},next:function(a){if(a.nextElementSibling)return a.nextElementSibling;for(a=a.nextSibling;a!=null&&a.nodeType!==1;)a=a.nextSibling;return a},find:function(a,c){return a.getElementsByTagName(c)},clone:cb,triggerHandler:function(a,c){var d=(ba(a,"events")||{})[c];m(d,function(c){c.call(a,null)})}},function(a,c){K.prototype[c]=
+function(c,e){for(var g,h=0;h<this.length;h++)g==q?(g=a(this[h],c,e),g!==q&&(g=u(g))):bb(g,a(this[h],c,e));return g==q?this:g}});Fa.prototype={put:function(a,c){this[ga(a)]=c},get:function(a){return this[ga(a)]},remove:function(a){var c=this[a=ga(a)];delete this[a];return c}};eb.prototype={push:function(a,c){var d=this[a=ga(a)];d?d.push(c):this[a]=[c]},shift:function(a){var c=this[a=ga(a)];if(c)return c.length==1?(delete this[a],c[0]):c.shift()},peek:function(a){if(a=this[ga(a)])return a[0]}};var rc=
+/^function\s*[^\(]*\(\s*([^\)]*)\)/m,sc=/,/,tc=/^\s*(_?)(\S+?)\1\s*$/,qc=/((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg,Eb="Non-assignable model expression: ";Db.$inject=["$provide"];var Bc=/^(x[\:\-_]|data[\:\-_])/i,Hb=/^([^:]+):\/\/(\w+:{0,1}\w*@)?(\{?[\w\.-]*\}?)(:([0-9]+))?(\/[^\?#]*)?(\?([^#]*))?(#(.*))?$/,ac=/^([^\?#]*)?(\?([^#]*))?(#(.*))?$/,Ic=ac,Ib={http:80,https:443,ftp:21};hb.prototype={$$replace:!1,absUrl:Ha("$$absUrl"),url:function(a,c){if(w(a))return this.$$url;var d=ac.exec(a);d[1]&&this.path(decodeURIComponent(d[1]));
+if(d[2]||d[1])this.search(d[3]||"");this.hash(d[5]||"",c);return this},protocol:Ha("$$protocol"),host:Ha("$$host"),port:Ha("$$port"),path:Kb("$$path",function(a){return a.charAt(0)=="/"?a:"/"+a}),search:function(a,c){if(w(a))return this.$$search;y(c)?c===null?delete this.$$search[a]:this.$$search[a]=c:this.$$search=B(a)?Va(a):a;this.$$compose();return this},hash:Kb("$$hash",ma),replace:function(){this.$$replace=!0;return this}};Ga.prototype=ya(hb.prototype);Jb.prototype=ya(Ga.prototype);var Ia={"null":function(){return null},
+"true":function(){return!0},"false":function(){return!1},undefined:C,"+":function(a,c,d,e){d=d(a,c);e=e(a,c);return y(d)?y(e)?d+e:d:y(e)?e:q},"-":function(a,c,d,e){d=d(a,c);e=e(a,c);return(y(d)?d:0)-(y(e)?e:0)},"*":function(a,c,d,e){return d(a,c)*e(a,c)},"/":function(a,c,d,e){return d(a,c)/e(a,c)},"%":function(a,c,d,e){return d(a,c)%e(a,c)},"^":function(a,c,d,e){return d(a,c)^e(a,c)},"=":C,"==":function(a,c,d,e){return d(a,c)==e(a,c)},"!=":function(a,c,d,e){return d(a,c)!=e(a,c)},"<":function(a,c,
+d,e){return d(a,c)<e(a,c)},">":function(a,c,d,e){return d(a,c)>e(a,c)},"<=":function(a,c,d,e){return d(a,c)<=e(a,c)},">=":function(a,c,d,e){return d(a,c)>=e(a,c)},"&&":function(a,c,d,e){return d(a,c)&&e(a,c)},"||":function(a,c,d,e){return d(a,c)||e(a,c)},"&":function(a,c,d,e){return d(a,c)&e(a,c)},"|":function(a,c,d,e){return e(a,c)(a,c,d(a,c))},"!":function(a,c,d){return!d(a,c)}},Mc={n:"\n",f:"\u000c",r:"\r",t:"\t",v:"\u000b","'":"'",'"':'"'},ib={},Zc=P.XMLHttpRequest||function(){try{return new ActiveXObject("Msxml2.XMLHTTP.6.0")}catch(a){}try{return new ActiveXObject("Msxml2.XMLHTTP.3.0")}catch(c){}try{return new ActiveXObject("Msxml2.XMLHTTP")}catch(d){}throw Error("This browser does not support XMLHttpRequest.");
+};Rb.$inject=["$provide"];Sb.$inject=["$locale"];Ub.$inject=["$locale"];var Xb=".",id={yyyy:O("FullYear",4),yy:O("FullYear",2,0,!0),y:O("FullYear",1),MMMM:Ja("Month"),MMM:Ja("Month",!0),MM:O("Month",2,1),M:O("Month",1,1),dd:O("Date",2),d:O("Date",1),HH:O("Hours",2),H:O("Hours",1),hh:O("Hours",2,-12),h:O("Hours",1,-12),mm:O("Minutes",2),m:O("Minutes",1),ss:O("Seconds",2),s:O("Seconds",1),EEEE:Ja("Day"),EEE:Ja("Day",!0),a:function(a,c){return a.getHours()<12?c.AMPMS[0]:c.AMPMS[1]},Z:function(a){var a=
+-1*a.getTimezoneOffset(),c=a>=0?"+":"";c+=jb(Math[a>0?"floor":"ceil"](a/60),2)+jb(Math.abs(a%60),2);return c}},hd=/((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z))(.*)/,gd=/^\d+$/;Tb.$inject=["$locale"];var ed=I(z),fd=I(la);Vb.$inject=["$parse"];var kd=I({restrict:"E",compile:function(a,c){Z<=8&&(!c.href&&!c.name&&c.$set("href",""),a.append(T.createComment("IE fix")));return function(a,c){c.bind("click",function(a){c.attr("href")||a.preventDefault()})}}}),lb={};m(Ea,function(a,
+c){var d=ea("ng-"+c);lb[d]=function(){return{priority:100,compile:function(){return function(a,g,h){a.$watch(h[d],function(a){h.$set(c,!!a)})}}}}});m(["src","href"],function(a){var c=ea("ng-"+a);lb[c]=function(){return{priority:99,link:function(d,e,g){g.$observe(c,function(c){c&&(g.$set(a,c),Z&&e.prop(a,g[a]))})}}}});var Ma={$addControl:C,$removeControl:C,$setValidity:C,$setDirty:C};Yb.$inject=["$element","$attrs","$scope"];var Pa=function(a){return["$timeout",function(c){var d={name:"form",restrict:"E",
+controller:Yb,compile:function(){return{pre:function(a,d,h,f){if(!h.action){var i=function(a){a.preventDefault?a.preventDefault():a.returnValue=!1};$b(d[0],"submit",i);d.bind("$destroy",function(){c(function(){db(d[0],"submit",i)},0,!1)})}var j=d.parent().controller("form"),k=h.name||h.ngForm;k&&(a[k]=f);j&&d.bind("$destroy",function(){j.$removeControl(f);k&&(a[k]=q);v(f,Ma)})}}}};return a?v(U(d),{restrict:"EAC"}):d}]},ld=Pa(),md=Pa(!0),nd=/^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/,
+od=/^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}$/,pd=/^\s*(\-|\+)?(\d+|(\d*(\.\d*)))\s*$/,bc={text:Oa,number:function(a,c,d,e,g,h){Oa(a,c,d,e,g,h);e.$parsers.push(function(a){var c=X(a);return c||pd.test(a)?(e.$setValidity("number",!0),a===""?null:c?a:parseFloat(a)):(e.$setValidity("number",!1),q)});e.$formatters.push(function(a){return X(a)?"":""+a});if(d.min){var f=parseFloat(d.min),a=function(a){return!X(a)&&a<f?(e.$setValidity("min",!1),q):(e.$setValidity("min",!0),a)};e.$parsers.push(a);
+e.$formatters.push(a)}if(d.max){var i=parseFloat(d.max),d=function(a){return!X(a)&&a>i?(e.$setValidity("max",!1),q):(e.$setValidity("max",!0),a)};e.$parsers.push(d);e.$formatters.push(d)}e.$formatters.push(function(a){return X(a)||Qa(a)?(e.$setValidity("number",!0),a):(e.$setValidity("number",!1),q)})},url:function(a,c,d,e,g,h){Oa(a,c,d,e,g,h);a=function(a){return X(a)||nd.test(a)?(e.$setValidity("url",!0),a):(e.$setValidity("url",!1),q)};e.$formatters.push(a);e.$parsers.push(a)},email:function(a,
+c,d,e,g,h){Oa(a,c,d,e,g,h);a=function(a){return X(a)||od.test(a)?(e.$setValidity("email",!0),a):(e.$setValidity("email",!1),q)};e.$formatters.push(a);e.$parsers.push(a)},radio:function(a,c,d,e){w(d.name)&&c.attr("name",xa());c.bind("click",function(){c[0].checked&&a.$apply(function(){e.$setViewValue(d.value)})});e.$render=function(){c[0].checked=d.value==e.$viewValue};d.$observe("value",e.$render)},checkbox:function(a,c,d,e){var g=d.ngTrueValue,h=d.ngFalseValue;B(g)||(g=!0);B(h)||(h=!1);c.bind("click",
+function(){a.$apply(function(){e.$setViewValue(c[0].checked)})});e.$render=function(){c[0].checked=e.$viewValue};e.$formatters.push(function(a){return a===g});e.$parsers.push(function(a){return a?g:h})},hidden:C,button:C,submit:C,reset:C},cc=["$browser","$sniffer",function(a,c){return{restrict:"E",require:"?ngModel",link:function(d,e,g,h){h&&(bc[z(g.type)]||bc.text)(d,e,g,h,c,a)}}}],La="ng-valid",Ka="ng-invalid",Na="ng-pristine",Zb="ng-dirty",qd=["$scope","$exceptionHandler","$attrs","$element","$parse",
+function(a,c,d,e,g){function h(a,c){c=c?"-"+Za(c,"-"):"";e.removeClass((a?Ka:La)+c).addClass((a?La:Ka)+c)}this.$modelValue=this.$viewValue=Number.NaN;this.$parsers=[];this.$formatters=[];this.$viewChangeListeners=[];this.$pristine=!0;this.$dirty=!1;this.$valid=!0;this.$invalid=!1;this.$name=d.name;var f=g(d.ngModel),i=f.assign;if(!i)throw Error(Eb+d.ngModel+" ("+pa(e)+")");this.$render=C;var j=e.inheritedData("$formController")||Ma,k=0,l=this.$error={};e.addClass(Na);h(!0);this.$setValidity=function(a,
+c){if(l[a]!==!c){if(c){if(l[a]&&k--,!k)h(!0),this.$valid=!0,this.$invalid=!1}else h(!1),this.$invalid=!0,this.$valid=!1,k++;l[a]=!c;h(c,a);j.$setValidity(a,c,this)}};this.$setViewValue=function(d){this.$viewValue=d;if(this.$pristine)this.$dirty=!0,this.$pristine=!1,e.removeClass(Na).addClass(Zb),j.$setDirty();m(this.$parsers,function(a){d=a(d)});if(this.$modelValue!==d)this.$modelValue=d,i(a,d),m(this.$viewChangeListeners,function(a){try{a()}catch(d){c(d)}})};var n=this;a.$watch(function(){var c=
+f(a);if(n.$modelValue!==c){var d=n.$formatters,e=d.length;for(n.$modelValue=c;e--;)c=d[e](c);if(n.$viewValue!==c)n.$viewValue=c,n.$render()}})}],rd=function(){return{require:["ngModel","^?form"],controller:qd,link:function(a,c,d,e){var g=e[0],h=e[1]||Ma;h.$addControl(g);c.bind("$destroy",function(){h.$removeControl(g)})}}},sd=I({require:"ngModel",link:function(a,c,d,e){e.$viewChangeListeners.push(function(){a.$eval(d.ngChange)})}}),dc=function(){return{require:"?ngModel",link:function(a,c,d,e){if(e){d.required=
+!0;var g=function(a){if(d.required&&(X(a)||a===!1))e.$setValidity("required",!1);else return e.$setValidity("required",!0),a};e.$formatters.push(g);e.$parsers.unshift(g);d.$observe("required",function(){g(e.$viewValue)})}}}},td=function(){return{require:"ngModel",link:function(a,c,d,e){var g=(a=/\/(.*)\//.exec(d.ngList))&&RegExp(a[1])||d.ngList||",";e.$parsers.push(function(a){var c=[];a&&m(a.split(g),function(a){a&&c.push(Q(a))});return c});e.$formatters.push(function(a){return E(a)?a.join(", "):
+q})}}},ud=/^(true|false|\d+)$/,vd=function(){return{priority:100,compile:function(a,c){return ud.test(c.ngValue)?function(a,c,g){g.$set("value",a.$eval(g.ngValue))}:function(a,c,g){a.$watch(g.ngValue,function(a){g.$set("value",a,!1)})}}}},wd=S(function(a,c,d){c.addClass("ng-binding").data("$binding",d.ngBind);a.$watch(d.ngBind,function(a){c.text(a==q?"":a)})}),xd=["$interpolate",function(a){return function(c,d,e){c=a(d.attr(e.$attr.ngBindTemplate));d.addClass("ng-binding").data("$binding",c);e.$observe("ngBindTemplate",
+function(a){d.text(a)})}}],yd=[function(){return function(a,c,d){c.addClass("ng-binding").data("$binding",d.ngBindHtmlUnsafe);a.$watch(d.ngBindHtmlUnsafe,function(a){c.html(a||"")})}}],zd=kb("",!0),Ad=kb("Odd",0),Bd=kb("Even",1),Cd=S({compile:function(a,c){c.$set("ngCloak",q);a.removeClass("ng-cloak")}}),Dd=[function(){return{scope:!0,controller:"@"}}],Ed=["$sniffer",function(a){return{priority:1E3,compile:function(){a.csp=!0}}}],ec={};m("click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave".split(" "),
+function(a){var c=ea("ng-"+a);ec[c]=["$parse",function(d){return function(e,g,h){var f=d(h[c]);g.bind(z(a),function(a){e.$apply(function(){f(e,{$event:a})})})}}]});var Fd=S(function(a,c,d){c.bind("submit",function(){a.$apply(d.ngSubmit)})}),Gd=["$http","$templateCache","$anchorScroll","$compile",function(a,c,d,e){return{restrict:"ECA",terminal:!0,compile:function(g,h){var f=h.ngInclude||h.src,i=h.onload||"",j=h.autoscroll;return function(g,h){var n=0,o,p=function(){o&&(o.$destroy(),o=null);h.html("")};
+g.$watch(f,function(f){var m=++n;f?a.get(f,{cache:c}).success(function(a){m===n&&(o&&o.$destroy(),o=g.$new(),h.html(a),e(h.contents())(o),y(j)&&(!j||g.$eval(j))&&d(),o.$emit("$includeContentLoaded"),g.$eval(i))}).error(function(){m===n&&p()}):p()})}}}}],Hd=S({compile:function(){return{pre:function(a,c,d){a.$eval(d.ngInit)}}}}),Id=S({terminal:!0,priority:1E3}),Jd=["$locale","$interpolate",function(a,c){var d=/{}/g;return{restrict:"EA",link:function(e,g,h){var f=h.count,i=g.attr(h.$attr.when),j=h.offset||
+0,k=e.$eval(i),l={},n=c.startSymbol(),o=c.endSymbol();m(k,function(a,e){l[e]=c(a.replace(d,n+f+"-"+j+o))});e.$watch(function(){var c=parseFloat(e.$eval(f));return isNaN(c)?"":(c in k||(c=a.pluralCat(c-j)),l[c](e,g,!0))},function(a){g.text(a)})}}}],Kd=S({transclude:"element",priority:1E3,terminal:!0,compile:function(a,c,d){return function(a,c,h){var f=h.ngRepeat,h=f.match(/^\s*(.+)\s+in\s+(.*)\s*$/),i,j,k;if(!h)throw Error("Expected ngRepeat in form of '_item_ in _collection_' but got '"+f+"'.");f=
+h[1];i=h[2];h=f.match(/^(?:([\$\w]+)|\(([\$\w]+)\s*,\s*([\$\w]+)\))$/);if(!h)throw Error("'item' in 'item in collection' should be identifier or (key, value) but got '"+f+"'.");j=h[3]||h[1];k=h[2];var l=new eb;a.$watch(function(a){var e,f,h=a.$eval(i),m=c,q=new eb,y,A,u,w,r,v;if(E(h))r=h||[];else{r=[];for(u in h)h.hasOwnProperty(u)&&u.charAt(0)!="$"&&r.push(u);r.sort()}y=r.length-1;e=0;for(f=r.length;e<f;e++){u=h===r?e:r[e];w=h[u];if(v=l.shift(w)){A=v.scope;q.push(w,v);if(e!==v.index)v.index=e,m.after(v.element);
+m=v.element}else A=a.$new();A[j]=w;k&&(A[k]=u);A.$index=e;A.$first=e===0;A.$last=e===y;A.$middle=!(A.$first||A.$last);v||d(A,function(a){m.after(a);v={scope:A,element:m=a,index:e};q.push(w,v)})}for(u in l)if(l.hasOwnProperty(u))for(r=l[u];r.length;)w=r.pop(),w.element.remove(),w.scope.$destroy();l=q})}}}),Ld=S(function(a,c,d){a.$watch(d.ngShow,function(a){c.css("display",Ua(a)?"":"none")})}),Md=S(function(a,c,d){a.$watch(d.ngHide,function(a){c.css("display",Ua(a)?"none":"")})}),Nd=S(function(a,c,
+d){a.$watch(d.ngStyle,function(a,d){d&&a!==d&&m(d,function(a,d){c.css(d,"")});a&&c.css(a)},!0)}),Od=I({restrict:"EA",require:"ngSwitch",controller:["$scope",function(){this.cases={}}],link:function(a,c,d,e){var g,h,f;a.$watch(d.ngSwitch||d.on,function(i){h&&(f.$destroy(),h.remove(),h=f=null);if(g=e.cases["!"+i]||e.cases["?"])a.$eval(d.change),f=a.$new(),g(f,function(a){h=a;c.append(a)})})}}),Pd=S({transclude:"element",priority:500,require:"^ngSwitch",compile:function(a,c,d){return function(a,g,h,
+f){f.cases["!"+c.ngSwitchWhen]=d}}}),Qd=S({transclude:"element",priority:500,require:"^ngSwitch",compile:function(a,c,d){return function(a,c,h,f){f.cases["?"]=d}}}),Rd=S({controller:["$transclude","$element",function(a,c){a(function(a){c.append(a)})}]}),Sd=["$http","$templateCache","$route","$anchorScroll","$compile","$controller",function(a,c,d,e,g,h){return{restrict:"ECA",terminal:!0,link:function(a,c,j){function k(){var j=d.current&&d.current.locals,k=j&&j.$template;if(k){c.html(k);l&&(l.$destroy(),
+l=null);var k=g(c.contents()),m=d.current;l=m.scope=a.$new();if(m.controller)j.$scope=l,j=h(m.controller,j),c.children().data("$ngControllerController",j);k(l);l.$emit("$viewContentLoaded");l.$eval(n);e()}else c.html(""),l&&(l.$destroy(),l=null)}var l,n=j.onload||"";a.$on("$routeChangeSuccess",k);k()}}}],Td=["$templateCache",function(a){return{restrict:"E",terminal:!0,compile:function(c,d){d.type=="text/ng-template"&&a.put(d.id,c[0].text)}}}],Ud=I({terminal:!0}),Vd=["$compile","$parse",function(a,
+c){var d=/^\s*(.*?)(?:\s+as\s+(.*?))?(?:\s+group\s+by\s+(.*))?\s+for\s+(?:([\$\w][\$\w\d]*)|(?:\(\s*([\$\w][\$\w\d]*)\s*,\s*([\$\w][\$\w\d]*)\s*\)))\s+in\s+(.*)$/,e={$setViewValue:C};return{restrict:"E",require:["select","?ngModel"],controller:["$element","$scope","$attrs",function(a,c,d){var i=this,j={},k=e,l;i.databound=d.ngModel;i.init=function(a,c,d){k=a;l=d};i.addOption=function(c){j[c]=!0;k.$viewValue==c&&(a.val(c),l.parent()&&l.remove())};i.removeOption=function(a){this.hasOption(a)&&(delete j[a],
+k.$viewValue==a&&this.renderUnknownOption(a))};i.renderUnknownOption=function(c){c="? "+ga(c)+" ?";l.val(c);a.prepend(l);a.val(c);l.prop("selected",!0)};i.hasOption=function(a){return j.hasOwnProperty(a)};c.$on("$destroy",function(){i.renderUnknownOption=C})}],link:function(e,h,f,i){function j(a,c,d,e){d.$render=function(){var a=d.$viewValue;e.hasOption(a)?(z.parent()&&z.remove(),c.val(a),a===""&&v.prop("selected",!0)):w(a)&&v?c.val(""):e.renderUnknownOption(a)};c.bind("change",function(){a.$apply(function(){z.parent()&&
+z.remove();d.$setViewValue(c.val())})})}function k(a,c,d){var e;d.$render=function(){var a=new Fa(d.$viewValue);m(c.find("option"),function(c){c.selected=y(a.get(c.value))})};a.$watch(function(){fa(e,d.$viewValue)||(e=U(d.$viewValue),d.$render())});c.bind("change",function(){a.$apply(function(){var a=[];m(c.find("option"),function(c){c.selected&&a.push(c.value)});d.$setViewValue(a)})})}function l(e,f,g){function h(){var a={"":[]},c=[""],d,i,s,u,v;s=g.$modelValue;u=o(e)||[];var w=l?mb(u):u,y,x,z;x=
+{};v=!1;var B,E;p&&(v=new Fa(s));for(z=0;y=w.length,z<y;z++){x[k]=u[l?x[l]=w[z]:z];d=m(e,x)||"";if(!(i=a[d]))i=a[d]=[],c.push(d);p?d=v.remove(n(e,x))!=q:(d=s===n(e,x),v=v||d);B=j(e,x);B=B===q?"":B;i.push({id:l?w[z]:z,label:B,selected:d})}p||(t||s===null?a[""].unshift({id:"",label:"",selected:!v}):v||a[""].unshift({id:"?",label:"",selected:!0}));x=0;for(w=c.length;x<w;x++){d=c[x];i=a[d];if(r.length<=x)s={element:A.clone().attr("label",d),label:i.label},u=[s],r.push(u),f.append(s.element);else if(u=
+r[x],s=u[0],s.label!=d)s.element.attr("label",s.label=d);B=null;z=0;for(y=i.length;z<y;z++)if(d=i[z],v=u[z+1]){B=v.element;if(v.label!==d.label)B.text(v.label=d.label);if(v.id!==d.id)B.val(v.id=d.id);if(B[0].selected!==d.selected)B.prop("selected",v.selected=d.selected)}else d.id===""&&t?E=t:(E=C.clone()).val(d.id).attr("selected",d.selected).text(d.label),u.push({element:E,label:d.label,id:d.id,selected:d.selected}),B?B.after(E):s.element.append(E),B=E;for(z++;u.length>z;)u.pop().element.remove()}for(;r.length>
+x;)r.pop()[0].element.remove()}var i;if(!(i=s.match(d)))throw Error("Expected ngOptions in form of '_select_ (as _label_)? for (_key_,)?_value_ in _collection_' but got '"+s+"'.");var j=c(i[2]||i[1]),k=i[4]||i[6],l=i[5],m=c(i[3]||""),n=c(i[2]?i[1]:k),o=c(i[7]),r=[[{element:f,label:""}]];t&&(a(t)(e),t.removeClass("ng-scope"),t.remove());f.html("");f.bind("change",function(){e.$apply(function(){var a,c=o(e)||[],d={},h,i,j,m,s,t;if(p){i=[];m=0;for(t=r.length;m<t;m++){a=r[m];j=1;for(s=a.length;j<s;j++)if((h=
+a[j].element)[0].selected)h=h.val(),l&&(d[l]=h),d[k]=c[h],i.push(n(e,d))}}else h=f.val(),h=="?"?i=q:h==""?i=null:(d[k]=c[h],l&&(d[l]=h),i=n(e,d));g.$setViewValue(i)})});g.$render=h;e.$watch(h)}if(i[1]){for(var n=i[0],o=i[1],p=f.multiple,s=f.ngOptions,t=!1,v,C=u(T.createElement("option")),A=u(T.createElement("optgroup")),z=C.clone(),i=0,B=h.children(),r=B.length;i<r;i++)if(B[i].value==""){v=t=B.eq(i);break}n.init(o,t,z);if(p&&(f.required||f.ngRequired)){var E=function(a){o.$setValidity("required",
+!f.required||a&&a.length);return a};o.$parsers.push(E);o.$formatters.unshift(E);f.$observe("required",function(){E(o.$viewValue)})}s?l(e,h,o):p?k(e,h,o):j(e,h,o,n)}}}}],Wd=["$interpolate",function(a){var c={addOption:C,removeOption:C};return{restrict:"E",priority:100,compile:function(d,e){if(w(e.value)){var g=a(d.text(),!0);g||e.$set("value",d.text())}return function(a,d,e){var j=d.parent(),k=j.data("$selectController")||j.parent().data("$selectController");k&&k.databound?d.prop("selected",!1):k=
+c;g?a.$watch(g,function(a,c){e.$set("value",a);a!==c&&k.removeOption(c);k.addOption(a)}):k.addOption(e.value);d.bind("$destroy",function(){k.removeOption(e.value)})}}}}],Xd=I({restrict:"E",terminal:!0});(ca=P.jQuery)?(u=ca,v(ca.fn,{scope:ua.scope,controller:ua.controller,injector:ua.injector,inheritedData:ua.inheritedData}),ab("remove",!0),ab("empty"),ab("html")):u=K;Ya.element=u;(function(a){v(a,{bootstrap:rb,copy:U,extend:v,equals:fa,element:u,forEach:m,injector:sb,noop:C,bind:Ta,toJson:da,fromJson:pb,
+identity:ma,isUndefined:w,isDefined:y,isString:B,isFunction:H,isObject:L,isNumber:Qa,isElement:gc,isArray:E,version:jd,isDate:na,lowercase:z,uppercase:la,callbacks:{counter:0}});sa=lc(P);try{sa("ngLocale")}catch(c){sa("ngLocale",[]).provider("$locale",$c)}sa("ng",["ngLocale"],["$provide",function(a){a.provider("$compile",Db).directive({a:kd,input:cc,textarea:cc,form:ld,script:Td,select:Vd,style:Xd,option:Wd,ngBind:wd,ngBindHtmlUnsafe:yd,ngBindTemplate:xd,ngClass:zd,ngClassEven:Bd,ngClassOdd:Ad,ngCsp:Ed,
+ngCloak:Cd,ngController:Dd,ngForm:md,ngHide:Md,ngInclude:Gd,ngInit:Hd,ngNonBindable:Id,ngPluralize:Jd,ngRepeat:Kd,ngShow:Ld,ngSubmit:Fd,ngStyle:Nd,ngSwitch:Od,ngSwitchWhen:Pd,ngSwitchDefault:Qd,ngOptions:Ud,ngView:Sd,ngTransclude:Rd,ngModel:rd,ngList:td,ngChange:sd,required:dc,ngRequired:dc,ngValue:vd}).directive(lb).directive(ec);a.provider({$anchorScroll:uc,$browser:wc,$cacheFactory:xc,$controller:Cc,$document:Dc,$exceptionHandler:Ec,$filter:Rb,$interpolate:Fc,$http:Wc,$httpBackend:Xc,$location:Jc,
+$log:Kc,$parse:Oc,$route:Rc,$routeParams:Sc,$rootScope:Tc,$q:Pc,$sniffer:Uc,$templateCache:yc,$timeout:ad,$window:Vc})}])})(Ya);u(T).ready(function(){jc(T,rb)})})(window,document);angular.element(document).find("head").append('<style type="text/css">@charset "UTF-8";[ng\\:cloak],[ng-cloak],[data-ng-cloak],[x-ng-cloak],.ng-cloak,.x-ng-cloak{display:none;}ng\\:form{display:block;}</style>');
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/pages/ActivityPage.html b/src/main/java/com/gitblit/wicket/pages/ActivityPage.html
new file mode 100644
index 0000000..87e599f
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/ActivityPage.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"  
+      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"  
+      xml:lang="en"  
+      lang="en"> 
+<body>
+<wicket:extend>
+	<div class="container">
+	<div class="dashboardTitle">
+		<wicket:message key="gb.recentActivity"></wicket:message> <small><span class="hidden-phone"><span wicket:id="subheader">[days back]</span></span></small>
+	</div>
+	<div class="hidden-phone" style="text-align: center;">
+		<table>
+		<tr>
+		<td><div style="width:310px; height:150px" class="hidden-tablet" id="chartDaily"></div></td>
+		<td><div style="width:310px; height:175px" id="chartRepositories"></div></td>
+		<td><div style="width:310px; height:175px" id="chartAuthors"></div></td>
+		</tr>
+		</table>
+	</div>
+	<div wicket:id="activityPanel" style="padding-top:5px;" >[activity panel]</div>
+	</div>
+</wicket:extend>
+</body>
+</html>
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/pages/ActivityPage.java b/src/main/java/com/gitblit/wicket/pages/ActivityPage.java
new file mode 100644
index 0000000..413403b
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/ActivityPage.java
@@ -0,0 +1,222 @@
+/*
+ * 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.wicket.pages;
+
+import java.text.MessageFormat;
+import java.text.SimpleDateFormat;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.wicket.PageParameters;
+import org.apache.wicket.behavior.HeaderContributor;
+import org.apache.wicket.markup.html.basic.Label;
+
+import com.gitblit.GitBlit;
+import com.gitblit.Keys;
+import com.gitblit.models.Activity;
+import com.gitblit.models.Metric;
+import com.gitblit.models.RepositoryModel;
+import com.gitblit.utils.ActivityUtils;
+import com.gitblit.utils.StringUtils;
+import com.gitblit.wicket.CacheControl;
+import com.gitblit.wicket.PageRegistration;
+import com.gitblit.wicket.CacheControl.LastModified;
+import com.gitblit.wicket.PageRegistration.DropDownMenuItem;
+import com.gitblit.wicket.PageRegistration.DropDownMenuRegistration;
+import com.gitblit.wicket.WicketUtils;
+import com.gitblit.wicket.charting.GoogleChart;
+import com.gitblit.wicket.charting.GoogleCharts;
+import com.gitblit.wicket.charting.GoogleLineChart;
+import com.gitblit.wicket.charting.GooglePieChart;
+import com.gitblit.wicket.panels.ActivityPanel;
+
+/**
+ * Activity Page shows a list of recent commits across all visible Gitblit
+ * repositories.
+ * 
+ * @author James Moger
+ * 
+ */
+
+@CacheControl(LastModified.ACTIVITY)
+public class ActivityPage extends RootPage {
+
+	public ActivityPage(PageParameters params) {
+		super(params);
+		setupPage("", "");
+
+		// parameters
+		int daysBack = WicketUtils.getDaysBack(params);
+		if (daysBack < 1) {
+			daysBack = GitBlit.getInteger(Keys.web.activityDuration, 7);
+		}
+		String objectId = WicketUtils.getObject(params);
+
+		// determine repositories to view and retrieve the activity
+		List<RepositoryModel> models = getRepositories(params);
+		List<Activity> recentActivity = ActivityUtils.getRecentActivity(models, 
+				daysBack, objectId, getTimeZone());
+
+		String headerPattern;
+		if (daysBack == 1) {
+			// today
+			if (recentActivity.size() == 0) {
+				headerPattern = getString("gb.todaysActivityNone");
+			} else {
+				headerPattern = getString("gb.todaysActivityStats");
+			}
+		} else {
+			// multiple days
+			if (recentActivity.size() == 0) {
+				headerPattern = getString("gb.recentActivityNone");
+			} else {
+				headerPattern = getString("gb.recentActivityStats");
+			}
+		}
+		
+		if (recentActivity.size() == 0) {
+			// no activity, skip graphs and activity panel
+			add(new Label("subheader", MessageFormat.format(headerPattern,
+					daysBack)));
+			add(new Label("activityPanel"));
+		} else {
+			// calculate total commits and total authors
+			int totalCommits = 0;
+			Set<String> uniqueAuthors = new HashSet<String>();
+			for (Activity activity : recentActivity) {
+				totalCommits += activity.getCommitCount();
+				uniqueAuthors.addAll(activity.getAuthorMetrics().keySet());
+			}
+			int totalAuthors = uniqueAuthors.size();
+
+			// add the subheader with stat numbers
+			add(new Label("subheader", MessageFormat.format(headerPattern,
+					daysBack, totalCommits, totalAuthors)));
+
+			// create the activity charts
+			GoogleCharts charts = createCharts(recentActivity);
+			add(new HeaderContributor(charts));
+
+			// add activity panel
+			add(new ActivityPanel("activityPanel", recentActivity));
+		}
+	}
+
+	@Override
+	protected boolean reusePageParameters() {
+		return true;
+	}
+
+	@Override
+	protected void addDropDownMenus(List<PageRegistration> pages) {
+		DropDownMenuRegistration filters = new DropDownMenuRegistration("gb.filters",
+				ActivityPage.class);
+
+		PageParameters currentParameters = getPageParameters();
+		int daysBack = GitBlit.getInteger(Keys.web.activityDuration, 7);
+		if (currentParameters != null && !currentParameters.containsKey("db")) {
+			currentParameters.put("db", daysBack);
+		}
+
+		// preserve time filter options on repository choices
+		filters.menuItems.addAll(getRepositoryFilterItems(currentParameters));
+
+		// preserve repository filter options on time choices
+		filters.menuItems.addAll(getTimeFilterItems(currentParameters));
+
+		if (filters.menuItems.size() > 0) {
+			// Reset Filter
+			filters.menuItems.add(new DropDownMenuItem(getString("gb.reset"), null, null));
+		}
+		pages.add(filters);
+	}
+
+	/**
+	 * Creates the daily activity line chart, the active repositories pie chart,
+	 * and the active authors pie chart
+	 * 
+	 * @param recentActivity
+	 * @return
+	 */
+	private GoogleCharts createCharts(List<Activity> recentActivity) {
+		// activity metrics
+		Map<String, Metric> repositoryMetrics = new HashMap<String, Metric>();
+		Map<String, Metric> authorMetrics = new HashMap<String, Metric>();
+
+		// aggregate repository and author metrics
+		for (Activity activity : recentActivity) {
+
+			// aggregate author metrics
+			for (Map.Entry<String, Metric> entry : activity.getAuthorMetrics().entrySet()) {
+				String author = entry.getKey();
+				if (!authorMetrics.containsKey(author)) {
+					authorMetrics.put(author, new Metric(author));
+				}
+				authorMetrics.get(author).count += entry.getValue().count;
+			}
+
+			// aggregate repository metrics
+			for (Map.Entry<String, Metric> entry : activity.getRepositoryMetrics().entrySet()) {
+				String repository = StringUtils.stripDotGit(entry.getKey());
+				if (!repositoryMetrics.containsKey(repository)) {
+					repositoryMetrics.put(repository, new Metric(repository));
+				}
+				repositoryMetrics.get(repository).count += entry.getValue().count;
+			}
+		}
+
+		// build google charts
+		GoogleCharts charts = new GoogleCharts();
+
+		// sort in reverse-chronological order and then reverse that
+		Collections.sort(recentActivity);
+		Collections.reverse(recentActivity);
+
+		// daily line chart
+		GoogleChart chart = new GoogleLineChart("chartDaily", getString("gb.dailyActivity"), "day",
+				getString("gb.commits"));
+		SimpleDateFormat df = new SimpleDateFormat("MMM dd");
+		df.setTimeZone(getTimeZone());
+		for (Activity metric : recentActivity) {
+			chart.addValue(df.format(metric.startDate), metric.getCommitCount());
+		}
+		charts.addChart(chart);
+
+		// active repositories pie chart
+		chart = new GooglePieChart("chartRepositories", getString("gb.activeRepositories"),
+				getString("gb.repository"), getString("gb.commits"));
+		for (Metric metric : repositoryMetrics.values()) {
+			chart.addValue(metric.name, metric.count);
+		}
+		chart.setShowLegend(false);
+		charts.addChart(chart);
+
+		// active authors pie chart
+		chart = new GooglePieChart("chartAuthors", getString("gb.activeAuthors"),
+				getString("gb.author"), getString("gb.commits"));
+		for (Metric metric : authorMetrics.values()) {
+			chart.addValue(metric.name, metric.count);
+		}
+		chart.setShowLegend(false);
+		charts.addChart(chart);
+
+		return charts;
+	}
+}
diff --git a/src/main/java/com/gitblit/wicket/pages/BasePage.html b/src/main/java/com/gitblit/wicket/pages/BasePage.html
new file mode 100644
index 0000000..e0840b2
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/BasePage.html
@@ -0,0 +1,53 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"  
+      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"  
+      xml:lang="en"  
+      lang="en"
+      ng-app> 
+
+	<!-- Head -->
+	<wicket:head>
+		<meta name="viewport" content="width=device-width, initial-scale=1.0">
+		<meta http-equiv="X-UA-Compatible" content="IE=Edge" />
+   		<title wicket:id="title">[page title]</title>
+		<link rel="icon" href="gitblt-favicon.png" type="image/png" />
+		
+		<link rel="stylesheet" href="bootstrap/css/bootstrap.css"/>
+		<link rel="stylesheet" href="bootstrap/css/iconic.css"/>
+		<link rel="stylesheet" type="text/css" href="gitblit.css"/>
+	</wicket:head>
+
+	<body>
+
+		<!-- page content -->
+		<wicket:child />
+		
+		<!-- page footer -->
+		<div class="container">
+			<footer class="footer">
+				<p class="pull-right">
+					<a title="gitblit homepage" href="http://gitblit.com/">
+						<span wicket:id="gbVersion"></span>
+					</a> 
+				</p>
+			</footer>
+		</div>
+
+		<!-- Override Bootstrap's responsive menu background highlighting -->
+		<style>
+		@media (max-width: 979px) {
+			.nav-collapse .nav > li > a:hover, .nav-collapse .dropdown-menu a:hover {
+				background-color: #002060;
+			}
+			
+			.navbar div > ul .dropdown-menu li a {
+				color: #ccc;
+			}
+		}
+		</style>
+		
+		<!-- Include scripts at end for faster page loading -->
+		<script type="text/javascript" src="bootstrap/js/jquery.js"></script>
+		<script type="text/javascript" src="bootstrap/js/bootstrap.js"></script>		
+	</body>
+</html>
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/pages/BasePage.java b/src/main/java/com/gitblit/wicket/pages/BasePage.java
new file mode 100644
index 0000000..c9e11b0
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/BasePage.java
@@ -0,0 +1,437 @@
+/*
+ * 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.wicket.pages;
+
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.ResourceBundle;
+import java.util.Set;
+import java.util.TimeZone;
+import java.util.regex.Pattern;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.wicket.Application;
+import org.apache.wicket.Page;
+import org.apache.wicket.PageParameters;
+import org.apache.wicket.RedirectToUrlException;
+import org.apache.wicket.markup.html.CSSPackageResource;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.link.ExternalLink;
+import org.apache.wicket.markup.html.panel.FeedbackPanel;
+import org.apache.wicket.protocol.http.RequestUtils;
+import org.apache.wicket.protocol.http.WebResponse;
+import org.apache.wicket.protocol.http.servlet.ServletWebRequest;
+import org.apache.wicket.util.time.Duration;
+import org.apache.wicket.util.time.Time;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.gitblit.Constants;
+import com.gitblit.Constants.AccessPermission;
+import com.gitblit.Constants.AccessRestrictionType;
+import com.gitblit.Constants.AuthorizationControl;
+import com.gitblit.Constants.FederationStrategy;
+import com.gitblit.GitBlit;
+import com.gitblit.Keys;
+import com.gitblit.models.ProjectModel;
+import com.gitblit.models.TeamModel;
+import com.gitblit.models.UserModel;
+import com.gitblit.utils.StringUtils;
+import com.gitblit.utils.TimeUtils;
+import com.gitblit.wicket.CacheControl;
+import com.gitblit.wicket.GitBlitWebApp;
+import com.gitblit.wicket.GitBlitWebSession;
+import com.gitblit.wicket.WicketUtils;
+
+public abstract class BasePage extends SessionPage {
+
+	private final Logger logger;
+	
+	private transient TimeUtils timeUtils;
+
+	public BasePage() {
+		super();
+		logger = LoggerFactory.getLogger(getClass());
+		customizeHeader();
+	}
+
+	public BasePage(PageParameters params) {
+		super(params);
+		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;		
+			try {
+				bundle = ResourceBundle.getBundle("com.gitblit.wicket.GitBlitWebApp", GitBlitWebSession.get().getLocale());
+			} catch (Throwable t) {
+				bundle = ResourceBundle.getBundle("com.gitblit.wicket.GitBlitWebApp");
+			}
+			timeUtils = new TimeUtils(bundle, getTimeZone());
+		}
+		return timeUtils;
+	}
+	
+	@Override
+	protected void onBeforeRender() {
+		if (GitBlit.isDebugMode()) {
+			// strip Wicket tags in debug mode for jQuery DOM traversal
+			Application.get().getMarkupSettings().setStripWicketTags(true);
+		}
+		super.onBeforeRender();
+	}
+
+	@Override
+	protected void onAfterRender() {
+		if (GitBlit.isDebugMode()) {
+			// restore Wicket debug tags
+			Application.get().getMarkupSettings().setStripWicketTags(false);
+		}
+		super.onAfterRender();
+	}
+		
+	@Override
+	protected void setHeaders(WebResponse response)	{
+		int expires = GitBlit.getInteger(Keys.web.pageCacheExpires, 0);
+		if (expires > 0) {
+			// pages are personalized for the authenticated user so they must be
+			// marked private to prohibit proxy servers from caching them
+			response.setHeader("Cache-Control", "private, must-revalidate");
+			setLastModified();
+		} else {
+			// use default Wicket caching behavior
+			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)) {
+			CacheControl cacheControl = getClass().getAnnotation(CacheControl.class);
+			switch (cacheControl.value()) {
+			case ACTIVITY:
+				setLastModified(GitBlit.getLastActivityDate());
+				break;
+			case BOOT:
+				setLastModified(GitBlit.getBootDate());
+				break;
+			case NONE:
+				break;
+			default:
+				logger.warn(getClass().getSimpleName() + ": unhandled LastModified type " + cacheControl.value());
+				break;
+			}
+		}
+	}
+	
+	/**
+	 * 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));
+		response.setDateHeader("Expires", System.currentTimeMillis() + Duration.minutes(expires).getMilliseconds());
+	}
+
+	protected void setupPage(String repositoryName, String pageName) {
+		String siteName = GitBlit.getString(Keys.web.siteName, Constants.NAME);
+		if (StringUtils.isEmpty(siteName)) {
+			siteName = Constants.NAME;
+		}
+		if (repositoryName != null && repositoryName.trim().length() > 0) {
+			add(new Label("title", repositoryName + " - " + siteName));
+		} else {
+			add(new Label("title", siteName));
+		}
+
+		ExternalLink rootLink = new ExternalLink("rootLink", urlFor(GitBlitWebApp.HOME_PAGE_CLASS, null).toString());
+		WicketUtils.setHtmlTooltip(rootLink, GitBlit.getString(Keys.web.siteName, Constants.NAME));
+		add(rootLink);
+
+		// Feedback panel for info, warning, and non-fatal error messages
+		add(new FeedbackPanel("feedback"));
+
+		add(new Label("gbVersion", "v" + Constants.getVersion()));
+		if (GitBlit.getBoolean(Keys.web.aggressiveHeapManagement, false)) {
+			System.gc();
+		}
+	}
+
+	protected Map<AccessRestrictionType, String> getAccessRestrictions() {
+		Map<AccessRestrictionType, String> map = new LinkedHashMap<AccessRestrictionType, String>();
+		for (AccessRestrictionType type : AccessRestrictionType.values()) {
+			switch (type) {
+			case NONE:
+				map.put(type, getString("gb.notRestricted"));
+				break;
+			case PUSH:
+				map.put(type, getString("gb.pushRestricted"));
+				break;
+			case CLONE:
+				map.put(type, getString("gb.cloneRestricted"));
+				break;
+			case VIEW:
+				map.put(type, getString("gb.viewRestricted"));
+				break;
+			}
+		}
+		return map;
+	}
+	
+	protected Map<AccessPermission, String> getAccessPermissions() {
+		Map<AccessPermission, String> map = new LinkedHashMap<AccessPermission, String>();
+		for (AccessPermission type : AccessPermission.values()) {
+			switch (type) {
+			case NONE:
+				map.put(type, MessageFormat.format(getString("gb.noPermission"), type.code));
+				break;
+			case EXCLUDE:
+				map.put(type, MessageFormat.format(getString("gb.excludePermission"), type.code));
+				break;
+			case VIEW:
+				map.put(type, MessageFormat.format(getString("gb.viewPermission"), type.code));
+				break;
+			case CLONE:
+				map.put(type, MessageFormat.format(getString("gb.clonePermission"), type.code));
+				break;
+			case PUSH:
+				map.put(type, MessageFormat.format(getString("gb.pushPermission"), type.code));
+				break;
+			case CREATE:
+				map.put(type, MessageFormat.format(getString("gb.createPermission"), type.code));
+				break;
+			case DELETE:
+				map.put(type, MessageFormat.format(getString("gb.deletePermission"), type.code));
+				break;
+			case REWIND:
+				map.put(type, MessageFormat.format(getString("gb.rewindPermission"), type.code));
+				break;
+			}
+		}
+		return map;
+	}
+	
+	protected Map<FederationStrategy, String> getFederationTypes() {
+		Map<FederationStrategy, String> map = new LinkedHashMap<FederationStrategy, String>();
+		for (FederationStrategy type : FederationStrategy.values()) {
+			switch (type) {
+			case EXCLUDE:
+				map.put(type, getString("gb.excludeFromFederation"));
+				break;
+			case FEDERATE_THIS:
+				map.put(type, getString("gb.federateThis"));
+				break;
+			case FEDERATE_ORIGIN:
+				map.put(type, getString("gb.federateOrigin"));
+				break;
+			}
+		}
+		return map;
+	}
+	
+	protected Map<AuthorizationControl, String> getAuthorizationControls() {
+		Map<AuthorizationControl, String> map = new LinkedHashMap<AuthorizationControl, String>();
+		for (AuthorizationControl type : AuthorizationControl.values()) {
+			switch (type) {
+			case AUTHENTICATED:
+				map.put(type, getString("gb.allowAuthenticatedDescription"));
+				break;
+			case NAMED:
+				map.put(type, getString("gb.allowNamedDescription"));
+				break;
+			}
+		}
+		return map;
+	}
+
+	protected TimeZone getTimeZone() {
+		return GitBlit.getBoolean(Keys.web.useClientTimezone, false) ? GitBlitWebSession.get()
+				.getTimezone() : GitBlit.getTimezone();
+	}
+
+	protected String getServerName() {
+		ServletWebRequest servletWebRequest = (ServletWebRequest) getRequest();
+		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();
+		}
+
+		boolean hasParameter = false;
+		String regex = WicketUtils.getRegEx(params);
+		String team = WicketUtils.getTeam(params);
+		int daysBack = params.getInt("db", 0);
+
+		List<ProjectModel> availableModels = getProjectModels();
+		Set<ProjectModel> models = new HashSet<ProjectModel>();
+
+		if (!StringUtils.isEmpty(regex)) {
+			// filter the projects by the regex
+			hasParameter = true;
+			Pattern pattern = Pattern.compile(regex);
+			for (ProjectModel model : availableModels) {
+				if (pattern.matcher(model.name).find()) {
+					models.add(model);
+				}
+			}
+		}
+
+		if (!StringUtils.isEmpty(team)) {
+			// filter the projects by the specified teams
+			hasParameter = true;
+			List<String> teams = StringUtils.getStringsFromValue(team, ",");
+
+			// need TeamModels first
+			List<TeamModel> teamModels = new ArrayList<TeamModel>();
+			for (String name : teams) {
+				TeamModel teamModel = GitBlit.self().getTeamModel(name);
+				if (teamModel != null) {
+					teamModels.add(teamModel);
+				}
+			}
+
+			// brute-force our way through finding the matching models
+			for (ProjectModel projectModel : availableModels) {
+				for (String repositoryName : projectModel.repositories) {
+					for (TeamModel teamModel : teamModels) {
+						if (teamModel.hasRepositoryPermission(repositoryName)) {
+							models.add(projectModel);
+						}
+					}
+				}
+			}
+		}
+
+		if (!hasParameter) {
+			models.addAll(availableModels);
+		}
+
+		// time-filter the list
+		if (daysBack > 0) {
+			Calendar cal = Calendar.getInstance();
+			cal.set(Calendar.HOUR_OF_DAY, 0);
+			cal.set(Calendar.MINUTE, 0);
+			cal.set(Calendar.SECOND, 0);
+			cal.set(Calendar.MILLISECOND, 0);
+			cal.add(Calendar.DATE, -1 * daysBack);
+			Date threshold = cal.getTime();
+			Set<ProjectModel> timeFiltered = new HashSet<ProjectModel>();
+			for (ProjectModel model : models) {
+				if (model.lastChange.after(threshold)) {
+					timeFiltered.add(model);
+				}
+			}
+			models = timeFiltered;
+		}
+
+		List<ProjectModel> list = new ArrayList<ProjectModel>(models);
+		Collections.sort(list);
+		return list;
+	}
+
+	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);
+	}
+
+	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());
+		} else {
+			logger.error(message  + " for " + GitBlitWebSession.get().getUsername(), t);
+		}
+		if (toPage != null) {
+			GitBlitWebSession.get().cacheErrorMessage(message);
+			String relativeUrl = urlFor(toPage, params).toString();
+			String absoluteUrl = RequestUtils.toAbsolutePath(relativeUrl);
+			throw new RedirectToUrlException(absoluteUrl);
+		} else {
+			super.error(message);
+		}
+	}
+
+	public void authenticationError(String message) {
+		logger.error(getRequest().getURL() + " for " + GitBlitWebSession.get().getUsername());
+		if (!GitBlitWebSession.get().isLoggedIn()) {
+			// cache the request if we have not authenticated.
+			// the request will continue after authentication.
+			GitBlitWebSession.get().cacheRequest(getClass());
+		}
+		error(message, true);
+	}
+}
diff --git a/src/main/java/com/gitblit/wicket/pages/BlamePage.html b/src/main/java/com/gitblit/wicket/pages/BlamePage.html
new file mode 100644
index 0000000..722cf3d
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/BlamePage.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"  
+      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"  
+      xml:lang="en"  
+      lang="en"> 
+
+<body>
+<wicket:extend>
+	
+	<!-- blame nav links -->	
+	<div class="page_nav2">
+		<a wicket:id="blobLink"><wicket:message key="gb.view"></wicket:message></a> | <a wicket:id="historyLink"><wicket:message key="gb.history"></wicket:message></a> | <a wicket:id="headLink"><wicket:message key="gb.head"></wicket:message></a> | <a wicket:id="commitLink"><wicket:message key="gb.commit"></wicket:message></a> | <a wicket:id="commitDiffLink"><wicket:message key="gb.commitdiff"></wicket:message></a>
+	</div>	
+	
+	<!-- commit header -->
+	<div wicket:id="commitHeader">[commit header]</div>
+
+	<!-- breadcrumbs -->
+	<div wicket:id="breadcrumbs">[breadcrumbs]</div>
+	
+	<div wicket:id="missingBlob">[missing blob]</div>
+		
+	<!--  blame content -->
+	<table class="annotated" style="margin-bottom:5px;">
+		<tbody>
+			<tr>
+				<th><wicket:message key="gb.commit">[commit]</wicket:message></th>
+				<th><wicket:message key="gb.line">[line]</wicket:message></th>
+				<th><wicket:message key="gb.content">[content]</wicket:message></th>
+			</tr>
+			<tr wicket:id="annotation">
+				<td><span class="sha1" wicket:id="commit"></span></td>
+				<td><span class="sha1" wicket:id="line"></span></td>
+				<td><span class="sha1" wicket:id="data"></span></td>
+			</tr>
+		</tbody>
+	</table>
+	
+</wicket:extend>
+</body>
+</html>
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/pages/BlamePage.java b/src/main/java/com/gitblit/wicket/pages/BlamePage.java
new file mode 100644
index 0000000..53bd233
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/BlamePage.java
@@ -0,0 +1,174 @@
+/*
+ * 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.wicket.pages;
+
+import java.text.DateFormat;
+import java.text.MessageFormat;
+import java.text.SimpleDateFormat;
+import java.util.List;
+
+import org.apache.wicket.PageParameters;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.link.BookmarkablePageLink;
+import org.apache.wicket.markup.repeater.Item;
+import org.apache.wicket.markup.repeater.data.DataView;
+import org.apache.wicket.markup.repeater.data.ListDataProvider;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.revwalk.RevCommit;
+
+import com.gitblit.GitBlit;
+import com.gitblit.Keys;
+import com.gitblit.models.AnnotatedLine;
+import com.gitblit.models.PathModel;
+import com.gitblit.utils.DiffUtils;
+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.WicketUtils;
+import com.gitblit.wicket.panels.CommitHeaderPanel;
+import com.gitblit.wicket.panels.LinkPanel;
+import com.gitblit.wicket.panels.PathBreadcrumbsPanel;
+
+@CacheControl(LastModified.BOOT)
+public class BlamePage extends RepositoryPage {
+
+	public BlamePage(PageParameters params) {
+		super(params);
+
+		final String blobPath = WicketUtils.getPath(params);
+
+		RevCommit commit = getCommit();
+
+		add(new BookmarkablePageLink<Void>("blobLink", BlobPage.class,
+				WicketUtils.newPathParameter(repositoryName, objectId, blobPath)));
+		add(new BookmarkablePageLink<Void>("commitLink", CommitPage.class,
+				WicketUtils.newObjectParameter(repositoryName, objectId)));
+		add(new BookmarkablePageLink<Void>("commitDiffLink", CommitDiffPage.class,
+				WicketUtils.newObjectParameter(repositoryName, objectId)));
+
+		// blame page links
+		add(new BookmarkablePageLink<Void>("headLink", BlamePage.class,
+				WicketUtils.newPathParameter(repositoryName, Constants.HEAD, blobPath)));
+		add(new BookmarkablePageLink<Void>("historyLink", HistoryPage.class,
+				WicketUtils.newPathParameter(repositoryName, objectId, blobPath)));
+
+		add(new CommitHeaderPanel("commitHeader", repositoryName, commit));
+
+		add(new PathBreadcrumbsPanel("breadcrumbs", repositoryName, blobPath, objectId));
+
+		String format = GitBlit.getString(Keys.web.datetimestampLongFormat,
+				"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) {
+			if (path.path.equals(blobPath)) {
+				pathModel = path;
+				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) {
+			private static final long serialVersionUID = 1L;
+			private int count;
+			private String lastCommitId = "";
+			private boolean showInitials = true;
+			private String zeroId = ObjectId.zeroId().getName();
+
+			public void populateItem(final Item<AnnotatedLine> item) {
+				AnnotatedLine entry = item.getModelObject();
+				item.add(new Label("line", "" + entry.lineNumber));
+				item.add(new Label("data", StringUtils.escapeForHtml(entry.data, true))
+						.setEscapeModelStrings(false));
+				if (!lastCommitId.equals(entry.commitId)) {
+					lastCommitId = entry.commitId;
+					count++;
+					if (zeroId.equals(entry.commitId)) {
+						// unknown commit
+						item.add(new Label("commit", "<?>"));
+						showInitials = false;
+					} else {
+						// show the link for first line
+						LinkPanel commitLink = new LinkPanel("commit", null,
+								getShortObjectId(entry.commitId), CommitPage.class,
+								newCommitParameter(entry.commitId));
+						WicketUtils.setHtmlTooltip(commitLink,
+								MessageFormat.format("{0}, {1}", entry.author, df.format(entry.when)));
+						item.add(commitLink);
+						showInitials = true;
+					}
+				} else {
+					if (showInitials) {
+						showInitials = false;
+						// show author initials
+						item.add(new Label("commit", getInitials(entry.author)));
+					} else {
+						// hide the commit link until the next block
+						item.add(new Label("commit").setVisible(false));
+					}
+				}
+				if (count % 2 == 0) {
+					WicketUtils.setCssClass(item, "even");
+				} else {
+					WicketUtils.setCssClass(item, "odd");
+				}
+			}
+		};
+		add(blameView);
+	}
+
+	private String getInitials(String author) {
+		StringBuilder sb = new StringBuilder();
+		String[] chunks = author.split(" ");
+		for (String chunk : chunks) {
+			sb.append(chunk.charAt(0));
+		}
+		return sb.toString().toUpperCase();
+	}
+
+	@Override
+	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\">");
+		String pattern = getString("gb.doesNotExistInTree").replace("{0}", "<b>{0}</b>").replace("{1}", "<b>{1}</b>");
+		sb.append(MessageFormat.format(pattern, blobPath, commit.getTree().getId().getName()));
+		sb.append("</div>");
+		return sb.toString();
+	}
+}
diff --git a/src/com/gitblit/wicket/pages/BlobDiffPage.html b/src/main/java/com/gitblit/wicket/pages/BlobDiffPage.html
similarity index 100%
rename from src/com/gitblit/wicket/pages/BlobDiffPage.html
rename to src/main/java/com/gitblit/wicket/pages/BlobDiffPage.html
diff --git a/src/main/java/com/gitblit/wicket/pages/BlobDiffPage.java b/src/main/java/com/gitblit/wicket/pages/BlobDiffPage.java
new file mode 100644
index 0000000..c297bca
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/BlobDiffPage.java
@@ -0,0 +1,93 @@
+/*
+ * 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.wicket.pages;
+
+import org.apache.wicket.PageParameters;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.link.BookmarkablePageLink;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+
+import com.gitblit.GitBlit;
+import com.gitblit.Keys;
+import com.gitblit.utils.DiffUtils;
+import com.gitblit.utils.DiffUtils.DiffOutputType;
+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.WicketUtils;
+import com.gitblit.wicket.panels.CommitHeaderPanel;
+import com.gitblit.wicket.panels.PathBreadcrumbsPanel;
+
+@CacheControl(LastModified.BOOT)
+public class BlobDiffPage extends RepositoryPage {
+
+	public BlobDiffPage(PageParameters params) {
+		super(params);
+
+		final String blobPath = WicketUtils.getPath(params);
+		final String baseObjectId = WicketUtils.getBaseObjectId(params);
+
+		Repository r = getRepository();
+		RevCommit commit = getCommit();
+
+		DiffOutputType diffType = DiffOutputType.forName(GitBlit.getString(Keys.web.diffStyle,
+				DiffOutputType.GITBLIT.name()));
+
+		String diff;
+		if (StringUtils.isEmpty(baseObjectId)) {
+			// use first parent
+			diff = DiffUtils.getDiff(r, commit, blobPath, diffType);
+			add(new BookmarkablePageLink<Void>("patchLink", PatchPage.class,
+					WicketUtils.newPathParameter(repositoryName, objectId, blobPath)));
+		} else {
+			// base commit specified
+			RevCommit baseCommit = JGitUtils.getCommit(r, baseObjectId);
+			diff = DiffUtils.getDiff(r, baseCommit, commit, blobPath, diffType);
+			add(new BookmarkablePageLink<Void>("patchLink", PatchPage.class,
+					WicketUtils.newBlobDiffParameter(repositoryName, baseObjectId, objectId,
+							blobPath)));
+		}
+
+		add(new BookmarkablePageLink<Void>("commitLink", CommitPage.class,
+				WicketUtils.newObjectParameter(repositoryName, objectId)));
+		add(new BookmarkablePageLink<Void>("commitDiffLink", CommitDiffPage.class,
+				WicketUtils.newObjectParameter(repositoryName, objectId)));
+
+		// diff page links
+		add(new BookmarkablePageLink<Void>("blameLink", BlamePage.class,
+				WicketUtils.newPathParameter(repositoryName, objectId, blobPath)));
+		add(new BookmarkablePageLink<Void>("historyLink", HistoryPage.class,
+				WicketUtils.newPathParameter(repositoryName, objectId, blobPath)));
+
+		add(new CommitHeaderPanel("commitHeader", repositoryName, commit));
+
+		add(new PathBreadcrumbsPanel("breadcrumbs", repositoryName, blobPath, objectId));
+
+		add(new Label("diffText", diff).setEscapeModelStrings(false));
+	}
+
+	@Override
+	protected String getPageName() {
+		return getString("gb.diff");
+	}
+	
+	@Override
+	protected Class<? extends BasePage> getRepoNavPageClass() {
+		return TreePage.class;
+	}
+}
diff --git a/src/main/java/com/gitblit/wicket/pages/BlobPage.html b/src/main/java/com/gitblit/wicket/pages/BlobPage.html
new file mode 100644
index 0000000..d45e3c3
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/BlobPage.html
@@ -0,0 +1,66 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"  
+      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"  
+      xml:lang="en"  
+      lang="en">
+      
+<!-- contribute google-code-prettify resources to the page header -->
+<wicket:head>
+  <wicket:link>
+   	<link href="prettify/prettify.css" type="text/css" rel="stylesheet" />
+	<script type="text/javascript" src="prettify/prettify.js"></script>
+	<script type="text/javascript" src="prettify/lang-apollo.js"></script>
+	<script type="text/javascript" src="prettify/lang-basic.js"></script>
+	<script type="text/javascript" src="prettify/lang-clj.js"></script>
+	<script type="text/javascript" src="prettify/lang-css.js"></script>
+	<script type="text/javascript" src="prettify/lang-dart.js"></script>
+	<script type="text/javascript" src="prettify/lang-erlang.js"></script>
+	<script type="text/javascript" src="prettify/lang-go.js"></script>
+	<script type="text/javascript" src="prettify/lang-hs.js"></script>
+	<script type="text/javascript" src="prettify/lang-lisp.js"></script>
+	<script type="text/javascript" src="prettify/lang-llvm.js"></script>
+	<script type="text/javascript" src="prettify/lang-lua.js"></script>
+	<script type="text/javascript" src="prettify/lang-matlab.js"></script>
+	<script type="text/javascript" src="prettify/lang-ml.js"></script>
+	<script type="text/javascript" src="prettify/lang-mumps.js"></script>
+	<script type="text/javascript" src="prettify/lang-n.js"></script>
+	<script type="text/javascript" src="prettify/lang-pascal.js"></script>
+	<script type="text/javascript" src="prettify/lang-proto.js"></script>
+	<script type="text/javascript" src="prettify/lang-r.js"></script>
+	<script type="text/javascript" src="prettify/lang-rd.js"></script>
+	<script type="text/javascript" src="prettify/lang-scala.js"></script>
+	<script type="text/javascript" src="prettify/lang-sql.js"></script>
+	<script type="text/javascript" src="prettify/lang-tcl.js"></script>
+	<script type="text/javascript" src="prettify/lang-tex.js"></script>
+	<script type="text/javascript" src="prettify/lang-vb.js"></script>
+	<script type="text/javascript" src="prettify/lang-vhdl.js"></script>
+	<script type="text/javascript" src="prettify/lang-wiki.js"></script>
+	<script type="text/javascript" src="prettify/lang-xq.js"></script>
+	<script type="text/javascript" src="prettify/lang-yaml.js"></script>
+  </wicket:link>
+</wicket:head>
+
+<wicket:extend>
+<!-- need to specify body.onload -->
+<body onload="prettyPrint()">
+
+		<!-- blob nav links -->	
+		<div class="page_nav2">
+			<a wicket:id="blameLink"><wicket:message key="gb.blame"></wicket:message></a> | <a wicket:id="historyLink"><wicket:message key="gb.history"></wicket:message></a> | <a wicket:id="rawLink"><wicket:message key="gb.raw"></wicket:message></a> | <a wicket:id="headLink"><wicket:message key="gb.head"></wicket:message></a>
+		</div>	
+	
+		<!-- commit header -->
+		<div wicket:id="commitHeader">[commit header]</div>
+
+		<!-- breadcrumbs -->
+		<div wicket:id="breadcrumbs">[breadcrumbs]</div>
+		
+		<!--  blob content -->
+		<pre style="border:0px;" wicket:id="blobText">[blob content]</pre>
+
+		<!--  blob image -->
+		<img wicket:id="blobImage" style="padding-top:5px;"></img>
+	
+</body>
+</wicket:extend>
+</html>
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/pages/BlobPage.java b/src/main/java/com/gitblit/wicket/pages/BlobPage.java
new file mode 100644
index 0000000..b104df2
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/BlobPage.java
@@ -0,0 +1,224 @@
+/*
+ * 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.wicket.pages;
+
+import java.text.MessageFormat;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.wicket.Component;
+import org.apache.wicket.PageParameters;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.image.Image;
+import org.apache.wicket.markup.html.link.BookmarkablePageLink;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+
+import com.gitblit.GitBlit;
+import com.gitblit.Keys;
+import com.gitblit.utils.JGitUtils;
+import com.gitblit.utils.StringUtils;
+import com.gitblit.wicket.CacheControl;
+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;
+
+@CacheControl(LastModified.BOOT)
+public class BlobPage extends RepositoryPage {
+
+	protected String fileExtension;
+
+	public BlobPage(PageParameters params) {
+		super(params);
+
+		Repository r = getRepository();
+		final String blobPath = WicketUtils.getPath(params);
+		String [] encodings = GitBlit.getEncodings();
+		
+		if (StringUtils.isEmpty(blobPath)) {
+			// blob by objectid
+
+			add(new BookmarkablePageLink<Void>("blameLink", BlamePage.class,
+					WicketUtils.newPathParameter(repositoryName, objectId, blobPath))
+					.setEnabled(false));
+			add(new BookmarkablePageLink<Void>("historyLink", HistoryPage.class).setEnabled(false));
+			add(new BookmarkablePageLink<Void>("rawLink", RawPage.class,
+					WicketUtils.newPathParameter(repositoryName, objectId, blobPath)));
+			add(new BookmarkablePageLink<Void>("headLink", BlobPage.class).setEnabled(false));
+			add(new CommitHeaderPanel("commitHeader", objectId));
+			add(new PathBreadcrumbsPanel("breadcrumbs", repositoryName, blobPath, objectId));
+			Component c = new Label("blobText", JGitUtils.getStringContent(r, objectId, encodings));
+			WicketUtils.setCssClass(c, "plainprint");
+			add(c);
+		} else {
+			// standard blob view
+			String extension = null;
+			if (blobPath.lastIndexOf('.') > -1) {
+				extension = blobPath.substring(blobPath.lastIndexOf('.') + 1).toLowerCase();
+			}
+
+			// see if we should redirect to the markdown page
+			for (String ext : GitBlit.getStrings(Keys.web.markdownExtensions)) {
+				if (ext.equals(extension)) {
+					setResponsePage(MarkdownPage.class, params);
+					return;
+				}
+			}
+
+			// manually get commit because it can be null
+			RevCommit commit = JGitUtils.getCommit(r, objectId);
+
+			// blob page links
+			add(new BookmarkablePageLink<Void>("blameLink", BlamePage.class,
+					WicketUtils.newPathParameter(repositoryName, objectId, blobPath)));
+			add(new BookmarkablePageLink<Void>("historyLink", HistoryPage.class,
+					WicketUtils.newPathParameter(repositoryName, objectId, blobPath)));
+			add(new BookmarkablePageLink<Void>("rawLink", RawPage.class,
+					WicketUtils.newPathParameter(repositoryName, objectId, blobPath)));
+			add(new BookmarkablePageLink<Void>("headLink", BlobPage.class,
+					WicketUtils.newPathParameter(repositoryName, Constants.HEAD, blobPath)));
+
+			add(new CommitHeaderPanel("commitHeader", repositoryName, commit));
+
+			add(new PathBreadcrumbsPanel("breadcrumbs", repositoryName, blobPath, objectId));
+
+			// Map the extensions to types
+			Map<String, Integer> map = new HashMap<String, Integer>();
+			for (String ext : GitBlit.getStrings(Keys.web.prettyPrintExtensions)) {
+				map.put(ext.toLowerCase(), 1);
+			}
+			for (String ext : GitBlit.getStrings(Keys.web.imageExtensions)) {
+				map.put(ext.toLowerCase(), 2);
+			}
+			for (String ext : GitBlit.getStrings(Keys.web.binaryExtensions)) {
+				map.put(ext.toLowerCase(), 3);
+			}
+
+			if (extension != null) {
+				int type = 0;
+				if (map.containsKey(extension)) {
+					type = map.get(extension);
+				}
+				switch (type) {
+				case 2:
+					// image blobs
+					add(new Label("blobText").setVisible(false));
+					add(new ExternalImage("blobImage", urlFor(RawPage.class, WicketUtils.newPathParameter(repositoryName, objectId, blobPath)).toString()));
+					break;
+				case 3:
+					// binary blobs
+					add(new Label("blobText", "Binary File"));
+					add(new Image("blobImage").setVisible(false));
+					break;
+				default:
+					// plain text
+					String source = JGitUtils.getStringContent(r, commit.getTree(), blobPath, encodings);
+					String table;
+					if (source == null) {
+						table = missingBlob(blobPath, commit);
+					} else {
+						table = generateSourceView(source, extension, type == 1);
+					}
+					add(new Label("blobText", table).setEscapeModelStrings(false));
+					add(new Image("blobImage").setVisible(false));
+					fileExtension = extension;
+				}
+			} else {
+				// plain text
+				String source = JGitUtils.getStringContent(r, commit.getTree(), blobPath, encodings);
+				String table;
+				if (source == null) {
+					table = missingBlob(blobPath, commit);
+				} else {
+					table = generateSourceView(source, null, false);
+				}
+				add(new Label("blobText", table).setEscapeModelStrings(false));
+				add(new Image("blobImage").setVisible(false));
+			}
+		}
+	}
+	
+	protected String missingBlob(String blobPath, RevCommit commit) {
+		StringBuilder sb = new StringBuilder();
+		sb.append("<div class=\"alert alert-error\">");
+		String pattern = getString("gb.doesNotExistInTree").replace("{0}", "<b>{0}</b>").replace("{1}", "<b>{1}</b>");
+		sb.append(MessageFormat.format(pattern, blobPath, commit.getTree().getId().getName()));
+		sb.append("</div>");
+		return sb.toString();
+	}
+
+	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\">");
+		sb.append("<pre>");
+		String numPattern = "<span id=\"L{0}\" class=\"num\">{0}</span>\n";
+		for (int i = 0; i < lines.length; i++) {
+			sb.append(MessageFormat.format(numPattern, "" + (i + 1)));
+		}
+		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\">");
+		if (prettyPrint) {
+			sb.append("<pre class=\"prettyprint lang-" + extension + "\">");
+		} else {
+			sb.append("<pre class=\"plainprint\">");
+		}
+		lines = StringUtils.escapeForHtml(source, true).split("\n");
+		
+		sb.append("<table width=\"100%\"><tbody>");
+		
+		String linePattern = "<tr class=\"{0}\"><td><a href=\"#L{2}\">{1}</a>\r</tr>";
+		for (int i = 0; i < lines.length; i++) {
+			String line = lines[i].replace('\r', ' ');
+			String cssClass = (i % 2 == 0) ? "even" : "odd";
+			sb.append(MessageFormat.format(linePattern, cssClass, line, "" + (i + 1)));
+		}
+		sb.append("</tbody></table></pre>");
+		sb.append("</pre>");
+		sb.append("</div>");
+		sb.append("</td>");
+		sb.append("<!-- end lines column -->");
+		
+		sb.append("</tr></tbody></table>");
+		sb.append("<!-- end blob table -->");
+		
+		return sb.toString();
+	}
+
+	@Override
+	protected String getPageName() {
+		return getString("gb.view");
+	}
+	
+	@Override
+	protected Class<? extends BasePage> getRepoNavPageClass() {
+		return TreePage.class;
+	}
+}
diff --git a/src/com/gitblit/wicket/pages/BranchesPage.html b/src/main/java/com/gitblit/wicket/pages/BranchesPage.html
similarity index 100%
rename from src/com/gitblit/wicket/pages/BranchesPage.html
rename to src/main/java/com/gitblit/wicket/pages/BranchesPage.html
diff --git a/src/main/java/com/gitblit/wicket/pages/BranchesPage.java b/src/main/java/com/gitblit/wicket/pages/BranchesPage.java
new file mode 100644
index 0000000..fe7483e
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/BranchesPage.java
@@ -0,0 +1,37 @@
+/*
+ * 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.wicket.pages;
+
+import org.apache.wicket.PageParameters;
+
+import com.gitblit.wicket.CacheControl;
+import com.gitblit.wicket.CacheControl.LastModified;
+import com.gitblit.wicket.panels.BranchesPanel;
+
+@CacheControl(LastModified.REPOSITORY)
+public class BranchesPage extends RepositoryPage {
+
+	public BranchesPage(PageParameters params) {
+		super(params);
+
+		add(new BranchesPanel("branchesPanel", getRepositoryModel(), getRepository(), -1, isShowAdmin() || isOwner()));
+	}
+
+	@Override
+	protected String getPageName() {
+		return getString("gb.branches");
+	}
+}
diff --git a/src/com/gitblit/wicket/pages/ChangePasswordPage.html b/src/main/java/com/gitblit/wicket/pages/ChangePasswordPage.html
similarity index 100%
rename from src/com/gitblit/wicket/pages/ChangePasswordPage.html
rename to src/main/java/com/gitblit/wicket/pages/ChangePasswordPage.html
diff --git a/src/com/gitblit/wicket/pages/ChangePasswordPage.java b/src/main/java/com/gitblit/wicket/pages/ChangePasswordPage.java
similarity index 100%
rename from src/com/gitblit/wicket/pages/ChangePasswordPage.java
rename to src/main/java/com/gitblit/wicket/pages/ChangePasswordPage.java
diff --git a/src/main/java/com/gitblit/wicket/pages/CommitDiffPage.html b/src/main/java/com/gitblit/wicket/pages/CommitDiffPage.html
new file mode 100644
index 0000000..11a0ce3
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/CommitDiffPage.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"  
+      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"  
+      xml:lang="en"  
+      lang="en"> 
+
+<body>
+<wicket:extend>
+
+	<!-- commitdiff nav links -->	
+	<div class="page_nav2">
+		<wicket:message key="gb.parent"></wicket:message>: <span wicket:id="parentLink">[parent link]</span> | <a wicket:id="patchLink"><wicket:message key="gb.patch"></wicket:message></a> | <a wicket:id="commitLink"><wicket:message key="gb.commit"></wicket:message></a>
+	</div>	
+	
+	<!-- commit header -->
+	<div wicket:id="commitHeader">[commit header]</div>
+
+	<!-- full message -->
+	<pre style="border-style:none" "class="commit_message" wicket:id="fullMessage">[commit message]</pre>
+
+	<!-- commit legend -->
+	<div class="hidden-phone" style="text-align:right;" wicket:id="commitLegend"></div>
+	
+	<!-- changed paths -->
+	<div class="header"><i class="icon-file"></i> <wicket:message key="gb.changedFiles">[changed files]</wicket:message></div>
+	
+	<table class="pretty">
+		<tr wicket:id="changedPath">
+			<td class="changeType"><span wicket:id="changeType">[change type]</span></td>		
+			<td class="path"><span wicket:id="pathName">[commit path]</span></td>			
+			<td class="hidden-phone rightAlign">
+				<span class="link">
+					<a wicket:id="patch"><wicket:message key="gb.patch"></wicket:message></a> | <a wicket:id="view"><wicket:message key="gb.view"></wicket:message></a> | <a wicket:id="blame"><wicket:message key="gb.blame"></wicket:message></a> | <a wicket:id="history"><wicket:message key="gb.history"></wicket:message></a>
+				</span>
+			</td>
+		</tr>
+	</table>
+	
+	<!--  diff content -->
+	<pre style="padding-top:10px;" wicket:id="diffText">[diff text]</pre>
+	
+</wicket:extend>
+</body>
+</html>
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/pages/CommitDiffPage.java b/src/main/java/com/gitblit/wicket/pages/CommitDiffPage.java
new file mode 100644
index 0000000..6f1b459
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/CommitDiffPage.java
@@ -0,0 +1,166 @@
+/*
+ * 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.wicket.pages;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.wicket.PageParameters;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.link.BookmarkablePageLink;
+import org.apache.wicket.markup.html.link.ExternalLink;
+import org.apache.wicket.markup.repeater.Item;
+import org.apache.wicket.markup.repeater.data.DataView;
+import org.apache.wicket.markup.repeater.data.ListDataProvider;
+import org.eclipse.jgit.diff.DiffEntry.ChangeType;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+
+import com.gitblit.GitBlit;
+import com.gitblit.Keys;
+import com.gitblit.models.PathModel.PathChangeModel;
+import com.gitblit.models.SubmoduleModel;
+import com.gitblit.utils.DiffUtils;
+import com.gitblit.utils.DiffUtils.DiffOutputType;
+import com.gitblit.utils.JGitUtils;
+import com.gitblit.wicket.CacheControl;
+import com.gitblit.wicket.CacheControl.LastModified;
+import com.gitblit.wicket.WicketUtils;
+import com.gitblit.wicket.panels.CommitHeaderPanel;
+import com.gitblit.wicket.panels.CommitLegendPanel;
+import com.gitblit.wicket.panels.LinkPanel;
+
+@CacheControl(LastModified.BOOT)
+public class CommitDiffPage extends RepositoryPage {
+
+	public CommitDiffPage(PageParameters params) {
+		super(params);
+
+		Repository r = getRepository();
+
+		DiffOutputType diffType = DiffOutputType.forName(GitBlit.getString(Keys.web.diffStyle,
+				DiffOutputType.GITBLIT.name()));
+
+		RevCommit commit = getCommit();
+
+		String diff = DiffUtils.getCommitDiff(r, commit, diffType);
+
+		List<String> parents = new ArrayList<String>();
+		if (commit.getParentCount() > 0) {
+			for (RevCommit parent : commit.getParents()) {
+				parents.add(parent.name());
+			}
+		}
+
+		// commit page links
+		if (parents.size() == 0) {
+			add(new Label("parentLink", getString("gb.none")));
+		} else {
+			add(new LinkPanel("parentLink", null, parents.get(0).substring(0, 8),
+					CommitDiffPage.class, newCommitParameter(parents.get(0))));
+		}
+		add(new BookmarkablePageLink<Void>("patchLink", PatchPage.class,
+				WicketUtils.newObjectParameter(repositoryName, objectId)));
+		add(new BookmarkablePageLink<Void>("commitLink", CommitPage.class,
+				WicketUtils.newObjectParameter(repositoryName, objectId)));
+
+		add(new CommitHeaderPanel("commitHeader", repositoryName, commit));
+
+		addFullText("fullMessage", commit.getFullMessage(), true);
+
+		// changed paths list
+		List<PathChangeModel> paths = JGitUtils.getFilesInCommit(r, commit);
+
+		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;
+
+			public void populateItem(final Item<PathChangeModel> item) {
+				final PathChangeModel entry = item.getModelObject();
+				Label changeType = new Label("changeType", "");
+				WicketUtils.setChangeTypeCssClass(changeType, entry.changeType);
+				setChangeTypeTooltip(changeType, entry.changeType);
+				item.add(changeType);
+
+				boolean hasSubmodule = false;
+				String submodulePath = null;
+				if (entry.isTree()) {
+					// tree
+					item.add(new LinkPanel("pathName", null, entry.path, TreePage.class,
+							WicketUtils
+									.newPathParameter(repositoryName, entry.commitId, entry.path)));
+				} else if (entry.isSubmodule()) {
+					// submodule
+					String submoduleId = entry.objectId;
+					SubmoduleModel submodule = getSubmodule(entry.path);
+					submodulePath = submodule.gitblitPath;
+					hasSubmodule = submodule.hasSubmodule;
+
+					// add relative link
+					item.add(new LinkPanel("pathName", "list", entry.path + " @ " + getShortObjectId(submoduleId), "#" + entry.path));
+				} else {
+					// add relative link
+					item.add(new LinkPanel("pathName", "list", entry.path, "#" + entry.path));
+				}
+
+				// quick links
+				if (entry.isSubmodule()) {
+					// submodule
+					item.add(new ExternalLink("patch", "").setEnabled(false));
+					item.add(new BookmarkablePageLink<Void>("view", CommitPage.class, WicketUtils
+							.newObjectParameter(submodulePath, entry.objectId)).setEnabled(hasSubmodule));
+					item.add(new ExternalLink("blame", "").setEnabled(false));
+					item.add(new BookmarkablePageLink<Void>("history", HistoryPage.class, WicketUtils
+							.newPathParameter(repositoryName, entry.commitId, entry.path))
+							.setEnabled(!entry.changeType.equals(ChangeType.ADD)));
+				} else {
+					// tree or blob
+					item.add(new BookmarkablePageLink<Void>("patch", PatchPage.class, WicketUtils
+							.newPathParameter(repositoryName, entry.commitId, entry.path))
+							.setEnabled(!entry.changeType.equals(ChangeType.ADD)
+									&& !entry.changeType.equals(ChangeType.DELETE)));
+					item.add(new BookmarkablePageLink<Void>("view", BlobPage.class, WicketUtils
+							.newPathParameter(repositoryName, entry.commitId, entry.path))
+							.setEnabled(!entry.changeType.equals(ChangeType.DELETE)));
+					item.add(new BookmarkablePageLink<Void>("blame", BlamePage.class, WicketUtils
+							.newPathParameter(repositoryName, entry.commitId, entry.path))
+							.setEnabled(!entry.changeType.equals(ChangeType.ADD)
+									&& !entry.changeType.equals(ChangeType.DELETE)));
+					item.add(new BookmarkablePageLink<Void>("history", HistoryPage.class, WicketUtils
+							.newPathParameter(repositoryName, entry.commitId, entry.path))
+							.setEnabled(!entry.changeType.equals(ChangeType.ADD)));
+				}
+				
+				WicketUtils.setAlternatingBackground(item, counter);
+				counter++;
+			}
+		};
+		add(pathsView);
+		add(new Label("diffText", diff).setEscapeModelStrings(false));
+	}
+
+	@Override
+	protected String getPageName() {
+		return getString("gb.commitdiff");
+	}
+	
+	@Override
+	protected Class<? extends BasePage> getRepoNavPageClass() {
+		return LogPage.class;
+	}
+}
diff --git a/src/main/java/com/gitblit/wicket/pages/CommitPage.html b/src/main/java/com/gitblit/wicket/pages/CommitPage.html
new file mode 100644
index 0000000..ab5ddca
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/CommitPage.html
@@ -0,0 +1,99 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"  
+      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"  
+      xml:lang="en"  
+      lang="en"> 
+
+<body>
+<wicket:extend>
+
+	<!-- commit nav links -->	
+	<div class="page_nav2">
+		<wicket:message key="gb.parent"></wicket:message>: <span wicket:id="parentLink">[parent link]</span> | <a wicket:id="patchLink"><wicket:message key="gb.patch"></wicket:message></a> | <span wicket:id="commitdiffLink">[commitdiff link]</span>
+	</div>	
+	
+	<!-- commit header -->
+	<div wicket:id="commitHeader">[commit header]</div>
+	
+	<div class="row">
+	
+	
+	<div class="span10" style="padding-bottom:10px;">
+	<!-- commit info -->
+	<table class="summary">
+		<tr><th><wicket:message key="gb.refs">refs</wicket:message></th><td><div wicket:id="refsPanel">[references]</div></td></tr>
+		<tr><th><wicket:message key="gb.author">author</wicket:message></th><td><span class="sha1" wicket:id="commitAuthor">[author</span></td></tr>
+		<tr><th></th><td><span class="sha1" wicket:id="commitAuthorDate">[author date]</span></td></tr>
+		<tr><th><wicket:message key="gb.committer">committer</wicket:message></th><td><span class="sha1" wicket:id="commitCommitter">[committer]</span></td></tr>
+		<tr><th></th><td><span class="sha1" wicket:id="commitCommitterDate">[commit date]</span></td></tr>
+		<tr class="hidden-phone"><th><wicket:message key="gb.commit">commit</wicket:message></th><td><span class="sha1" wicket:id="commitId">[commit id]</span></td></tr>
+		<tr class="hidden-phone"><th><wicket:message key="gb.tree">tree</wicket:message></th>
+			<td><span class="sha1" wicket:id="commitTree">[commit tree]</span>
+				<span class="link">
+					<a wicket:id="treeLink"><wicket:message key="gb.tree"></wicket:message></a> | <span wicket:id="compressedLinks"></span>
+				</span>
+			</td></tr>
+		<tr class="hidden-phone"><th valign="top" style="vertical-align: top;"><wicket:message key="gb.parent">parent</wicket:message></th>
+			<td>
+				<span wicket:id="commitParents">
+					<span class="sha1" wicket:id="commitParent">[commit parents]</span>
+					<span class="link">
+						<a wicket:id="view"><wicket:message key="gb.view"></wicket:message></a> | <a wicket:id="diff"><wicket:message key="gb.diff"></wicket:message></a>
+					</span><br/>			
+				</span>
+			</td>
+		</tr>
+	</table>
+	</div>
+	
+	</div>
+	
+	<!-- full message -->
+	<pre class="commit_message" wicket:id="fullMessage">[commit message]</pre>
+
+	<!--  git notes -->
+	<table class="gitnotes">
+		<tr wicket:id="notes">
+			<td class="info">
+				<table>
+					<tr><td><span wicket:id="refName"></span></td></tr>
+					<tr><td><span class="sha1" wicket:id="authorName"></span></td></tr>
+					<tr><td><span class="sha1" wicket:id="authorDate"></span></td></tr>
+				</table>
+				<!--  Note Author Gravatar -->
+				<span style="vertical-align: top;" wicket:id="noteAuthorAvatar" />				
+			</td>
+			<td class="message"><span class="sha1" wicket:id="noteContent"></span></td>
+		</tr>
+	</table>
+	
+	<!--  commit legend -->
+	<div class="hidden-phone" style="text-align:right;" wicket:id="commitLegend"></div>
+	
+	<!-- header -->
+	<div class="header"><i class="icon-file"></i> <wicket:message key="gb.changedFiles">[changed files]</wicket:message></div>
+	
+	<!-- changed paths -->
+	<table class="pretty">
+		<tr wicket:id="changedPath">
+			<td class="changeType"><span wicket:id="changeType">[change type]</span></td>
+			<td class="path"><span wicket:id="pathName">[commit path]</span></td>			
+			<td class="hidden-phone rightAlign">
+				<span class="link">
+					<a wicket:id="diff"><wicket:message key="gb.diff"></wicket:message></a> | <a wicket:id="view"><wicket:message key="gb.view"></wicket:message></a> | <a wicket:id="blame"><wicket:message key="gb.blame"></wicket:message></a> | <a wicket:id="history"><wicket:message key="gb.history"></wicket:message></a>
+				</span>
+			</td>
+		</tr>
+	</table>
+	
+	<wicket:fragment wicket:id="fullPersonIdent">
+		<span wicket:id="personName"></span><span wicket:id="personAddress"></span>
+	</wicket:fragment>
+	
+	<wicket:fragment wicket:id="partialPersonIdent">
+		<span wicket:id="personName"></span>
+	</wicket:fragment>
+	
+</wicket:extend>
+</body>
+</html>
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/pages/CommitPage.java b/src/main/java/com/gitblit/wicket/pages/CommitPage.java
new file mode 100644
index 0000000..1d11d44
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/CommitPage.java
@@ -0,0 +1,233 @@
+/*
+ * 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.wicket.pages;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.apache.wicket.PageParameters;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.link.BookmarkablePageLink;
+import org.apache.wicket.markup.html.link.ExternalLink;
+import org.apache.wicket.markup.repeater.Item;
+import org.apache.wicket.markup.repeater.data.DataView;
+import org.apache.wicket.markup.repeater.data.ListDataProvider;
+import org.apache.wicket.model.StringResourceModel;
+import org.eclipse.jgit.diff.DiffEntry.ChangeType;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+
+import com.gitblit.Constants;
+import com.gitblit.GitBlit;
+import com.gitblit.models.GitNote;
+import com.gitblit.models.PathModel.PathChangeModel;
+import com.gitblit.models.SubmoduleModel;
+import com.gitblit.utils.JGitUtils;
+import com.gitblit.wicket.CacheControl;
+import com.gitblit.wicket.CacheControl.LastModified;
+import com.gitblit.wicket.WicketUtils;
+import com.gitblit.wicket.panels.CommitHeaderPanel;
+import com.gitblit.wicket.panels.CommitLegendPanel;
+import com.gitblit.wicket.panels.CompressedDownloadsPanel;
+import com.gitblit.wicket.panels.GravatarImage;
+import com.gitblit.wicket.panels.LinkPanel;
+import com.gitblit.wicket.panels.RefsPanel;
+
+@CacheControl(LastModified.BOOT)
+public class CommitPage extends RepositoryPage {
+
+	public CommitPage(PageParameters params) {
+		super(params);
+
+		Repository r = getRepository();
+		RevCommit c = getCommit();
+		
+		List<String> parents = new ArrayList<String>();
+		if (c.getParentCount() > 0) {
+			for (RevCommit parent : c.getParents()) {
+				parents.add(parent.name());
+			}
+		}
+
+		// commit page links
+		if (parents.size() == 0) {
+			add(new Label("parentLink", "none"));
+			add(new Label("commitdiffLink", getString("gb.commitdiff")));
+		} else {
+			add(new LinkPanel("parentLink", null, getShortObjectId(parents.get(0)),
+					CommitPage.class, newCommitParameter(parents.get(0))));
+			add(new LinkPanel("commitdiffLink", null, new StringResourceModel("gb.commitdiff",
+					this, null), CommitDiffPage.class, WicketUtils.newObjectParameter(
+					repositoryName, objectId)));
+		}
+		add(new BookmarkablePageLink<Void>("patchLink", PatchPage.class,
+				WicketUtils.newObjectParameter(repositoryName, objectId)));
+
+		add(new CommitHeaderPanel("commitHeader", repositoryName, c));
+
+		addRefs(r, c);
+
+		// author
+		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",
+				c.getCommitterIdent().getWhen(), getTimeZone(), getTimeUtils()));
+
+		add(new Label("commitId", c.getName()));
+
+		add(new LinkPanel("commitTree", "list", c.getTree().getName(), TreePage.class,
+				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
+		ListDataProvider<String> parentsDp = new ListDataProvider<String>(parents);
+		DataView<String> parentsView = new DataView<String>("commitParents", parentsDp) {
+			private static final long serialVersionUID = 1L;
+
+			public void populateItem(final Item<String> item) {
+				String entry = item.getModelObject();
+				item.add(new LinkPanel("commitParent", "list", entry, CommitPage.class,
+						newCommitParameter(entry)));
+				item.add(new BookmarkablePageLink<Void>("view", CommitPage.class,
+						newCommitParameter(entry)));
+				item.add(new BookmarkablePageLink<Void>("diff", CommitDiffPage.class,
+						newCommitParameter(entry)));
+			}
+		};
+		add(parentsView);
+
+		addFullText("fullMessage", c.getFullMessage(), true);
+
+		// git notes
+		List<GitNote> notes = JGitUtils.getNotesOnCommit(r, c);
+		ListDataProvider<GitNote> notesDp = new ListDataProvider<GitNote>(notes);
+		DataView<GitNote> notesView = new DataView<GitNote>("notes", notesDp) {
+			private static final long serialVersionUID = 1L;
+
+			public void populateItem(final Item<GitNote> item) {
+				GitNote entry = item.getModelObject();
+				item.add(new RefsPanel("refName", repositoryName, Arrays.asList(entry.notesRef)));
+				item.add(createPersonPanel("authorName", entry.notesRef.getAuthorIdent(),
+						Constants.SearchType.AUTHOR));
+				item.add(new GravatarImage("noteAuthorAvatar", entry.notesRef.getAuthorIdent()));
+				item.add(WicketUtils.createTimestampLabel("authorDate", entry.notesRef
+						.getAuthorIdent().getWhen(), getTimeZone(), getTimeUtils()));
+				item.add(new Label("noteContent", GitBlit.self().processCommitMessage(
+						repositoryName, entry.content)).setEscapeModelStrings(false));
+			}
+		};
+		add(notesView.setVisible(notes.size() > 0));
+
+		// changed paths list
+		List<PathChangeModel> paths = JGitUtils.getFilesInCommit(r, c);
+		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;
+
+			public void populateItem(final Item<PathChangeModel> item) {
+				final PathChangeModel entry = item.getModelObject();
+				Label changeType = new Label("changeType", "");
+				WicketUtils.setChangeTypeCssClass(changeType, entry.changeType);
+				setChangeTypeTooltip(changeType, entry.changeType);
+				item.add(changeType);
+				
+				boolean hasSubmodule = false;
+				String submodulePath = null;
+				if (entry.isTree()) {
+					// tree
+					item.add(new LinkPanel("pathName", null, entry.path, TreePage.class,
+							WicketUtils
+									.newPathParameter(repositoryName, entry.commitId, entry.path)));
+				} else if (entry.isSubmodule()) {
+					// submodule
+					String submoduleId = entry.objectId;
+					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));
+				} else {
+					// blob
+					String displayPath = entry.path;
+					String path = entry.path;
+					if (entry.isSymlink()) {
+						path = JGitUtils.getStringContent(getRepository(), getCommit().getTree(), path);
+						displayPath = entry.path + " -> " + path;
+					}
+					item.add(new LinkPanel("pathName", "list", displayPath, BlobPage.class,
+							WicketUtils
+									.newPathParameter(repositoryName, entry.commitId, path)));
+				}
+				
+				// quick links
+				if (entry.isSubmodule()) {
+					// submodule					
+					item.add(new BookmarkablePageLink<Void>("diff", BlobDiffPage.class, WicketUtils
+							.newPathParameter(repositoryName, entry.commitId, entry.path))
+							.setEnabled(!entry.changeType.equals(ChangeType.ADD)));
+					item.add(new BookmarkablePageLink<Void>("view", CommitPage.class, WicketUtils
+							.newObjectParameter(submodulePath, entry.objectId)).setEnabled(hasSubmodule));
+					item.add(new ExternalLink("blame", "").setEnabled(false));
+					item.add(new BookmarkablePageLink<Void>("history", HistoryPage.class, WicketUtils
+							.newPathParameter(repositoryName, entry.commitId, entry.path))
+							.setEnabled(!entry.changeType.equals(ChangeType.ADD)));
+				} else {
+					// tree or blob
+					item.add(new BookmarkablePageLink<Void>("diff", BlobDiffPage.class, WicketUtils
+							.newPathParameter(repositoryName, entry.commitId, entry.path))
+							.setEnabled(!entry.changeType.equals(ChangeType.ADD)
+									&& !entry.changeType.equals(ChangeType.DELETE)));
+					item.add(new BookmarkablePageLink<Void>("view", BlobPage.class, WicketUtils
+							.newPathParameter(repositoryName, entry.commitId, entry.path))
+							.setEnabled(!entry.changeType.equals(ChangeType.DELETE)));
+					item.add(new BookmarkablePageLink<Void>("blame", BlamePage.class, WicketUtils
+							.newPathParameter(repositoryName, entry.commitId, entry.path))
+							.setEnabled(!entry.changeType.equals(ChangeType.ADD)
+									&& !entry.changeType.equals(ChangeType.DELETE)));
+					item.add(new BookmarkablePageLink<Void>("history", HistoryPage.class, WicketUtils
+							.newPathParameter(repositoryName, entry.commitId, entry.path))
+							.setEnabled(!entry.changeType.equals(ChangeType.ADD)));
+				}
+
+				WicketUtils.setAlternatingBackground(item, counter);
+				counter++;
+			}
+		};
+		add(pathsView);
+	}
+
+	@Override
+	protected String getPageName() {
+		return getString("gb.commit");
+	}
+	
+	@Override
+	protected Class<? extends BasePage> getRepoNavPageClass() {
+		return LogPage.class;
+	}
+}
diff --git a/src/main/java/com/gitblit/wicket/pages/ComparePage.html b/src/main/java/com/gitblit/wicket/pages/ComparePage.html
new file mode 100644
index 0000000..5716c5d
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/ComparePage.html
@@ -0,0 +1,81 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"  
+      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"  
+      xml:lang="en"  
+      lang="en"> 
+
+<body>
+<wicket:extend>
+
+	<div class="tabbable">
+    	<ul class="nav nav-pills">
+    		<li class="active"><a href="#refs" data-toggle="tab"><wicket:message key="gb.refs"></wicket:message></a></li>
+    		<li><a href="#ids" data-toggle="tab"><wicket:message key="gb.manual"></wicket:message></a></li>
+    	</ul>
+    	<div class="tab-content">
+    		<div class="tab-pane active" id="refs">
+				<form wicket:id="compareRefsForm" class="form-inline">
+					<select wicket:id="fromRef" class="span3" />
+					<i class="icon-arrow-right"></i>
+					<select wicket:id="toRef" class="span3" />
+					<button class="btn" type="submit"><wicket:message key="gb.compare"></wicket:message></button>
+				</form>
+			</div>
+    		<div class="tab-pane" id="ids">
+				<form wicket:id="compareIdsForm" class="form-inline">
+					<input wicket:id="fromId" type="text" class="span3" />
+					<i class="icon-arrow-right"></i>
+					<input wicket:id="toId" type="text" class="span3" />
+					<button class="btn" type="submit"><wicket:message key="gb.compare"></wicket:message></button>
+				</form>
+			</div>
+		</div>
+	</div>
+
+	<div wicket:id="comparison"></div>
+
+	<wicket:fragment wicket:id="comparisonFragment">
+	
+		<div class="tabbable">
+    		<ul class="nav nav-tabs">
+    			<li><a href="#commits" data-toggle="tab"><wicket:message key="gb.commits"></wicket:message></a></li>
+    			<li class="active"><a href="#diff" data-toggle="tab"><wicket:message key="gb.diff"></wicket:message></a></li>
+    		</ul>
+    		<div class="tab-content">
+    			<div class="tab-pane" id="commits">
+					<!-- commit list -->
+					<div wicket:id="commitList">[commit list]</div>
+    			</div>
+	    		<div class="tab-pane active" id="diff">
+<!-- 					<div class="page_nav2"> -->
+<!-- 						<a wicket:id="patchLink"><wicket:message key="gb.patch"></wicket:message></a> -->
+<!-- 					</div>	 -->
+	    		
+	    			<!-- changed paths -->
+					<div>
+						<!-- commit legend -->
+						<div class="hidden-phone" style="text-align:right;" wicket:id="commitLegend"></div>
+						<div class="header"><i class="icon-file"></i> <wicket:message key="gb.changedFiles">[changed files]</wicket:message></div>
+					</div>
+					<table class="pretty">
+						<tr wicket:id="changedPath">
+							<td class="changeType"><span wicket:id="changeType">[change type]</span></td>		
+							<td class="path"><span wicket:id="pathName">[commit path]</span></td>			
+							<td class="hidden-phone rightAlign">
+								<span class="link">
+									<a wicket:id="patch"><wicket:message key="gb.patch"></wicket:message></a> | <a wicket:id="view"><wicket:message key="gb.view"></wicket:message></a> | <a wicket:id="blame"><wicket:message key="gb.blame"></wicket:message></a> | <a wicket:id="history"><wicket:message key="gb.history"></wicket:message></a>
+								</span>
+							</td>
+						</tr>
+					</table>
+	    	
+					<!--  diff content -->
+					<pre style="padding-top:10px;" wicket:id="diffText">[diff text]</pre>
+    			</div>
+    		</div>
+    	</div>
+	</wicket:fragment>
+
+</wicket:extend>
+</body>
+</html>
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/pages/ComparePage.java b/src/main/java/com/gitblit/wicket/pages/ComparePage.java
new file mode 100644
index 0000000..f5f3526
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/ComparePage.java
@@ -0,0 +1,280 @@
+/*
+ * Copyright 2013 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit.wicket.pages;
+
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.wicket.PageParameters;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.form.DropDownChoice;
+import org.apache.wicket.markup.html.form.TextField;
+import org.apache.wicket.markup.html.link.BookmarkablePageLink;
+import org.apache.wicket.markup.html.link.ExternalLink;
+import org.apache.wicket.markup.html.panel.Fragment;
+import org.apache.wicket.markup.repeater.Item;
+import org.apache.wicket.markup.repeater.data.DataView;
+import org.apache.wicket.markup.repeater.data.ListDataProvider;
+import org.apache.wicket.model.IModel;
+import org.apache.wicket.model.Model;
+import org.apache.wicket.protocol.http.RequestUtils;
+import org.apache.wicket.request.target.basic.RedirectRequestTarget;
+import org.eclipse.jgit.diff.DiffEntry.ChangeType;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+
+import com.gitblit.GitBlit;
+import com.gitblit.Keys;
+import com.gitblit.models.PathModel.PathChangeModel;
+import com.gitblit.models.RefModel;
+import com.gitblit.models.RepositoryModel;
+import com.gitblit.models.SubmoduleModel;
+import com.gitblit.utils.DiffUtils;
+import com.gitblit.utils.DiffUtils.DiffOutputType;
+import com.gitblit.utils.JGitUtils;
+import com.gitblit.utils.StringUtils;
+import com.gitblit.wicket.SessionlessForm;
+import com.gitblit.wicket.WicketUtils;
+import com.gitblit.wicket.panels.CommitLegendPanel;
+import com.gitblit.wicket.panels.LinkPanel;
+import com.gitblit.wicket.panels.LogPanel;
+
+/**
+ * The compare page allows you to compare two branches, tags, or hash ids.
+ * 
+ * @author James Moger
+ *
+ */
+public class ComparePage extends RepositoryPage {
+
+	IModel<String> fromCommitId = new Model<String>("");
+	IModel<String> toCommitId = new Model<String>("");
+
+	IModel<String> fromRefId = new Model<String>("");
+	IModel<String> toRefId = new Model<String>("");
+
+	public ComparePage(PageParameters params) {
+		super(params);
+		Repository r = getRepository();
+		RepositoryModel repository = getRepositoryModel();
+		
+		if (StringUtils.isEmpty(objectId)) {
+			// seleciton form
+			add(new Label("comparison").setVisible(false));
+		} else {
+			// active comparison
+			Fragment comparison = new Fragment("comparison", "comparisonFragment", this);
+			add(comparison);
+			
+			DiffOutputType diffType = DiffOutputType.forName(GitBlit.getString(Keys.web.diffStyle,
+					DiffOutputType.GITBLIT.name()));
+
+			RevCommit fromCommit;
+			RevCommit toCommit;
+			
+			String[] parts = objectId.split("\\.\\.");
+			if (parts[0].startsWith("refs/") && parts[1].startsWith("refs/")) {
+				// set the ref models
+				fromRefId.setObject(parts[0]);
+				toRefId.setObject(parts[1]);
+				
+				fromCommit = getCommit(r, fromRefId.getObject());
+				toCommit = getCommit(r, toRefId.getObject());
+			} else {
+				// set the id models
+				fromCommitId.setObject(parts[0]);
+				toCommitId.setObject(parts[1]);
+				
+				fromCommit = getCommit(r, fromCommitId.getObject());
+				toCommit = getCommit(r, toCommitId.getObject());
+			}
+
+			// prepare submodules
+			getSubmodules(toCommit);
+			
+			final String startId = fromCommit.getId().getName();
+			final String endId = toCommit.getId().getName();
+
+			// commit ids
+			fromCommitId.setObject(startId);
+			toCommitId.setObject(endId);
+
+			String diff = DiffUtils.getDiff(r, fromCommit, toCommit, diffType);
+
+			// compare page links
+//			comparison.add(new BookmarkablePageLink<Void>("patchLink", PatchPage.class,
+//					WicketUtils.newRangeParameter(repositoryName, fromCommitId.toString(), toCommitId.getObject())));
+
+			// display list of commits
+			comparison.add(new LogPanel("commitList", repositoryName, objectId, r, 0, 0, repository.showRemoteBranches));
+
+			// changed paths list
+			List<PathChangeModel> paths = JGitUtils.getFilesInRange(r, fromCommit, toCommit);
+
+			comparison.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;
+
+				public void populateItem(final Item<PathChangeModel> item) {
+					final PathChangeModel entry = item.getModelObject();
+					Label changeType = new Label("changeType", "");
+					WicketUtils.setChangeTypeCssClass(changeType, entry.changeType);
+					setChangeTypeTooltip(changeType, entry.changeType);
+					item.add(changeType);
+
+					boolean hasSubmodule = false;
+					String submodulePath = null;
+					if (entry.isTree()) {
+						// tree
+						item.add(new LinkPanel("pathName", null, entry.path, TreePage.class,
+								WicketUtils
+								.newPathParameter(repositoryName, endId, entry.path)));
+					} else if (entry.isSubmodule()) {
+						// submodule
+						String submoduleId = entry.objectId;
+						SubmoduleModel submodule = getSubmodule(entry.path);
+						submodulePath = submodule.gitblitPath;
+						hasSubmodule = submodule.hasSubmodule;
+
+						// add relative link
+						item.add(new LinkPanel("pathName", "list", entry.path + " @ " + getShortObjectId(submoduleId), "#" + entry.path));
+					} else {
+						// add relative link
+						item.add(new LinkPanel("pathName", "list", entry.path, "#" + entry.path));
+					}
+
+					// quick links
+					if (entry.isSubmodule()) {
+						// submodule
+						item.add(new ExternalLink("patch", "").setEnabled(false));
+						item.add(new BookmarkablePageLink<Void>("view", CommitPage.class, WicketUtils
+								.newObjectParameter(submodulePath, entry.objectId)).setEnabled(hasSubmodule));
+						item.add(new ExternalLink("blame", "").setEnabled(false));
+						item.add(new BookmarkablePageLink<Void>("history", HistoryPage.class, WicketUtils
+								.newPathParameter(repositoryName, endId, entry.path))
+								.setEnabled(!entry.changeType.equals(ChangeType.ADD)));
+					} else {
+						// tree or blob
+						item.add(new BookmarkablePageLink<Void>("patch", PatchPage.class, WicketUtils
+								.newBlobDiffParameter(repositoryName, startId, endId, entry.path))
+								.setEnabled(!entry.changeType.equals(ChangeType.DELETE)));
+						item.add(new BookmarkablePageLink<Void>("view", BlobPage.class, WicketUtils
+								.newPathParameter(repositoryName, endId, entry.path))
+								.setEnabled(!entry.changeType.equals(ChangeType.DELETE)));
+						item.add(new BookmarkablePageLink<Void>("blame", BlamePage.class, WicketUtils
+								.newPathParameter(repositoryName, endId, entry.path))
+								.setEnabled(!entry.changeType.equals(ChangeType.ADD)
+										&& !entry.changeType.equals(ChangeType.DELETE)));
+						item.add(new BookmarkablePageLink<Void>("history", HistoryPage.class, WicketUtils
+								.newPathParameter(repositoryName, endId, entry.path))
+								.setEnabled(!entry.changeType.equals(ChangeType.ADD)));
+					}
+					WicketUtils.setAlternatingBackground(item, counter);
+					counter++;
+				}
+			};
+			comparison.add(pathsView);
+			comparison.add(new Label("diffText", diff).setEscapeModelStrings(false));
+		}
+
+		//
+		// ref selection form
+		//
+		SessionlessForm<Void> refsForm = new SessionlessForm<Void>("compareRefsForm", getClass(), getPageParameters()) {
+
+			private static final long serialVersionUID = 1L;
+
+			@Override
+			public void onSubmit() {
+				String from = ComparePage.this.fromRefId.getObject();
+				String to = ComparePage.this.toRefId.getObject();
+				
+				PageParameters params = WicketUtils.newRangeParameter(repositoryName, from, to);
+				String relativeUrl = urlFor(ComparePage.class, params).toString();
+				String absoluteUrl = RequestUtils.toAbsolutePath(relativeUrl);
+				getRequestCycle().setRequestTarget(new RedirectRequestTarget(absoluteUrl));
+			}
+		};
+		
+		List<String> refs = new ArrayList<String>();
+		for (RefModel ref : JGitUtils.getLocalBranches(r, true, -1)) {
+			refs.add(ref.getName());
+		}
+		if (repository.showRemoteBranches) {
+			for (RefModel ref : JGitUtils.getRemoteBranches(r, true, -1)) {
+				refs.add(ref.getName());
+			}
+		}
+		for (RefModel ref : JGitUtils.getTags(r, true, -1)) {
+			refs.add(ref.getName());
+		}
+		refsForm.add(new DropDownChoice<String>("fromRef", fromRefId, refs).setEnabled(refs.size() > 0));
+		refsForm.add(new DropDownChoice<String>("toRef", toRefId, refs).setEnabled(refs.size() > 0));
+		add(refsForm);
+		
+		//
+		// manual ids form
+		//
+		SessionlessForm<Void> idsForm = new SessionlessForm<Void>("compareIdsForm", getClass(), getPageParameters()) {
+
+			private static final long serialVersionUID = 1L;
+
+			@Override
+			public void onSubmit() {
+				String from = ComparePage.this.fromCommitId.getObject();
+				String to = ComparePage.this.toCommitId.getObject();
+				
+				PageParameters params = WicketUtils.newRangeParameter(repositoryName, from, to);
+				String relativeUrl = urlFor(ComparePage.class, params).toString();
+				String absoluteUrl = RequestUtils.toAbsolutePath(relativeUrl);
+				getRequestCycle().setRequestTarget(new RedirectRequestTarget(absoluteUrl));
+			}
+		};
+		
+		TextField<String> fromIdField = new TextField<String>("fromId", fromCommitId);
+		WicketUtils.setInputPlaceholder(fromIdField, getString("gb.from") + "...");
+		idsForm.add(fromIdField);
+		
+		TextField<String> toIdField = new TextField<String>("toId", toCommitId);
+		WicketUtils.setInputPlaceholder(toIdField, getString("gb.to") + "...");
+		idsForm.add(toIdField);
+		add(idsForm);
+		
+		r.close();
+	}
+
+	@Override
+	protected String getPageName() {
+		return getString("gb.compare");
+	}
+	
+	@Override
+	protected Class<? extends BasePage> getRepoNavPageClass() {
+		return ComparePage.class;
+	}
+
+	private RevCommit getCommit(Repository r, String rev)
+	{
+		RevCommit otherCommit = JGitUtils.getCommit(r, rev);
+		if (otherCommit == null) {
+			error(MessageFormat.format(getString("gb.failedToFindCommit"), rev, repositoryName, getPageName()), true);
+		}
+		return otherCommit;
+	}
+}
diff --git a/src/main/java/com/gitblit/wicket/pages/DashboardPage.java b/src/main/java/com/gitblit/wicket/pages/DashboardPage.java
new file mode 100644
index 0000000..0af46c7
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/DashboardPage.java
@@ -0,0 +1,244 @@
+/*
+ * Copyright 2013 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit.wicket.pages;
+
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TimeZone;
+import java.util.TreeSet;
+
+import org.apache.wicket.PageParameters;
+import org.apache.wicket.behavior.HeaderContributor;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.panel.Fragment;
+import org.eclipse.jgit.lib.Repository;
+
+import com.gitblit.GitBlit;
+import com.gitblit.Keys;
+import com.gitblit.models.DailyLogEntry;
+import com.gitblit.models.Metric;
+import com.gitblit.models.RefLogEntry;
+import com.gitblit.models.RepositoryCommit;
+import com.gitblit.models.RepositoryModel;
+import com.gitblit.models.UserModel;
+import com.gitblit.utils.ArrayUtils;
+import com.gitblit.utils.RefLogUtils;
+import com.gitblit.utils.StringUtils;
+import com.gitblit.wicket.GitBlitWebApp;
+import com.gitblit.wicket.PageRegistration;
+import com.gitblit.wicket.PageRegistration.DropDownMenuItem;
+import com.gitblit.wicket.PageRegistration.DropDownMenuRegistration;
+import com.gitblit.wicket.charting.GoogleChart;
+import com.gitblit.wicket.charting.GoogleCharts;
+import com.gitblit.wicket.charting.GooglePieChart;
+import com.gitblit.wicket.panels.DigestsPanel;
+import com.gitblit.wicket.panels.LinkPanel;
+
+public abstract class DashboardPage extends RootPage {
+
+	public DashboardPage() {
+		super();
+	}
+
+	public DashboardPage(PageParameters params) {
+		super(params);
+	}
+
+	@Override
+	protected boolean reusePageParameters() {
+		return true;
+	}
+
+	protected void addActivity(UserModel user, Collection<RepositoryModel> repositories, String feedTitle, int daysBack) {
+		Calendar c = Calendar.getInstance();
+		c.add(Calendar.DATE, -1*daysBack);
+		Date minimumDate = c.getTime();
+		TimeZone timezone = getTimeZone();
+		
+		// create daily commit digest feed
+		List<DailyLogEntry> digests = new ArrayList<DailyLogEntry>();
+		for (RepositoryModel model : repositories) {
+			if (model.isCollectingGarbage) {
+				continue;
+			}
+			if (model.hasCommits && model.lastChange.after(minimumDate)) {
+				Repository repository = GitBlit.self().getRepository(model.name);
+				List<DailyLogEntry> entries = RefLogUtils.getDailyLogByRef(model.name, repository, minimumDate, timezone);
+				digests.addAll(entries);
+				repository.close();
+			}
+		}
+		
+		Fragment activityFragment = new Fragment("activity", "activityFragment", this);
+		add(activityFragment);
+		activityFragment.add(new Label("feedTitle", feedTitle));
+		if (digests.size() == 0) {
+			// quiet or no starred repositories
+			if (repositories.size() == 0) {
+				if (UserModel.ANONYMOUS.equals(user)) {
+					if (daysBack == 1) {
+						activityFragment.add(new Label("digests", getString("gb.noActivityToday")));
+					} else {
+						activityFragment.add(new Label("digests", MessageFormat.format(getString("gb.noActivity"), daysBack)));
+					}
+				} else {
+					activityFragment.add(new LinkPanel("digests", null, getString("gb.findSomeRepositories"), RepositoriesPage.class));
+				}
+			} else {
+				if (daysBack == 1) {
+					activityFragment.add(new Label("digests", getString("gb.noActivityToday")));
+				} else {
+					activityFragment.add(new Label("digests", MessageFormat.format(getString("gb.noActivity"), daysBack)));
+				}
+			}
+		} else {
+			// show daily commit digest feed
+			Collections.sort(digests);
+			DigestsPanel digestsPanel = new DigestsPanel("digests", digests);
+			activityFragment.add(digestsPanel);
+		}
+		
+		// add the nifty charts
+		if (!ArrayUtils.isEmpty(digests)) {
+			// aggregate author exclusions
+			Set<String> authorExclusions = new TreeSet<String>();
+			for (String author : GitBlit.getStrings(Keys.web.metricAuthorExclusions)) {
+				authorExclusions.add(author.toLowerCase());
+			}
+			for (RepositoryModel model : repositories) {
+				if (!ArrayUtils.isEmpty(model.metricAuthorExclusions)) {
+					for (String author : model.metricAuthorExclusions) {
+						authorExclusions.add(author.toLowerCase());
+					}
+				}
+			}
+
+			addCharts(activityFragment, digests, authorExclusions, daysBack);
+		} else {
+			activityFragment.add(new Label("charts").setVisible(false));
+			activityFragment.add(new Label("feedheader").setVisible(false));
+		}
+	}
+	
+	@Override
+	protected void addDropDownMenus(List<PageRegistration> pages) {
+		PageParameters params = getPageParameters();
+
+		DropDownMenuRegistration menu = new DropDownMenuRegistration("gb.filters",
+				GitBlitWebApp.HOME_PAGE_CLASS);
+
+		// preserve repository filter option on time choices
+		menu.menuItems.addAll(getTimeFilterItems(params));
+
+		if (menu.menuItems.size() > 0) {
+			// Reset Filter
+			menu.menuItems.add(new DropDownMenuItem(getString("gb.reset"), null, null));
+		}
+
+		pages.add(menu);
+	}
+
+
+	/**
+	 * Creates the daily activity line chart, the active repositories pie chart,
+	 * and the active authors pie chart
+	 * 
+	 * @param recentChanges
+	 * @param authorExclusions
+	 * @param daysBack
+	 */
+	protected void addCharts(Fragment frag, List<DailyLogEntry> recentChanges, Set<String> authorExclusions, int daysBack) {
+		// activity metrics
+		Map<String, Metric> repositoryMetrics = new HashMap<String, Metric>();
+		Map<String, Metric> authorMetrics = new HashMap<String, Metric>();
+
+		// aggregate repository and author metrics
+		int totalCommits = 0;
+		for (RefLogEntry change : recentChanges) {
+
+			// aggregate repository metrics
+			String repository = StringUtils.stripDotGit(change.repository);
+			if (!repositoryMetrics.containsKey(repository)) {
+				repositoryMetrics.put(repository, new Metric(repository));
+			}
+			repositoryMetrics.get(repository).count += 1;
+			
+			for (RepositoryCommit commit : change.getCommits()) {
+				totalCommits++;
+				String author = StringUtils.removeNewlines(commit.getAuthorIdent().getName());
+				String authorName = author.toLowerCase();
+				String authorEmail = StringUtils.removeNewlines(commit.getAuthorIdent().getEmailAddress()).toLowerCase();
+				if (!authorExclusions.contains(authorName) && !authorExclusions.contains(authorEmail)) {
+					if (!authorMetrics.containsKey(author)) {
+						authorMetrics.put(author, new Metric(author));
+					}
+					authorMetrics.get(author).count += 1;
+				}
+			}
+		}
+		
+		String headerPattern;
+		if (daysBack == 1) {
+			// today
+			if (totalCommits == 0) {
+				headerPattern = getString("gb.todaysActivityNone");
+			} else {
+				headerPattern = getString("gb.todaysActivityStats");
+			}
+		} else {
+			// multiple days
+			if (totalCommits == 0) {
+				headerPattern = getString("gb.recentActivityNone");
+			} else {
+				headerPattern = getString("gb.recentActivityStats");
+			}
+		}
+		frag.add(new Label("feedheader", MessageFormat.format(headerPattern,
+				daysBack, totalCommits, authorMetrics.size())));
+
+		// build google charts
+		GoogleCharts charts = new GoogleCharts();
+
+		// active repositories pie chart
+		GoogleChart chart = new GooglePieChart("chartRepositories", getString("gb.activeRepositories"),
+				getString("gb.repository"), getString("gb.commits"));
+		for (Metric metric : repositoryMetrics.values()) {
+			chart.addValue(metric.name, metric.count);
+		}
+		chart.setShowLegend(false);
+		charts.addChart(chart);
+
+		// active authors pie chart
+		chart = new GooglePieChart("chartAuthors", getString("gb.activeAuthors"),
+				getString("gb.author"), getString("gb.commits"));
+		for (Metric metric : authorMetrics.values()) {
+			chart.addValue(metric.name, metric.count);
+		}
+		chart.setShowLegend(false);
+		charts.addChart(chart);
+
+		add(new HeaderContributor(charts));		
+		frag.add(new Fragment("charts", "chartsFragment", this));
+	}
+}
diff --git a/src/com/gitblit/wicket/pages/DocsPage.html b/src/main/java/com/gitblit/wicket/pages/DocsPage.html
similarity index 100%
rename from src/com/gitblit/wicket/pages/DocsPage.html
rename to src/main/java/com/gitblit/wicket/pages/DocsPage.html
diff --git a/src/main/java/com/gitblit/wicket/pages/DocsPage.java b/src/main/java/com/gitblit/wicket/pages/DocsPage.java
new file mode 100644
index 0000000..9330316
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/DocsPage.java
@@ -0,0 +1,85 @@
+/*
+ * 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.wicket.pages;
+
+import java.util.List;
+
+import org.apache.wicket.PageParameters;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.link.BookmarkablePageLink;
+import org.apache.wicket.markup.repeater.Item;
+import org.apache.wicket.markup.repeater.data.DataView;
+import org.apache.wicket.markup.repeater.data.ListDataProvider;
+import org.eclipse.jgit.lib.Repository;
+
+import com.gitblit.GitBlit;
+import com.gitblit.Keys;
+import com.gitblit.models.PathModel;
+import com.gitblit.utils.ByteFormat;
+import com.gitblit.utils.JGitUtils;
+import com.gitblit.wicket.CacheControl;
+import com.gitblit.wicket.WicketUtils;
+import com.gitblit.wicket.CacheControl.LastModified;
+import com.gitblit.wicket.panels.LinkPanel;
+
+@CacheControl(LastModified.REPOSITORY)
+public class DocsPage extends RepositoryPage {
+
+	public DocsPage(PageParameters params) {
+		super(params);
+
+		Repository r = getRepository();
+		List<String> extensions = GitBlit.getStrings(Keys.web.markdownExtensions);
+		List<PathModel> paths = JGitUtils.getDocuments(r, extensions);
+
+		final ByteFormat byteFormat = new ByteFormat();
+
+		add(new Label("header", getString("gb.docs")));
+
+		// documents list
+		ListDataProvider<PathModel> pathsDp = new ListDataProvider<PathModel>(paths);
+		DataView<PathModel> pathsView = new DataView<PathModel>("document", pathsDp) {
+			private static final long serialVersionUID = 1L;
+			int counter;
+
+			public void populateItem(final Item<PathModel> item) {
+				PathModel entry = item.getModelObject();
+				item.add(WicketUtils.newImage("docIcon", "file_world_16x16.png"));
+				item.add(new Label("docSize", byteFormat.format(entry.size)));
+				item.add(new LinkPanel("docName", "list", entry.name, BlobPage.class, WicketUtils
+						.newPathParameter(repositoryName, entry.commitId, entry.path)));
+
+				// links
+				item.add(new BookmarkablePageLink<Void>("view", BlobPage.class, WicketUtils
+						.newPathParameter(repositoryName, entry.commitId, entry.path)));
+				item.add(new BookmarkablePageLink<Void>("raw", RawPage.class, WicketUtils
+						.newPathParameter(repositoryName, entry.commitId, entry.path)));
+				item.add(new BookmarkablePageLink<Void>("blame", BlamePage.class, WicketUtils
+						.newPathParameter(repositoryName, entry.commitId, entry.path)));
+				item.add(new BookmarkablePageLink<Void>("history", HistoryPage.class, WicketUtils
+						.newPathParameter(repositoryName, entry.commitId, entry.path)));
+				WicketUtils.setAlternatingBackground(item, counter);
+				counter++;
+			}
+		};
+		add(pathsView);
+	}
+
+	@Override
+	protected String getPageName() {
+		return getString("gb.docs");
+	}
+}
diff --git a/src/main/java/com/gitblit/wicket/pages/EditRepositoryPage.html b/src/main/java/com/gitblit/wicket/pages/EditRepositoryPage.html
new file mode 100644
index 0000000..3807027
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/EditRepositoryPage.html
@@ -0,0 +1,113 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"  
+      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"  
+      xml:lang="en"  
+      lang="en"> 
+
+<wicket:extend>
+<body onload="document.getElementById('name').focus();">
+	<form style="padding-top:5px;" wicket:id="editForm">
+
+<div class="tabbable">
+	<!-- tab titles -->
+	<ul class="nav nav-tabs">
+		<li class="active"><a href="#general" data-toggle="tab"><wicket:message key="gb.general"></wicket:message></a></li>
+		<li><a href="#permissions" data-toggle="tab"><wicket:message key="gb.accessPermissions"></wicket:message></a></li>
+		<li><a href="#federation" data-toggle="tab"><wicket:message key="gb.federation"></wicket:message></a></li>
+		<li><a href="#search" data-toggle="tab"><wicket:message key="gb.search"></wicket:message></a></li>
+		<li><a href="#hooks" data-toggle="tab"><wicket:message key="gb.hookScripts"></wicket:message></a></li>
+	</ul>
+
+	<!-- tab content -->
+	<div class="tab-content">
+
+		<!-- general tab -->
+		<div class="tab-pane active" id="general">
+		<table class="plain">
+			<tbody class="settings">
+				<tr><th><wicket:message key="gb.name"></wicket:message></th><td class="edit"><input class="span4" type="text" wicket:id="name" id="name" size="40" tabindex="1" /> &nbsp;<span class="help-inline"><wicket:message key="gb.nameDescription"></wicket:message></span></td></tr>
+				<tr><th><wicket:message key="gb.description"></wicket:message></th><td class="edit"><input class="span4" type="text" wicket:id="description" size="40" tabindex="2" /></td></tr>
+				<tr><th colspan="2"><hr/></th></tr>
+				<tr><th><wicket:message key="gb.origin"></wicket:message></th><td class="edit"><input class="span5" type="text" wicket:id="origin" size="80" tabindex="3" /></td></tr>
+				<tr><th><wicket:message key="gb.headRef"></wicket:message></th><td class="edit"><select class="span3" wicket:id="HEAD" tabindex="4" /> &nbsp;<span class="help-inline"><wicket:message key="gb.headRefDescription"></wicket:message></span></td></tr>
+				<tr><th><wicket:message key="gb.gcPeriod"></wicket:message></th><td class="edit"><select class="span2" wicket:id="gcPeriod" tabindex="5" /> &nbsp;<span class="help-inline"><wicket:message key="gb.gcPeriodDescription"></wicket:message></span></td></tr>
+				<tr><th><wicket:message key="gb.gcThreshold"></wicket:message></th><td class="edit"><input class="span1" type="text" wicket:id="gcThreshold" tabindex="6" /> &nbsp;<span class="help-inline"><wicket:message key="gb.gcThresholdDescription"></wicket:message></span></td></tr>
+				<tr><th colspan="2"><hr/></th></tr>
+				<tr><th><wicket:message key="gb.enableTickets"></wicket:message></th><td class="edit"><label class="checkbox"><input type="checkbox" wicket:id="useTickets" tabindex="7" /> &nbsp;<span class="help-inline"><wicket:message key="gb.useTicketsDescription"></wicket:message></span></label></td></tr>
+				<tr><th><wicket:message key="gb.enableDocs"></wicket:message></th><td class="edit"><label class="checkbox"><input type="checkbox" wicket:id="useDocs" tabindex="8" /> &nbsp;<span class="help-inline"><wicket:message key="gb.useDocsDescription"></wicket:message></span></label></td></tr>
+				<tr><th><wicket:message key="gb.enableIncrementalPushTags"></wicket:message></th><td class="edit"><label class="checkbox"><input type="checkbox" wicket:id="useIncrementalPushTags" tabindex="8" /> &nbsp;<span class="help-inline"><wicket:message key="gb.useIncrementalPushTagsDescription"></wicket:message></span></label></td></tr>
+				<tr><th><wicket:message key="gb.showRemoteBranches"></wicket:message></th><td class="edit"><label class="checkbox"><input type="checkbox" wicket:id="showRemoteBranches" tabindex="9" /> &nbsp;<span class="help-inline"><wicket:message key="gb.showRemoteBranchesDescription"></wicket:message></span></label></td></tr>
+				<tr><th><wicket:message key="gb.showReadme"></wicket:message></th><td class="edit"><label class="checkbox"><input type="checkbox" wicket:id="showReadme" tabindex="10" /> &nbsp;<span class="help-inline"><wicket:message key="gb.showReadmeDescription"></wicket:message></span></label></td></tr>
+				<tr><th><wicket:message key="gb.skipSizeCalculation"></wicket:message></th><td class="edit"><label class="checkbox"><input type="checkbox" wicket:id="skipSizeCalculation" tabindex="11" /> &nbsp;<span class="help-inline"><wicket:message key="gb.skipSizeCalculationDescription"></wicket:message></span></label></td></tr>
+				<tr><th><wicket:message key="gb.skipSummaryMetrics"></wicket:message></th><td class="edit"><label class="checkbox"><input type="checkbox" wicket:id="skipSummaryMetrics" tabindex="12" /> &nbsp;<span class="help-inline"><wicket:message key="gb.skipSummaryMetricsDescription"></wicket:message></span></label></td></tr>
+				<tr><th><wicket:message key="gb.maxActivityCommits"></wicket:message></th><td class="edit"><select class="span2" wicket:id="maxActivityCommits" tabindex="13" /> &nbsp;<span class="help-inline"><wicket:message key="gb.maxActivityCommitsDescription"></wicket:message></span></td></tr>
+				<tr><th><wicket:message key="gb.metricAuthorExclusions"></wicket:message></th><td class="edit"><input class="span8" type="text" wicket:id="metricAuthorExclusions" size="40" tabindex="14" /></td></tr>
+				<tr><th colspan="2"><hr/></th></tr>
+				<tr><th><wicket:message key="gb.mailingLists"></wicket:message></th><td class="edit"><input class="span8" type="text" wicket:id="mailingLists" size="40" tabindex="15" /></td></tr>
+			</tbody>
+		</table>
+		</div>
+
+		<!-- access permissions -->
+		<div class="tab-pane" id="permissions">
+			<table class="plain">
+				<tbody class="settings">
+					<tr><th><wicket:message key="gb.owners"></wicket:message></th><td class="edit"><span wicket:id="owners" tabindex="16" /> </td></tr>
+					<tr><th colspan="2"><hr/></th></tr>
+					<tr><th><wicket:message key="gb.accessRestriction"></wicket:message></th><td class="edit"><select class="span4" wicket:id="accessRestriction" tabindex="17" /></td></tr>
+					<tr><th colspan="2"><hr/></th></tr>
+					<tr><th><wicket:message key="gb.authorizationControl"></wicket:message></th><td style="padding:2px;"><span class="authorizationControl" wicket:id="authorizationControl"></span></td></tr>
+					<tr><th colspan="2"><hr/></th></tr>
+					<tr><th><wicket:message key="gb.isFrozen"></wicket:message></th><td class="edit"><label class="checkbox"><input type="checkbox" wicket:id="isFrozen" tabindex="18" /> &nbsp;<span class="help-inline"><wicket:message key="gb.isFrozenDescription"></wicket:message></span></label></td></tr>
+					<tr><th><wicket:message key="gb.allowForks"></wicket:message></th><td class="edit"><label class="checkbox"><input type="checkbox" wicket:id="allowForks" tabindex="19" /> &nbsp;<span class="help-inline"><wicket:message key="gb.allowForksDescription"></wicket:message></span></label></td></tr>
+					<tr><th><wicket:message key="gb.verifyCommitter"></wicket:message></th><td class="edit"><label class="checkbox"><input type="checkbox" wicket:id="verifyCommitter" tabindex="20" /> &nbsp;<span class="help-inline"><wicket:message key="gb.verifyCommitterDescription"></wicket:message></span><br/><span class="help-inline" style="padding-left:10px;"><wicket:message key="gb.verifyCommitterNote"></wicket:message></span></label></td></tr>
+					<tr><th colspan="2"><hr/></th></tr>
+					<tr><th><wicket:message key="gb.userPermissions"></wicket:message></th><td style="padding:2px;"><span wicket:id="users"></span></td></tr>
+					<tr><th colspan="2"><hr/></th></tr>
+					<tr><th><wicket:message key="gb.teamPermissions"></wicket:message></th><td style="padding:2px;"><span wicket:id="teams"></span></td></tr>
+				</tbody>
+			</table>
+		</div>
+
+		<!-- federation -->
+		<div class="tab-pane" id="federation">
+			<table class="plain">
+				<tbody class="settings">
+					<tr><th><wicket:message key="gb.federationStrategy"></wicket:message></th><td class="edit"><select class="span4" wicket:id="federationStrategy" tabindex="21" /></td></tr>
+					<tr><th><wicket:message key="gb.federationSets"></wicket:message></th><td style="padding:2px;"><span wicket:id="federationSets"></span></td></tr>
+				</tbody>
+			</table>
+		</div>
+
+		<!-- search -->
+		<div class="tab-pane" id="search">
+			<table class="plain">
+				<tbody class="settings">
+					<tr><th><wicket:message key="gb.indexedBranches"></wicket:message></th><td style="padding:2px;"><span wicket:id="indexedBranches"></span></td></tr>
+				</tbody>
+			</table>
+		</div>
+
+		<!-- hooks -->
+		<div class="tab-pane" id="hooks">
+			<table class="plain">
+				<tbody class="settings">
+					<tr><th><wicket:message key="gb.preReceiveScripts"></wicket:message><p></p><span wicket:id="inheritedPreReceive"></span></th><td style="padding:2px;"><span wicket:id="preReceiveScripts"></span></td></tr>
+					<tr><th><wicket:message key="gb.postReceiveScripts"></wicket:message><p></p><span wicket:id="inheritedPostReceive"></span></th><td style="padding:2px;"><span wicket:id="postReceiveScripts"></span></td></tr>
+					<div wicket:id="customFieldsSection">
+						<tr><td colspan="2"><h3><wicket:message key="gb.customFields"></wicket:message> &nbsp;<small><wicket:message key="gb.customFieldsDescription"></wicket:message></small></h3></td></tr>
+						<tr wicket:id="customFieldsListView"><th><span wicket:id="customFieldLabel"></span></th><td class="edit"><input class="span8" type="text" wicket:id="customFieldValue" /></td></tr>
+					</div>
+				</tbody>
+			</table>
+		</div>
+	</div>
+
+	<div class="row">
+		<div class="form-actions"><input class="btn btn-primary" type="submit" value="Save" wicket:message="value:gb.save" wicket:id="save" /> &nbsp; <input class="btn" type="submit" value="Cancel" wicket:message="value:gb.cancel" wicket:id="cancel" /></div>
+	</div>
+</div>
+</form>	
+</body>
+</wicket:extend>
+</html>
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/pages/EditRepositoryPage.java b/src/main/java/com/gitblit/wicket/pages/EditRepositoryPage.java
new file mode 100644
index 0000000..4c471a1
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/EditRepositoryPage.java
@@ -0,0 +1,725 @@
+/*
+ * 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.wicket.pages;
+
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.wicket.PageParameters;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.ajax.form.AjaxFormChoiceComponentUpdatingBehavior;
+import org.apache.wicket.ajax.form.AjaxFormComponentUpdatingBehavior;
+import org.apache.wicket.behavior.SimpleAttributeModifier;
+import org.apache.wicket.extensions.markup.html.form.palette.Palette;
+import org.apache.wicket.markup.html.WebMarkupContainer;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.form.Button;
+import org.apache.wicket.markup.html.form.CheckBox;
+import org.apache.wicket.markup.html.form.DropDownChoice;
+import org.apache.wicket.markup.html.form.Form;
+import org.apache.wicket.markup.html.form.IChoiceRenderer;
+import org.apache.wicket.markup.html.form.RadioChoice;
+import org.apache.wicket.markup.html.form.TextField;
+import org.apache.wicket.markup.html.list.ListItem;
+import org.apache.wicket.markup.html.list.ListView;
+import org.apache.wicket.model.CompoundPropertyModel;
+import org.apache.wicket.model.IModel;
+import org.apache.wicket.model.Model;
+import org.apache.wicket.model.util.CollectionModel;
+import org.apache.wicket.model.util.ListModel;
+
+import com.gitblit.Constants;
+import com.gitblit.Constants.AccessRestrictionType;
+import com.gitblit.Constants.AuthorizationControl;
+import com.gitblit.Constants.FederationStrategy;
+import com.gitblit.Constants.RegistrantType;
+import com.gitblit.GitBlit;
+import com.gitblit.GitBlitException;
+import com.gitblit.Keys;
+import com.gitblit.models.RegistrantAccessPermission;
+import com.gitblit.models.RepositoryModel;
+import com.gitblit.models.UserModel;
+import com.gitblit.utils.ArrayUtils;
+import com.gitblit.utils.StringUtils;
+import com.gitblit.wicket.GitBlitWebSession;
+import com.gitblit.wicket.StringChoiceRenderer;
+import com.gitblit.wicket.WicketUtils;
+import com.gitblit.wicket.panels.BulletListPanel;
+import com.gitblit.wicket.panels.RegistrantPermissionsPanel;
+
+public class EditRepositoryPage extends RootSubPage {
+
+	private final boolean isCreate;
+
+	private boolean isAdmin;
+	
+	RepositoryModel repositoryModel;
+
+	private IModel<String> metricAuthorExclusions;
+	
+	private IModel<String> mailingLists;
+
+	public EditRepositoryPage() {
+		// create constructor
+		super();
+		isCreate = true;
+		RepositoryModel model = new RepositoryModel();
+		String restriction = GitBlit.getString(Keys.git.defaultAccessRestriction, null);
+		model.accessRestriction = AccessRestrictionType.fromName(restriction);
+		String authorization = GitBlit.getString(Keys.git.defaultAuthorizationControl, null);
+		model.authorizationControl = AuthorizationControl.fromName(authorization);
+		
+		GitBlitWebSession session = GitBlitWebSession.get();
+		UserModel user = session.getUser();
+		if (user != null && user.canCreate() && !user.canAdmin()) {
+			// personal create permissions, inject personal repository path
+			model.name = user.getPersonalPath() + "/";
+			model.projectPath = user.getPersonalPath();
+			model.addOwner(user.username);
+			// personal repositories are private by default
+			model.accessRestriction = AccessRestrictionType.VIEW;
+			model.authorizationControl = AuthorizationControl.NAMED;
+		}
+		
+		setupPage(model);
+		setStatelessHint(false);
+		setOutputMarkupId(true);
+	}
+
+	public EditRepositoryPage(PageParameters params) {
+		// edit constructor
+		super(params);
+		isCreate = false;
+		String name = WicketUtils.getRepositoryName(params);
+		RepositoryModel model = GitBlit.self().getRepositoryModel(name);
+		setupPage(model);
+		setStatelessHint(false);
+		setOutputMarkupId(true);
+	}
+	
+	@Override
+	protected boolean requiresPageMap() {
+		return true;
+	}
+	
+	@Override
+	protected Class<? extends BasePage> getRootNavPageClass() {
+		return RepositoriesPage.class;
+	}
+
+	protected void setupPage(RepositoryModel model) {
+		this.repositoryModel = model;
+		
+		// ensure this user can create or edit this repository
+		checkPermissions(repositoryModel);
+
+		List<String> indexedBranches = new ArrayList<String>();
+		List<String> federationSets = new ArrayList<String>();
+		final List<RegistrantAccessPermission> repositoryUsers = new ArrayList<RegistrantAccessPermission>();
+		final List<RegistrantAccessPermission> repositoryTeams = new ArrayList<RegistrantAccessPermission>();
+		List<String> preReceiveScripts = new ArrayList<String>();
+		List<String> postReceiveScripts = new ArrayList<String>();
+
+		GitBlitWebSession session = GitBlitWebSession.get();
+		final UserModel user = session.getUser() == null ? UserModel.ANONYMOUS : session.getUser();
+		final boolean allowEditName = isCreate || isAdmin || repositoryModel.isUsersPersonalRepository(user.username);
+		
+		if (isCreate) {
+			if (user.canAdmin()) {
+				super.setupPage(getString("gb.newRepository"), "");
+			} else {
+				super.setupPage(getString("gb.newRepository"), user.getDisplayName());
+			}
+		} else {
+			super.setupPage(getString("gb.edit"), repositoryModel.name);
+			repositoryUsers.addAll(GitBlit.self().getUserAccessPermissions(repositoryModel));
+			repositoryTeams.addAll(GitBlit.self().getTeamAccessPermissions(repositoryModel));
+			Collections.sort(repositoryUsers);
+			Collections.sort(repositoryTeams);
+			
+			federationSets.addAll(repositoryModel.federationSets);
+			if (!ArrayUtils.isEmpty(repositoryModel.indexedBranches)) {
+				indexedBranches.addAll(repositoryModel.indexedBranches);
+			}
+		}
+
+		final String oldName = repositoryModel.name;
+
+		final RegistrantPermissionsPanel usersPalette = new RegistrantPermissionsPanel("users", 
+				RegistrantType.USER, GitBlit.self().getAllUsernames(), repositoryUsers, getAccessPermissions());
+		final RegistrantPermissionsPanel teamsPalette = new RegistrantPermissionsPanel("teams", 
+				RegistrantType.TEAM, GitBlit.self().getAllTeamnames(), repositoryTeams, getAccessPermissions());
+
+		// owners palette
+		List<String> owners = new ArrayList<String>(repositoryModel.owners);
+		List<String> persons = GitBlit.self().getAllUsernames();
+		final Palette<String> ownersPalette = new Palette<String>("owners", new ListModel<String>(owners), new CollectionModel<String>(
+		      persons), new StringChoiceRenderer(), 12, true);
+		
+		// indexed local branches palette
+		List<String> allLocalBranches = new ArrayList<String>();
+		allLocalBranches.add(Constants.DEFAULT_BRANCH);
+		allLocalBranches.addAll(repositoryModel.getLocalBranches());
+		boolean luceneEnabled = GitBlit.getBoolean(Keys.web.allowLuceneIndexing, true);
+		final Palette<String> indexedBranchesPalette = new Palette<String>("indexedBranches", new ListModel<String>(
+				indexedBranches), new CollectionModel<String>(allLocalBranches),
+				new StringChoiceRenderer(), 8, false);
+		indexedBranchesPalette.setEnabled(luceneEnabled);
+		
+		// federation sets palette
+		List<String> sets = GitBlit.getStrings(Keys.federation.sets);
+		final Palette<String> federationSetsPalette = new Palette<String>("federationSets",
+				new ListModel<String>(federationSets), new CollectionModel<String>(sets),
+				new StringChoiceRenderer(), 8, false);
+
+		// pre-receive palette
+		if (!ArrayUtils.isEmpty(repositoryModel.preReceiveScripts)) {
+			preReceiveScripts.addAll(repositoryModel.preReceiveScripts);
+		}
+		final Palette<String> preReceivePalette = new Palette<String>("preReceiveScripts",
+				new ListModel<String>(preReceiveScripts), new CollectionModel<String>(GitBlit
+						.self().getPreReceiveScriptsUnused(repositoryModel)),
+				new StringChoiceRenderer(), 12, true);
+
+		// post-receive palette
+		if (!ArrayUtils.isEmpty(repositoryModel.postReceiveScripts)) {
+			postReceiveScripts.addAll(repositoryModel.postReceiveScripts);
+		}
+		final Palette<String> postReceivePalette = new Palette<String>("postReceiveScripts",
+				new ListModel<String>(postReceiveScripts), new CollectionModel<String>(GitBlit
+						.self().getPostReceiveScriptsUnused(repositoryModel)),
+				new StringChoiceRenderer(), 12, true);
+		
+		// custom fields
+		final Map<String, String> customFieldsMap = GitBlit.getMap(Keys.groovy.customFields);
+		List<String> customKeys = new ArrayList<String>(customFieldsMap.keySet());
+		final ListView<String> customFieldsListView = new ListView<String>("customFieldsListView", customKeys) {
+			
+			private static final long serialVersionUID = 1L;
+
+			@Override
+			protected void populateItem(ListItem<String> item) {
+				String key = item.getModelObject();
+				item.add(new Label("customFieldLabel", customFieldsMap.get(key)));
+				
+				String value = "";
+				if (repositoryModel.customFields != null && repositoryModel.customFields.containsKey(key)) {
+					value = repositoryModel.customFields.get(key);
+				}
+				TextField<String> field = new TextField<String>("customFieldValue", new Model<String>(value));
+				item.add(field);
+			}
+		};
+		customFieldsListView.setReuseItems(true);
+
+		CompoundPropertyModel<RepositoryModel> rModel = new CompoundPropertyModel<RepositoryModel>(
+				repositoryModel);
+		Form<RepositoryModel> form = new Form<RepositoryModel>("editForm", rModel) {
+
+			private static final long serialVersionUID = 1L;
+
+			@Override
+			protected void onSubmit() {
+				try {
+					// confirm a repository name was entered
+					if (repositoryModel.name == null && StringUtils.isEmpty(repositoryModel.name)) {
+						error(getString("gb.pleaseSetRepositoryName"));
+						return;
+					}
+					
+					// ensure name is trimmed
+					repositoryModel.name = repositoryModel.name.trim();
+					
+					// automatically convert backslashes to forward slashes
+					repositoryModel.name = repositoryModel.name.replace('\\', '/');
+					// Automatically replace // with /
+					repositoryModel.name = repositoryModel.name.replace("//", "/");
+
+					// prohibit folder paths
+					if (repositoryModel.name.startsWith("/")) {
+						error(getString("gb.illegalLeadingSlash"));
+						return;
+					}
+					if (repositoryModel.name.startsWith("../")) {
+						error(getString("gb.illegalRelativeSlash"));
+						return;
+					}
+					if (repositoryModel.name.contains("/../")) {
+						error(getString("gb.illegalRelativeSlash"));
+						return;
+					}					
+					if (repositoryModel.name.endsWith("/")) {
+						repositoryModel.name = repositoryModel.name.substring(0, repositoryModel.name.length() - 1);
+					}
+
+					// confirm valid characters in repository name
+					Character c = StringUtils.findInvalidCharacter(repositoryModel.name);
+					if (c != null) {
+						error(MessageFormat.format(getString("gb.illegalCharacterRepositoryName"),
+								c));
+						return;
+					}
+					
+					if (user.canCreate() && !user.canAdmin() && allowEditName) {
+						// ensure repository name begins with the user's path
+						if (!repositoryModel.name.startsWith(user.getPersonalPath())) {
+							error(MessageFormat.format(getString("gb.illegalPersonalRepositoryLocation"),
+									user.getPersonalPath()));
+							return;
+						}
+						
+						if (repositoryModel.name.equals(user.getPersonalPath())) {
+							// reset path prefix and show error
+							repositoryModel.name = user.getPersonalPath() + "/";
+							error(getString("gb.pleaseSetRepositoryName"));
+							return;
+						}
+					}
+
+					// confirm access restriction selection
+					if (repositoryModel.accessRestriction == null) {
+						error(getString("gb.selectAccessRestriction"));
+						return;
+					}
+
+					// confirm federation strategy selection
+					if (repositoryModel.federationStrategy == null) {
+						error(getString("gb.selectFederationStrategy"));
+						return;
+					}
+
+					// save federation set preferences
+					if (repositoryModel.federationStrategy.exceeds(FederationStrategy.EXCLUDE)) {
+						repositoryModel.federationSets.clear();
+						Iterator<String> sets = federationSetsPalette.getSelectedChoices();
+						while (sets.hasNext()) {
+							repositoryModel.federationSets.add(sets.next());
+						}
+					}
+
+					// set author metric exclusions
+					String ax = metricAuthorExclusions.getObject();
+					if (!StringUtils.isEmpty(ax)) {
+						Set<String> list = new HashSet<String>();
+						for (String exclusion : StringUtils.getStringsFromValue(ax,  " ")) {
+							if (StringUtils.isEmpty(exclusion)) {
+								continue;
+							}
+							if (exclusion.indexOf(' ') > -1) {
+								list.add("\"" + exclusion + "\"");	
+							} else {
+								list.add(exclusion);
+							}
+						}
+						repositoryModel.metricAuthorExclusions = new ArrayList<String>(list);
+					}
+
+					// set mailing lists
+					String ml = mailingLists.getObject();
+					if (!StringUtils.isEmpty(ml)) {
+						Set<String> list = new HashSet<String>();
+						for (String address : ml.split("(,|\\s)")) {
+							if (StringUtils.isEmpty(address)) {
+								continue;
+							}
+							list.add(address.toLowerCase());
+						}
+						repositoryModel.mailingLists = new ArrayList<String>(list);
+					}
+
+					// indexed branches
+					List<String> indexedBranches = new ArrayList<String>();
+					Iterator<String> branches = indexedBranchesPalette.getSelectedChoices();
+					while (branches.hasNext()) {
+						indexedBranches.add(branches.next());
+					}
+					repositoryModel.indexedBranches = indexedBranches;
+
+					// owners
+					repositoryModel.owners.clear();
+					Iterator<String> owners = ownersPalette.getSelectedChoices();
+					while (owners.hasNext()) {
+						repositoryModel.addOwner(owners.next());
+					}
+					
+					// pre-receive scripts
+					List<String> preReceiveScripts = new ArrayList<String>();
+					Iterator<String> pres = preReceivePalette.getSelectedChoices();
+					while (pres.hasNext()) {
+						preReceiveScripts.add(pres.next());
+					}
+					repositoryModel.preReceiveScripts = preReceiveScripts;
+
+					// post-receive scripts
+					List<String> postReceiveScripts = new ArrayList<String>();
+					Iterator<String> post = postReceivePalette.getSelectedChoices();
+					while (post.hasNext()) {
+						postReceiveScripts.add(post.next());
+					}
+					repositoryModel.postReceiveScripts = postReceiveScripts;
+					
+					// custom fields
+					repositoryModel.customFields = new LinkedHashMap<String, String>();
+					for (int i = 0; i < customFieldsListView.size(); i++) {
+						ListItem<String> child = (ListItem<String>) customFieldsListView.get(i);
+						String key = child.getModelObject();
+
+						TextField<String> field = (TextField<String>) child.get("customFieldValue");
+						String value = field.getValue();
+						
+						repositoryModel.customFields.put(key, value);
+					}
+					
+					// save the repository
+					GitBlit.self().updateRepositoryModel(oldName, repositoryModel, isCreate);
+
+					// repository access permissions
+					if (repositoryModel.accessRestriction.exceeds(AccessRestrictionType.NONE)) {
+						GitBlit.self().setUserAccessPermissions(repositoryModel, repositoryUsers);
+						GitBlit.self().setTeamAccessPermissions(repositoryModel, repositoryTeams);
+					}
+				} catch (GitBlitException e) {
+					error(e.getMessage());
+					return;
+				}
+				setRedirect(false);
+				setResponsePage(RepositoriesPage.class);
+			}
+		};
+
+		// do not let the browser pre-populate these fields
+		form.add(new SimpleAttributeModifier("autocomplete", "off"));
+
+		// field names reflective match RepositoryModel fields
+		form.add(new TextField<String>("name").setEnabled(allowEditName));
+		form.add(new TextField<String>("description"));
+		form.add(ownersPalette);
+		form.add(new CheckBox("allowForks").setEnabled(GitBlit.getBoolean(Keys.web.allowForking, true)));
+		DropDownChoice<AccessRestrictionType> accessRestriction = new DropDownChoice<AccessRestrictionType>("accessRestriction", Arrays
+				.asList(AccessRestrictionType.values()), new AccessRestrictionRenderer());
+		form.add(accessRestriction);
+		form.add(new CheckBox("isFrozen"));
+		// TODO enable origin definition
+		form.add(new TextField<String>("origin").setEnabled(false/* isCreate */));
+		
+		// allow relinking HEAD to a branch or tag other than master on edit repository
+		List<String> availableRefs = new ArrayList<String>();
+		if (!ArrayUtils.isEmpty(repositoryModel.availableRefs)) {
+			availableRefs.addAll(repositoryModel.availableRefs);
+		}
+		form.add(new DropDownChoice<String>("HEAD", availableRefs).setEnabled(availableRefs.size() > 0));
+
+		boolean gcEnabled = GitBlit.getBoolean(Keys.git.enableGarbageCollection, false); 
+		List<Integer> gcPeriods = Arrays.asList(1, 2, 3, 4, 5, 7, 10, 14 );
+		form.add(new DropDownChoice<Integer>("gcPeriod", gcPeriods, new GCPeriodRenderer()).setEnabled(gcEnabled));
+		form.add(new TextField<String>("gcThreshold").setEnabled(gcEnabled));
+
+		// federation strategies - remove ORIGIN choice if this repository has
+		// no origin.
+		List<FederationStrategy> federationStrategies = new ArrayList<FederationStrategy>(
+				Arrays.asList(FederationStrategy.values()));
+		if (StringUtils.isEmpty(repositoryModel.origin)) {
+			federationStrategies.remove(FederationStrategy.FEDERATE_ORIGIN);
+		}
+		form.add(new DropDownChoice<FederationStrategy>("federationStrategy", federationStrategies,
+				new FederationTypeRenderer()));
+		form.add(new CheckBox("useTickets"));
+		form.add(new CheckBox("useDocs"));
+		form.add(new CheckBox("useIncrementalPushTags"));
+		form.add(new CheckBox("showRemoteBranches"));
+		form.add(new CheckBox("showReadme"));
+		form.add(new CheckBox("skipSizeCalculation"));
+		form.add(new CheckBox("skipSummaryMetrics"));
+		List<Integer> maxActivityCommits  = Arrays.asList(-1, 0, 25, 50, 75, 100, 150, 200, 250, 500 );
+		form.add(new DropDownChoice<Integer>("maxActivityCommits", maxActivityCommits, new MaxActivityCommitsRenderer()));
+
+		metricAuthorExclusions = new Model<String>(ArrayUtils.isEmpty(repositoryModel.metricAuthorExclusions) ? ""
+				: StringUtils.flattenStrings(repositoryModel.metricAuthorExclusions, " "));
+		form.add(new TextField<String>("metricAuthorExclusions", metricAuthorExclusions));
+
+		mailingLists = new Model<String>(ArrayUtils.isEmpty(repositoryModel.mailingLists) ? ""
+				: StringUtils.flattenStrings(repositoryModel.mailingLists, " "));
+		form.add(new TextField<String>("mailingLists", mailingLists));
+		form.add(indexedBranchesPalette);
+		
+		List<AuthorizationControl> acList = Arrays.asList(AuthorizationControl.values());
+		final RadioChoice<AuthorizationControl> authorizationControl = new RadioChoice<Constants.AuthorizationControl>(
+				"authorizationControl", acList, new AuthorizationControlRenderer());
+		form.add(authorizationControl);
+		
+		final CheckBox verifyCommitter = new CheckBox("verifyCommitter");
+		verifyCommitter.setOutputMarkupId(true);
+		form.add(verifyCommitter);
+
+		form.add(usersPalette);
+		form.add(teamsPalette);
+		form.add(federationSetsPalette);
+		form.add(preReceivePalette);
+		form.add(new BulletListPanel("inheritedPreReceive", getString("gb.inherited"), GitBlit.self()
+				.getPreReceiveScriptsInherited(repositoryModel)));
+		form.add(postReceivePalette);
+		form.add(new BulletListPanel("inheritedPostReceive", getString("gb.inherited"), GitBlit.self()
+				.getPostReceiveScriptsInherited(repositoryModel)));
+		
+		WebMarkupContainer customFieldsSection = new WebMarkupContainer("customFieldsSection");
+		customFieldsSection.add(customFieldsListView);
+		form.add(customFieldsSection.setVisible(!GitBlit.getString(Keys.groovy.customFields, "").isEmpty()));
+		
+		// initial enable/disable of permission controls
+		if (repositoryModel.accessRestriction.equals(AccessRestrictionType.NONE)) {
+			// anonymous everything, disable all controls
+			usersPalette.setEnabled(false);
+			teamsPalette.setEnabled(false);
+			authorizationControl.setEnabled(false);
+			verifyCommitter.setEnabled(false);
+		} else {
+			// authenticated something
+			// enable authorization controls
+			authorizationControl.setEnabled(true);
+			verifyCommitter.setEnabled(true);
+			
+			boolean allowFineGrainedControls = repositoryModel.authorizationControl.equals(AuthorizationControl.NAMED);
+			usersPalette.setEnabled(allowFineGrainedControls);
+			teamsPalette.setEnabled(allowFineGrainedControls);
+		}
+		
+		accessRestriction.add(new AjaxFormComponentUpdatingBehavior("onchange") {
+	           
+			private static final long serialVersionUID = 1L;
+
+			protected void onUpdate(AjaxRequestTarget target) {
+				// enable/disable permissions panel based on access restriction
+				boolean allowAuthorizationControl = repositoryModel.accessRestriction.exceeds(AccessRestrictionType.NONE);
+				authorizationControl.setEnabled(allowAuthorizationControl);
+				verifyCommitter.setEnabled(allowAuthorizationControl);
+				
+				boolean allowFineGrainedControls = allowAuthorizationControl && repositoryModel.authorizationControl.equals(AuthorizationControl.NAMED);
+				usersPalette.setEnabled(allowFineGrainedControls);
+				teamsPalette.setEnabled(allowFineGrainedControls);
+				
+				if (allowFineGrainedControls) {
+					repositoryModel.authorizationControl = AuthorizationControl.NAMED;
+				}
+				
+				target.addComponent(authorizationControl);
+				target.addComponent(verifyCommitter);
+				target.addComponent(usersPalette);
+				target.addComponent(teamsPalette);
+			}
+		});
+		
+		authorizationControl.add(new AjaxFormChoiceComponentUpdatingBehavior() {
+	           
+			private static final long serialVersionUID = 1L;
+
+			protected void onUpdate(AjaxRequestTarget target) {
+				// enable/disable permissions panel based on access restriction
+				boolean allowAuthorizationControl = repositoryModel.accessRestriction.exceeds(AccessRestrictionType.NONE);
+				authorizationControl.setEnabled(allowAuthorizationControl);
+				
+				boolean allowFineGrainedControls = allowAuthorizationControl && repositoryModel.authorizationControl.equals(AuthorizationControl.NAMED);
+				usersPalette.setEnabled(allowFineGrainedControls);
+				teamsPalette.setEnabled(allowFineGrainedControls);
+				
+				if (allowFineGrainedControls) {
+					repositoryModel.authorizationControl = AuthorizationControl.NAMED;
+				}
+				
+				target.addComponent(authorizationControl);
+				target.addComponent(usersPalette);
+				target.addComponent(teamsPalette);
+			}
+		});
+		
+		form.add(new Button("save"));
+		Button cancel = new Button("cancel") {
+			private static final long serialVersionUID = 1L;
+
+			@Override
+			public void onSubmit() {
+				setResponsePage(RepositoriesPage.class);
+			}
+		};
+		cancel.setDefaultFormProcessing(false);
+		form.add(cancel);
+
+		add(form);
+	}
+
+	/**
+	 * Unfortunately must repeat part of AuthorizaitonStrategy here because that
+	 * mechanism does not take PageParameters into consideration, only page
+	 * instantiation.
+	 * 
+	 * Repository Owners should be able to edit their repository.
+	 */
+	private void checkPermissions(RepositoryModel model) {
+		boolean authenticateAdmin = GitBlit.getBoolean(Keys.web.authenticateAdminPages, true);
+		boolean allowAdmin = GitBlit.getBoolean(Keys.web.allowAdministration, true);
+
+		GitBlitWebSession session = GitBlitWebSession.get();
+		UserModel user = session.getUser();
+
+		if (allowAdmin) {
+			if (authenticateAdmin) {
+				if (user == null) {
+					// No Login Available
+					error(getString("gb.errorAdminLoginRequired"), true);
+				}
+				if (isCreate) {
+					// Create Repository
+					if (!user.canCreate() && !user.canAdmin()) {
+						// Only administrators or permitted users may create
+						error(getString("gb.errorOnlyAdminMayCreateRepository"), true);
+					}
+				} else {
+					// Edit Repository
+					if (user.canAdmin()) {
+						// Admins can edit everything
+						isAdmin = true;
+						return;
+					} else {
+						if (!model.isOwner(user.username)) {
+							// User is not an Admin nor Owner
+							error(getString("gb.errorOnlyAdminOrOwnerMayEditRepository"), true);
+						}
+					}
+				}
+			}
+		} else {
+			// No Administration Permitted
+			error(getString("gb.errorAdministrationDisabled"), true);
+		}
+	}
+
+	private class AccessRestrictionRenderer implements IChoiceRenderer<AccessRestrictionType> {
+
+		private static final long serialVersionUID = 1L;
+
+		private final Map<AccessRestrictionType, String> map;
+
+		public AccessRestrictionRenderer() {
+			map = getAccessRestrictions();
+		}
+
+		@Override
+		public String getDisplayValue(AccessRestrictionType type) {
+			return map.get(type);
+		}
+
+		@Override
+		public String getIdValue(AccessRestrictionType type, int index) {
+			return Integer.toString(index);
+		}
+	}
+
+	private class FederationTypeRenderer implements IChoiceRenderer<FederationStrategy> {
+
+		private static final long serialVersionUID = 1L;
+
+		private final Map<FederationStrategy, String> map;
+
+		public FederationTypeRenderer() {
+			map = getFederationTypes();
+		}
+
+		@Override
+		public String getDisplayValue(FederationStrategy type) {
+			return map.get(type);
+		}
+
+		@Override
+		public String getIdValue(FederationStrategy type, int index) {
+			return Integer.toString(index);
+		}
+	}
+	
+	private class AuthorizationControlRenderer implements IChoiceRenderer<AuthorizationControl> {
+
+		private static final long serialVersionUID = 1L;
+
+		private final Map<AuthorizationControl, String> map;
+
+		public AuthorizationControlRenderer() {
+			map = getAuthorizationControls();
+		}
+
+		@Override
+		public String getDisplayValue(AuthorizationControl type) {
+			return map.get(type);
+		}
+
+		@Override
+		public String getIdValue(AuthorizationControl type, int index) {
+			return Integer.toString(index);
+		}
+	}
+	
+	private class GCPeriodRenderer implements IChoiceRenderer<Integer> {
+
+		private static final long serialVersionUID = 1L;
+
+		public GCPeriodRenderer() {
+		}
+
+		@Override
+		public String getDisplayValue(Integer value) {
+			if (value == 1) {
+				return getString("gb.duration.oneDay");
+			} else {
+				return MessageFormat.format(getString("gb.duration.days"), value);
+			}
+		}
+
+		@Override
+		public String getIdValue(Integer value, int index) {
+			return Integer.toString(index);
+		}
+	}
+	
+	private class MaxActivityCommitsRenderer implements IChoiceRenderer<Integer> {
+
+		private static final long serialVersionUID = 1L;
+
+		public MaxActivityCommitsRenderer() {
+		}
+
+		@Override
+		public String getDisplayValue(Integer value) {
+			if (value == -1) {
+				return getString("gb.excludeFromActivity");
+			} else if (value == 0) {
+				return getString("gb.noMaximum");
+			} else {
+				return value + " " + getString("gb.commits");
+			}
+		}
+
+		@Override
+		public String getIdValue(Integer value, int index) {
+			return Integer.toString(index);
+		}
+	}
+	
+}
diff --git a/src/com/gitblit/wicket/pages/EditTeamPage.html b/src/main/java/com/gitblit/wicket/pages/EditTeamPage.html
similarity index 100%
rename from src/com/gitblit/wicket/pages/EditTeamPage.html
rename to src/main/java/com/gitblit/wicket/pages/EditTeamPage.html
diff --git a/src/main/java/com/gitblit/wicket/pages/EditTeamPage.java b/src/main/java/com/gitblit/wicket/pages/EditTeamPage.java
new file mode 100644
index 0000000..25fbd98
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/EditTeamPage.java
@@ -0,0 +1,255 @@
+/*
+ * 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.wicket.pages;
+
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.wicket.PageParameters;
+import org.apache.wicket.behavior.SimpleAttributeModifier;
+import org.apache.wicket.extensions.markup.html.form.palette.Palette;
+import org.apache.wicket.markup.html.form.Button;
+import org.apache.wicket.markup.html.form.CheckBox;
+import org.apache.wicket.markup.html.form.Form;
+import org.apache.wicket.markup.html.form.TextField;
+import org.apache.wicket.model.CompoundPropertyModel;
+import org.apache.wicket.model.IModel;
+import org.apache.wicket.model.Model;
+import org.apache.wicket.model.util.CollectionModel;
+import org.apache.wicket.model.util.ListModel;
+
+import com.gitblit.Constants.RegistrantType;
+import com.gitblit.GitBlit;
+import com.gitblit.GitBlitException;
+import com.gitblit.Keys;
+import com.gitblit.models.RegistrantAccessPermission;
+import com.gitblit.models.TeamModel;
+import com.gitblit.utils.StringUtils;
+import com.gitblit.wicket.RequiresAdminRole;
+import com.gitblit.wicket.StringChoiceRenderer;
+import com.gitblit.wicket.WicketUtils;
+import com.gitblit.wicket.panels.BulletListPanel;
+import com.gitblit.wicket.panels.RegistrantPermissionsPanel;
+
+@RequiresAdminRole
+public class EditTeamPage extends RootSubPage {
+
+	private final boolean isCreate;
+
+	private IModel<String> mailingLists;
+
+	public EditTeamPage() {
+		// create constructor
+		super();
+		isCreate = true;
+		setupPage(new TeamModel(""));
+		setStatelessHint(false);
+		setOutputMarkupId(true);
+	}
+
+	public EditTeamPage(PageParameters params) {
+		// edit constructor
+		super(params);
+		isCreate = false;
+		String name = WicketUtils.getTeamname(params);
+		TeamModel model = GitBlit.self().getTeamModel(name);
+		setupPage(model);
+		setStatelessHint(false);
+		setOutputMarkupId(true);
+	}
+
+	@Override
+	protected boolean requiresPageMap() {
+		return true;
+	}
+	
+	@Override
+	protected Class<? extends BasePage> getRootNavPageClass() {
+		return UsersPage.class;
+	}
+
+	protected void setupPage(final TeamModel teamModel) {
+		if (isCreate) {
+			super.setupPage(getString("gb.newTeam"), "");
+		} else {
+			super.setupPage(getString("gb.edit"), teamModel.name);
+		}
+
+		CompoundPropertyModel<TeamModel> model = new CompoundPropertyModel<TeamModel>(teamModel);
+
+		List<String> repos = getAccessRestrictedRepositoryList(true, null);
+
+		List<String> teamUsers = new ArrayList<String>(teamModel.users);
+		Collections.sort(teamUsers);
+		List<String> preReceiveScripts = new ArrayList<String>();
+		List<String> postReceiveScripts = new ArrayList<String>();
+
+		final String oldName = teamModel.name;
+		final List<RegistrantAccessPermission> permissions = teamModel.getRepositoryPermissions();
+
+		// users palette
+		final Palette<String> users = new Palette<String>("users", new ListModel<String>(
+				new ArrayList<String>(teamUsers)), new CollectionModel<String>(GitBlit.self()
+				.getAllUsernames()), new StringChoiceRenderer(), 10, false);
+
+		// pre-receive palette
+		if (teamModel.preReceiveScripts != null) {
+			preReceiveScripts.addAll(teamModel.preReceiveScripts);
+		}
+		final Palette<String> preReceivePalette = new Palette<String>("preReceiveScripts",
+				new ListModel<String>(preReceiveScripts), new CollectionModel<String>(GitBlit
+						.self().getPreReceiveScriptsUnused(null)), new StringChoiceRenderer(),
+						12, true);
+
+		// post-receive palette
+		if (teamModel.postReceiveScripts != null) {
+			postReceiveScripts.addAll(teamModel.postReceiveScripts);
+		}
+		final Palette<String> postReceivePalette = new Palette<String>("postReceiveScripts",
+				new ListModel<String>(postReceiveScripts), new CollectionModel<String>(GitBlit
+						.self().getPostReceiveScriptsUnused(null)), new StringChoiceRenderer(),
+								12, true);
+
+		Form<TeamModel> form = new Form<TeamModel>("editForm", model) {
+
+			private static final long serialVersionUID = 1L;
+
+			/*
+			 * (non-Javadoc)
+			 * 
+			 * @see org.apache.wicket.markup.html.form.Form#onSubmit()
+			 */
+			@Override
+			protected void onSubmit() {
+				String teamname = teamModel.name;
+				if (StringUtils.isEmpty(teamname)) {
+					error(getString("gb.pleaseSetTeamName"));
+					return;
+				}
+				if (isCreate) {
+					TeamModel model = GitBlit.self().getTeamModel(teamname);
+					if (model != null) {
+						error(MessageFormat.format(getString("gb.teamNameUnavailable"), teamname));
+						return;
+					}
+				}
+				// update team permissions
+				for (RegistrantAccessPermission repositoryPermission : permissions) {
+					teamModel.setRepositoryPermission(repositoryPermission.registrant, repositoryPermission.permission);
+				}
+
+				Iterator<String> selectedUsers = users.getSelectedChoices();
+				List<String> members = new ArrayList<String>();
+				while (selectedUsers.hasNext()) {
+					members.add(selectedUsers.next().toLowerCase());
+				}
+				teamModel.users.clear();
+				teamModel.users.addAll(members);
+
+				// set mailing lists
+				String ml = mailingLists.getObject();
+				if (!StringUtils.isEmpty(ml)) {
+					Set<String> list = new HashSet<String>();
+					for (String address : ml.split("(,|\\s)")) {
+						if (StringUtils.isEmpty(address)) {
+							continue;
+						}
+						list.add(address.toLowerCase());
+					}
+					teamModel.mailingLists.clear();
+					teamModel.mailingLists.addAll(list);
+				}
+
+				// pre-receive scripts
+				List<String> preReceiveScripts = new ArrayList<String>();
+				Iterator<String> pres = preReceivePalette.getSelectedChoices();
+				while (pres.hasNext()) {
+					preReceiveScripts.add(pres.next());
+				}
+				teamModel.preReceiveScripts.clear();
+				teamModel.preReceiveScripts.addAll(preReceiveScripts);
+
+				// post-receive scripts
+				List<String> postReceiveScripts = new ArrayList<String>();
+				Iterator<String> post = postReceivePalette.getSelectedChoices();
+				while (post.hasNext()) {
+					postReceiveScripts.add(post.next());
+				}
+				teamModel.postReceiveScripts.clear();
+				teamModel.postReceiveScripts.addAll(postReceiveScripts);
+
+				try {
+					GitBlit.self().updateTeamModel(oldName, teamModel, isCreate);
+				} catch (GitBlitException e) {
+					error(e.getMessage());
+					return;
+				}
+				setRedirect(false);
+				if (isCreate) {
+					// create another team
+					info(MessageFormat.format(getString("gb.teamCreated"),
+							teamModel.name));
+				}
+				// back to users page
+				setResponsePage(UsersPage.class);
+			}
+		};
+
+		// do not let the browser pre-populate these fields
+		form.add(new SimpleAttributeModifier("autocomplete", "off"));
+
+		// not all user services support manipulating team memberships
+		boolean editMemberships = GitBlit.self().supportsTeamMembershipChanges(null);
+		
+		// field names reflective match TeamModel fields
+		form.add(new TextField<String>("name"));
+		form.add(new CheckBox("canAdmin"));
+		form.add(new CheckBox("canFork").setEnabled(GitBlit.getBoolean(Keys.web.allowForking, true)));
+		form.add(new CheckBox("canCreate"));
+		form.add(users.setEnabled(editMemberships));
+		mailingLists = new Model<String>(teamModel.mailingLists == null ? ""
+				: StringUtils.flattenStrings(teamModel.mailingLists, " "));
+		form.add(new TextField<String>("mailingLists", mailingLists));
+
+		form.add(new RegistrantPermissionsPanel("repositories", RegistrantType.REPOSITORY,
+				repos, permissions, getAccessPermissions()));
+		form.add(preReceivePalette);
+		form.add(new BulletListPanel("inheritedPreReceive", "inherited", GitBlit.self()
+				.getPreReceiveScriptsInherited(null)));
+		form.add(postReceivePalette);
+		form.add(new BulletListPanel("inheritedPostReceive", "inherited", GitBlit.self()
+				.getPostReceiveScriptsInherited(null)));
+
+		form.add(new Button("save"));
+		Button cancel = new Button("cancel") {
+			private static final long serialVersionUID = 1L;
+
+			@Override
+			public void onSubmit() {
+				setResponsePage(UsersPage.class);
+			}
+		};
+		cancel.setDefaultFormProcessing(false);
+		form.add(cancel);
+
+		add(form);
+	}
+}
diff --git a/src/com/gitblit/wicket/pages/EditUserPage.html b/src/main/java/com/gitblit/wicket/pages/EditUserPage.html
similarity index 100%
rename from src/com/gitblit/wicket/pages/EditUserPage.html
rename to src/main/java/com/gitblit/wicket/pages/EditUserPage.html
diff --git a/src/main/java/com/gitblit/wicket/pages/EditUserPage.java b/src/main/java/com/gitblit/wicket/pages/EditUserPage.java
new file mode 100644
index 0000000..93230c2
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/EditUserPage.java
@@ -0,0 +1,266 @@
+/*
+ * 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.wicket.pages;
+
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.wicket.PageParameters;
+import org.apache.wicket.behavior.SimpleAttributeModifier;
+import org.apache.wicket.extensions.markup.html.form.palette.Palette;
+import org.apache.wicket.markup.html.form.Button;
+import org.apache.wicket.markup.html.form.CheckBox;
+import org.apache.wicket.markup.html.form.Form;
+import org.apache.wicket.markup.html.form.PasswordTextField;
+import org.apache.wicket.markup.html.form.TextField;
+import org.apache.wicket.model.CompoundPropertyModel;
+import org.apache.wicket.model.Model;
+import org.apache.wicket.model.util.CollectionModel;
+import org.apache.wicket.model.util.ListModel;
+
+import com.gitblit.Constants.RegistrantType;
+import com.gitblit.GitBlit;
+import com.gitblit.GitBlitException;
+import com.gitblit.Keys;
+import com.gitblit.models.RegistrantAccessPermission;
+import com.gitblit.models.TeamModel;
+import com.gitblit.models.UserModel;
+import com.gitblit.utils.StringUtils;
+import com.gitblit.wicket.RequiresAdminRole;
+import com.gitblit.wicket.StringChoiceRenderer;
+import com.gitblit.wicket.WicketUtils;
+import com.gitblit.wicket.panels.RegistrantPermissionsPanel;
+
+@RequiresAdminRole
+public class EditUserPage extends RootSubPage {
+
+	private final boolean isCreate;
+	
+	public EditUserPage() {
+		// create constructor
+		super();
+		if (!GitBlit.self().supportsAddUser()) {
+			error(MessageFormat.format(getString("gb.userServiceDoesNotPermitAddUser"),
+					GitBlit.getString(Keys.realm.userService, "${baseFolder}/users.conf")), true);
+		}
+		isCreate = true;
+		setupPage(new UserModel(""));
+		setStatelessHint(false);
+		setOutputMarkupId(true);
+	}
+
+	public EditUserPage(PageParameters params) {
+		// edit constructor
+		super(params);
+		isCreate = false;
+		String name = WicketUtils.getUsername(params);
+		UserModel model = GitBlit.self().getUserModel(name);
+		setupPage(model);
+		setStatelessHint(false);
+		setOutputMarkupId(true);
+	}
+	
+	@Override
+	protected boolean requiresPageMap() {
+		return true;
+	}
+	
+	@Override
+	protected Class<? extends BasePage> getRootNavPageClass() {
+		return UsersPage.class;
+	}
+
+	protected void setupPage(final UserModel userModel) {
+		if (isCreate) {
+			super.setupPage(getString("gb.newUser"), "");
+		} else {
+			super.setupPage(getString("gb.edit"), userModel.username);
+		}
+
+		final Model<String> confirmPassword = new Model<String>(
+				StringUtils.isEmpty(userModel.password) ? "" : userModel.password);
+		CompoundPropertyModel<UserModel> model = new CompoundPropertyModel<UserModel>(userModel);
+
+		// build list of projects including all repositories wildcards
+		List<String> repos = getAccessRestrictedRepositoryList(true, userModel);
+		
+		List<String> userTeams = new ArrayList<String>();
+		for (TeamModel team : userModel.teams) {
+			userTeams.add(team.name);
+		}
+		Collections.sort(userTeams);
+		
+		final String oldName = userModel.username;
+		final List<RegistrantAccessPermission> permissions = GitBlit.self().getUserAccessPermissions(userModel);
+
+		final Palette<String> teams = new Palette<String>("teams", new ListModel<String>(
+				new ArrayList<String>(userTeams)), new CollectionModel<String>(GitBlit.self()
+				.getAllTeamnames()), new StringChoiceRenderer(), 10, false);
+		Form<UserModel> form = new Form<UserModel>("editForm", model) {
+
+			private static final long serialVersionUID = 1L;
+			
+			/*
+			 * (non-Javadoc)
+			 * 
+			 * @see org.apache.wicket.markup.html.form.Form#onSubmit()
+			 */
+			@Override
+			protected void onSubmit() {
+				if (StringUtils.isEmpty(userModel.username)) {
+					error(getString("gb.pleaseSetUsername"));
+					return;
+				}
+				// force username to lower-case
+				userModel.username = userModel.username.toLowerCase();
+				String username = userModel.username;
+				if (isCreate) {
+					UserModel model = GitBlit.self().getUserModel(username);
+					if (model != null) {
+						error(MessageFormat.format(getString("gb.usernameUnavailable"), username));
+						return;
+					}
+				}
+				boolean rename = !StringUtils.isEmpty(oldName)
+						&& !oldName.equalsIgnoreCase(username);
+				if (GitBlit.self().supportsCredentialChanges(userModel)) {
+					if (!userModel.password.equals(confirmPassword.getObject())) {
+						error(getString("gb.passwordsDoNotMatch"));
+						return;
+					}
+					String password = userModel.password;
+					if (!password.toUpperCase().startsWith(StringUtils.MD5_TYPE)
+							&& !password.toUpperCase().startsWith(StringUtils.COMBINED_MD5_TYPE)) {
+						// This is a plain text password.
+						// Check length.
+						int minLength = GitBlit.getInteger(Keys.realm.minPasswordLength, 5);
+						if (minLength < 4) {
+							minLength = 4;
+						}
+						if (password.trim().length() < minLength) {
+							error(MessageFormat.format(getString("gb.passwordTooShort"),
+									minLength));
+							return;
+						}
+	
+						// Optionally store the password MD5 digest.
+						String type = GitBlit.getString(Keys.realm.passwordStorage, "md5");
+						if (type.equalsIgnoreCase("md5")) {
+							// store MD5 digest of password
+							userModel.password = StringUtils.MD5_TYPE
+									+ StringUtils.getMD5(userModel.password);
+						} else if (type.equalsIgnoreCase("combined-md5")) {
+							// store MD5 digest of username+password
+							userModel.password = StringUtils.COMBINED_MD5_TYPE
+									+ StringUtils.getMD5(username + userModel.password);
+						}
+					} else if (rename
+							&& password.toUpperCase().startsWith(StringUtils.COMBINED_MD5_TYPE)) {
+						error(getString("gb.combinedMd5Rename"));
+						return;
+					}
+				}
+
+				// update user permissions
+				for (RegistrantAccessPermission repositoryPermission : permissions) {
+					userModel.setRepositoryPermission(repositoryPermission.registrant, repositoryPermission.permission);
+				}
+
+				Iterator<String> selectedTeams = teams.getSelectedChoices();
+				userModel.teams.clear();
+				while (selectedTeams.hasNext()) {
+					TeamModel team = GitBlit.self().getTeamModel(selectedTeams.next());
+					if (team == null) {
+						continue;
+					}
+					userModel.teams.add(team);
+				}
+
+				try {					
+					GitBlit.self().updateUserModel(oldName, userModel, isCreate);
+				} catch (GitBlitException e) {
+					error(e.getMessage());
+					return;
+				}
+				setRedirect(false);
+				if (isCreate) {
+					// create another user
+					info(MessageFormat.format(getString("gb.userCreated"),
+							userModel.username));
+					setResponsePage(EditUserPage.class);
+				} else {
+					// back to users page
+					setResponsePage(UsersPage.class);
+				}
+			}
+		};
+		
+		// do not let the browser pre-populate these fields
+		form.add(new SimpleAttributeModifier("autocomplete", "off"));
+		
+		// not all user services support manipulating username and password
+		boolean editCredentials = GitBlit.self().supportsCredentialChanges(userModel);
+		
+		// not all user services support manipulating display name
+		boolean editDisplayName = GitBlit.self().supportsDisplayNameChanges(userModel);
+
+		// not all user services support manipulating email address
+		boolean editEmailAddress = GitBlit.self().supportsEmailAddressChanges(userModel);
+
+		// not all user services support manipulating team memberships
+		boolean editTeams = GitBlit.self().supportsTeamMembershipChanges(userModel);
+
+		// field names reflective match UserModel fields
+		form.add(new TextField<String>("username").setEnabled(editCredentials));
+		PasswordTextField passwordField = new PasswordTextField("password");
+		passwordField.setResetPassword(false);
+		form.add(passwordField.setEnabled(editCredentials));
+		PasswordTextField confirmPasswordField = new PasswordTextField("confirmPassword",
+				confirmPassword);
+		confirmPasswordField.setResetPassword(false);
+		form.add(confirmPasswordField.setEnabled(editCredentials));
+		form.add(new TextField<String>("displayName").setEnabled(editDisplayName));
+		form.add(new TextField<String>("emailAddress").setEnabled(editEmailAddress));
+		form.add(new CheckBox("canAdmin"));
+		form.add(new CheckBox("canFork").setEnabled(GitBlit.getBoolean(Keys.web.allowForking, true)));
+		form.add(new CheckBox("canCreate"));
+		form.add(new CheckBox("excludeFromFederation"));
+		form.add(new RegistrantPermissionsPanel("repositories",	RegistrantType.REPOSITORY, repos, permissions, getAccessPermissions()));
+		form.add(teams.setEnabled(editTeams));
+
+		form.add(new TextField<String>("organizationalUnit").setEnabled(editDisplayName));
+		form.add(new TextField<String>("organization").setEnabled(editDisplayName));
+		form.add(new TextField<String>("locality").setEnabled(editDisplayName));
+		form.add(new TextField<String>("stateProvince").setEnabled(editDisplayName));
+		form.add(new TextField<String>("countryCode").setEnabled(editDisplayName));
+		form.add(new Button("save"));
+		Button cancel = new Button("cancel") {
+			private static final long serialVersionUID = 1L;
+
+			@Override
+			public void onSubmit() {
+				setResponsePage(UsersPage.class);
+			}
+		};
+		cancel.setDefaultFormProcessing(false);
+		form.add(cancel);
+
+		add(form);
+	}
+}
diff --git a/src/main/java/com/gitblit/wicket/pages/EmptyRepositoryPage.html b/src/main/java/com/gitblit/wicket/pages/EmptyRepositoryPage.html
new file mode 100644
index 0000000..bda5694
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/EmptyRepositoryPage.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"  
+      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"  
+      xml:lang="en"  
+      lang="en"> 
+
+<body>
+<wicket:extend>
+<div class="container">
+	<h2>Empty Repository</h2>
+	<p></p>
+		<div class="row">
+			<div class="span10">
+				<div class="alert alert-success">
+					<span wicket:id="repository" style="font-weight: bold;">[repository]</span> is an empty repository and can not be viewed by Gitblit.
+					<p></p>		
+					Please push some commits to <span wicket:id="pushurl"></span>
+					<p></p>
+					<hr/>
+					After you have pushed commits you may <b>refresh</b> this page to view your repository.
+				</div>
+			</div>
+		</div>
+		
+		<h3>Git Command-Line Syntax</h3>
+		<span style="padding-bottom:5px;">If you do not have a local Git repository, then you should clone this repository, commit some files, and then push your commits back to Gitblit.</span>
+		<p></p>
+		<pre style="padding: 5px 30px;" wicket:id="cloneSyntax"></pre>
+		<p></p>
+		<span style="padding-bottom:5px;">If you already have a local Git repository with commits, then you may add this repository as a remote and push to it.</span>
+		<p></p>
+		<pre wicket:id="remoteSyntax" style="padding: 5px 30px;"></pre>
+		<p></p>
+		<h3>Learn Git</h3>
+		If you are unsure how to use this information, consider reviewing the <a href="http://book.git-scm.com">Git Community Book</a> or <a href="http://progit.org/book" target="_blank">Pro Git</a> for a better understanding on how to use Git.
+		<p></p>
+		<h4>Open Source Git Clients</h4>
+		<ul>
+			<li><a href="http://git-scm.com">Git</a> - the official, command-line Git</li>
+			<li><a href="http://tortoisegit.googlecode.com">TortoiseGit</a> - Windows file explorer integration (requires official, command-line Git)</li>
+			<li><a href="http://eclipse.org/egit">Eclipse/EGit</a> - Git for the Eclipse IDE (based on JGit, like Gitblit)</li>
+			<li><a href="https://code.google.com/p/gitextensions/">Git Extensions</a> - C# frontend for Git that features Windows Explorer and Visual Studio integration</li>
+			<li><a href="http://rowanj.github.io/gitx/">GitX-dev</a> - a Mac OS X Git client</li>			
+		</ul>
+		<p></p>
+		<h4>Commercial/Closed-Source Git Clients</h4>
+		<ul>
+			<li><a href="http://www.syntevo.com/smartgithg">SmartGit/Hg</a> - A Java Git and Mercurial client for Windows, Mac, and Linux</li>
+			<li><a href="http://www.sourcetreeapp.com/">SourceTree</a> - A free Git and Mercurial client for Windows & Mac</li>
+			<li><a href="http://www.git-tower.com/">Tower</a> - a Mac OS X Git client</li>
+		</ul>
+</div>
+</wicket:extend>	
+</body>
+</html>
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/pages/EmptyRepositoryPage.java b/src/main/java/com/gitblit/wicket/pages/EmptyRepositoryPage.java
new file mode 100644
index 0000000..41f109a
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/EmptyRepositoryPage.java
@@ -0,0 +1,76 @@
+/*
+ * 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.wicket.pages;
+
+import java.text.MessageFormat;
+import java.util.List;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.wicket.PageParameters;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.protocol.http.WebRequest;
+
+import com.gitblit.GitBlit;
+import com.gitblit.models.RepositoryModel;
+import com.gitblit.models.RepositoryUrl;
+import com.gitblit.models.UserModel;
+import com.gitblit.wicket.GitBlitWebSession;
+import com.gitblit.wicket.GitblitRedirectException;
+import com.gitblit.wicket.WicketUtils;
+import com.gitblit.wicket.panels.RepositoryUrlPanel;
+
+public class EmptyRepositoryPage extends RootPage {
+
+	public EmptyRepositoryPage(PageParameters params) {
+		super(params);
+
+		setVersioned(false);
+
+		String repositoryName = WicketUtils.getRepositoryName(params);
+		RepositoryModel repository = GitBlit.self().getRepositoryModel(repositoryName);
+		if (repository == null) {
+			error(getString("gb.canNotLoadRepository") + " " + repositoryName, true);
+		}
+		
+		if (repository.hasCommits) {
+			// redirect to the summary page if this repository is not empty
+			throw new GitblitRedirectException(SummaryPage.class, params);
+		}
+		
+		setupPage(repositoryName, getString("gb.emptyRepository"));
+
+		UserModel user = GitBlitWebSession.get().getUser();
+		if (user == null) {
+			user = UserModel.ANONYMOUS;
+		}
+		
+		HttpServletRequest req = ((WebRequest) getRequest()).getHttpServletRequest();
+		List<RepositoryUrl> repositoryUrls = GitBlit.self().getRepositoryUrls(req, user, repository);
+		RepositoryUrl primaryUrl = repositoryUrls.size() == 0 ? null : repositoryUrls.get(0);
+		String url = primaryUrl != null ? primaryUrl.url : "";
+		
+		add(new Label("repository", repositoryName));
+		add(new RepositoryUrlPanel("pushurl", false, user, repository));
+		add(new Label("cloneSyntax", MessageFormat.format("git clone {0}", url)));
+		add(new Label("remoteSyntax", MessageFormat.format("git remote add gitblit {0}\ngit push gitblit master", url)));
+	}
+	
+	@Override
+	protected Class<? extends BasePage> getRootNavPageClass() {
+		return RepositoriesPage.class;
+	}
+}
diff --git a/src/main/java/com/gitblit/wicket/pages/EmptyRepositoryPage_es.html b/src/main/java/com/gitblit/wicket/pages/EmptyRepositoryPage_es.html
new file mode 100644
index 0000000..554a8d9
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/EmptyRepositoryPage_es.html
@@ -0,0 +1,58 @@
+
+
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"  
+      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"  
+      xml:lang="en"  
+      lang="es"> 
+
+<body>
+<wicket:extend>
+<div class="container">
+		<h2>Repositorio Vac&iacute;o</h2>
+		<p></p>
+		<div class="row">
+			<div class="span7">
+				<div class="alert alert-success">
+					<span wicket:id="repository" style="font-weight: bold;">[repository]</span> es un repositorio vac&iacute;o y no puede ser visto en Gitblit.
+					<p></p>
+					Por favor, empuja algunas consignas a <span wicket:id="pushurl"></span>
+					<p></p>
+					<hr/>
+					Despu&eacute;s de empujar tus consignas puedes <b>refrescar</b> &eacute;sta p&aacute;gina para ver tu Repositorio.
+				</div>
+			</div>
+		</div>
+		
+		<h3>Sintaxis de la L&iacute;nea de Comandos de Git</h3>
+		<span style="padding-bottom:5px;">Si no tienes un Repositiorio local Git, puedes clonar &eacute;ste, consignar algunos archivos, y despu&eacute;s empujar las consignas de vuelta a Gitblit.</span>
+		<p></p>
+		<pre style="padding: 5px 30px;" wicket:id="cloneSyntax"></pre>
+		<p></p>
+		<span style="padding-bottom:5px;">Si ya tienes un repositorio local Git con algunas consignas, puedes a&ntilde;adir &eacute;ste como remoto y empujar desde all&iacute;.</span>
+		<p></p>
+		<pre style="padding: 5px 30px;" wicket:id="remoteSyntax"></pre>
+		<p></p>
+		<h3>Aprender Git</h3>
+		Si no est&aacute;s seguro de como usar esta informaci&oacute;n, &eacute;chale un vistazo al <a href="http://book.git-scm.com">Libro de la cominidad Git</a> o <a href="http://progit.org/book" target="_blank">Pro Git</a> para una mejor compresi&oacute;n de como usar Git.
+		<p></p>
+		<h4>Clientes Git de C&oacute;digo abierto.</h4>
+		<ul>
+			<li><a href="http://git-scm.com">Git</a> - El Git oficial en l&iacute;nea de comandos</li>
+			<li><a href="http://tortoisegit.googlecode.com">TortoiseGit</a> - Explorador de archivos integrado en Windows (necesita Git oficial en l&iacute;nea de comandos)</li>
+			<li><a href="http://eclipse.org/egit">Eclipse/EGit</a> - Git para el IDE de Eclipse (basado en JGit, como Gitblit)</li>
+			<li><a href="https://code.google.com/p/gitextensions/">Git Extensions</a> - Interfaz de usuario gr&aacute;fico Git en C# con integraci&oacute;n en IE y en Visual Studio</li>
+			<li><a href="http://rowanj.github.io/gitx/">GitX-dev</a> - Cliente Git para Mac OS X</li>			
+		</ul>
+		<p></p>
+		<h4>Clientes Git comerciales</h4>
+		<ul>
+			<li><a href="http://www.syntevo.com/smartgithg">SmartGit/Hg</a> - aplicaci&oacute;n Java (necesita Git oficial en l&iacute;nea de comandos)</li>
+			<li><a href="http://www.sourcetreeapp.com/">SourceTree</a> - Un cliente Git gratuito para Mac, Mercurial, y SVN</li>
+			<li><a href="http://www.git-tower.com/">Tower</a> - Cliente Git para Mac OS X</li>
+		</ul>
+</div>		
+</wicket:extend>	
+</body>
+</html>
+
diff --git a/src/main/java/com/gitblit/wicket/pages/EmptyRepositoryPage_ko.html b/src/main/java/com/gitblit/wicket/pages/EmptyRepositoryPage_ko.html
new file mode 100644
index 0000000..17db15d
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/EmptyRepositoryPage_ko.html
@@ -0,0 +1,59 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"  
+      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"  
+      xml:lang="en"  
+      lang="en"> 
+
+<body>
+<wicket:extend>
+<div class="container">
+		<h2>비어있는 저장소</h2>
+		<p></p>
+		<div class="row">
+			<div class="span10">
+				<div class="alert alert-success">
+					<span wicket:id="repository" style="font-weight: bold;">[repository]</span> 저장소는 비어 있어서 Gitblit 에서 볼 수 없습니다.
+					<p></p>
+					이 Git url 에 커밋해 주세요. <span wicket:id="pushurl"></span>
+					<p></p>
+					<hr/>
+					After you have pushed commits you may <b>refresh</b> this page to view your repository.
+				</div>
+			</div>
+		</div>
+
+		<p></p>
+		<h3>Git 명령어</h3>
+		<span style="padding-bottom:5px;">로컬 Git 저장소가 없다면, 이 저장소를 클론(clone) 한 후, 몇 파일을 커밋하고, 그 커밋을 Gitblit 에 푸시(push) 하세요.</span>
+		<p></p>
+		<pre style="padding: 5px 30px;" wicket:id="cloneSyntax"></pre>
+		<p></p>
+		<span style="padding-bottom:5px;">만약 커밋된 로컬 Git 저장소가 있다면, 다음과 같이 저장소에 리모트를 추가하고 푸시(push)할 수 있습니다.</span>
+		<p></p>
+		<pre style="padding: 5px 30px;" wicket:id="remoteSyntax"></pre>
+		<p></p>
+		<h3>Git 배우기</h3>
+		만약 사용법에 자신이 없다면, Git 사용법을 더 잘 이해하기 위해 
+		 <a href="http://book.git-scm.com">Git Community Book</a> 또는 
+		 <a href="http://progit.org/book" target="_blank">Pro Git</a>, 
+		 <a href="http://dogfeet.github.com/articles/2012/progit.html" target="_blank">Pro Git 한글</a> 을 볼 것을 고려해 보세요.
+		<p></p>
+		<h4>오픈소스 Git 클라이언트</h4>
+		<ul>
+			<li><a href="http://git-scm.com">Git</a> - 명령어 기반 공식 Git</li>
+			<li><a href="http://tortoisegit.googlecode.com">TortoiseGit</a> - 윈도의 파일 탐색기에 통합된 UI 클라이언트 (명령어 기반 공식 Git 필요)</li>
+			<li><a href="http://eclipse.org/egit">Eclipse/EGit</a> - 이클립스 IDE 플러그인 (Gitblit 과 같은 JGit 기반)</li>
+			<li><a href="https://code.google.com/p/gitextensions/">Git Extensions</a> - C# frontend for Git that features Windows Explorer and Visual Studio integration</li>
+			<li><a href="http://rowanj.github.io/gitx/">GitX-dev</a> - a Mac OS X Git client</li>			
+		</ul>
+		<p></p>
+		<h4>유료 Git 클라이언트</h4>
+		<ul>
+			<li><a href="http://www.syntevo.com/smartgithg">SmartGit/Hg</a> - 자바 어플리케이션 (명령어 기반 공식 Git 필요)</li>
+			<li><a href="http://www.sourcetreeapp.com/">SourceTree</a> - A free Mac Client for Git, Mercurial, and SVN</li>
+			<li><a href="http://www.git-tower.com/">Tower</a> - a Mac OS X Git client</li>
+		</ul>
+</div>
+</wicket:extend>	
+</body>
+</html>
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/pages/EmptyRepositoryPage_nl.html b/src/main/java/com/gitblit/wicket/pages/EmptyRepositoryPage_nl.html
new file mode 100644
index 0000000..6a491ee
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/EmptyRepositoryPage_nl.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"  
+      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"  
+      xml:lang="nl"  
+      lang="nl"> 
+
+<body>
+<wicket:extend>
+<div class="container">
+	<h2>Empty Repository</h2>
+	<p></p>
+		<div class="row">
+			<div class="span10">
+				<div class="alert alert-success">
+					<span wicket:id="repository" style="font-weight: bold;">[repository]</span> is een lege repositorie en kan niet bekeken worden door Gitblit.
+					<p></p>		
+					Push aub een paar commitsome commits naar <span wicket:id="pushurl"></span>
+					<p></p>
+					<hr/>
+					Nadat u een paar commits gepushed hebt kunt u deze pagina <b>verversen</b> om de repository te bekijken.
+				</div>
+			</div>
+		</div>
+		
+		<h3>Git Command-Line Syntax</h3>
+		<span style="padding-bottom:5px;">Als u geen lokale Git repositorie heeft, kunt u deze repository clonen, er een paar bestanden naar committen en deze commits teug pushen naar Gitblit.</span>
+		<p></p>
+		<pre style="padding: 5px 30px;" wicket:id="cloneSyntax"></pre>
+		<p></p>
+		<span style="padding-bottom:5px;">Als u al een lokale Git repositorie heeft met commits kunt u deze repository als een remote toevoegen en er naar toe pushen.</span>
+		<p></p>
+		<pre wicket:id="remoteSyntax" style="padding: 5px 30px;"></pre>
+		<p></p>
+		<h3>Learn Git</h3>
+		Als u niet goed weet wat u met deze informatie aan moet raden we aan om het <a href="http://book.git-scm.com">Git Community Book</a> of <a href="http://progit.org/book" target="_blank">Pro Git</a> te bestuderen voor een betere begrip van hoe u Git kunt gebruiken.
+		<p></p>
+		<h4>Open Source Git Clients</h4>
+		<ul>
+			<li><a href="http://git-scm.com">Git</a> - de officiele, command-line Git</li>
+			<li><a href="http://tortoisegit.googlecode.com">TortoiseGit</a> - Windows bestandsverkenner ingetratie (officiele command-line Git is wel nodig)</li>
+			<li><a href="http://eclipse.org/egit">Eclipse/EGit</a> - Git voor de Eclipse IDE (gebaseerd op JGit, zoals Gitblit)</li>
+			<li><a href="https://code.google.com/p/gitextensions/">Git Extensions</a> - C# frontend voor Git met Windows Explorer en Visual Studio integratie</li>
+			<li><a href="http://rowanj.github.io/gitx/">GitX-dev</a> - een Mac OS X Git client</li>			
+		</ul>
+		<p></p>
+		<h4>Commercial/Closed-Source Git Clients</h4>
+		<ul>
+			<li><a href="http://www.syntevo.com/smartgithg">SmartGit/Hg</a> - Een Java Git, Mercurial, en SVN client applicatie (officiele command-line Git is wel nodig)</li>
+			<li><a href="http://www.sourcetreeapp.com/">SourceTree</a> - Een gratis Mac Client voor Git, Mercurial, en SVN</li>
+			<li><a href="http://www.git-tower.com/">Tower</a> - een Mac OS X Git client</li>
+		</ul>
+</div>
+</wicket:extend>	
+</body>
+</html>
diff --git a/src/main/java/com/gitblit/wicket/pages/EmptyRepositoryPage_pl.html b/src/main/java/com/gitblit/wicket/pages/EmptyRepositoryPage_pl.html
new file mode 100644
index 0000000..8db2f74
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/EmptyRepositoryPage_pl.html
@@ -0,0 +1,58 @@
+
+
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"  
+      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"  
+      xml:lang="en"  
+      lang="es"> 
+
+<body>
+<wicket:extend>
+<div class="container">
+		<h2>Puste repozytorium</h2>
+		<p></p>
+		<div class="row">
+			<div class="span10">
+				<div class="alert alert-success">
+					<span wicket:id="repository" style="font-weight: bold;">[repository]</span> jest pustym repozytorium i nie mo&#380;e by&#263; zaprezentowane przez Gitblit.
+					<p></p>
+					Wgraj, poprzez push, dowolne zmiany do lokalizacji <span wicket:id="pushurl"></span>
+					<p></p>
+					<hr/>
+					Po wgraniu zmian <b>od&#347;wie&#380;</b> stron&#281;, aby podejrze&#263; repozytorium.
+				</div>
+			</div>
+		</div>
+		
+		<h3>Sk&#322;adnia linii polece&#324; GITa</h3>
+		<span style="padding-bottom:5px;">Je&#347;li nie posiadasz lokalnego repozytorium GITa to sklonuj to repozytorium, wgraj dowolne pliki, a nast&#281;pnie wy&#347;lij poprzez push zmiany na Gitblit.</span>
+		<p></p>
+		<pre style="padding: 5px 30px;" wicket:id="cloneSyntax"></pre>
+		<p></p>
+		<span style="padding-bottom:5px;">Gdy posiadasz lokalne repozytorium GITa z dowolnymi zmianami, to mo&#380;esz doda&#263; to repozytorium jako remote i wys&#322;a&#263; do niego zmiany poprzez push.</span>
+		<p></p>
+		<pre style="padding: 5px 30px;" wicket:id="remoteSyntax"></pre>
+		<p></p>
+		<h3>Nauka GITa</h3>
+		Je&#380;eli powy&#380;sze informacje s&#261; dla Ciebie niezrozumia&#322;e, zapoznaj si&#281; z ksi&#261;&#380;k&#261; <a href="http://git-scm.com/book/pl" target="_blank">Pro Git - Wersja PL</a> dla lepszego zrozumienia, jak poprawnie u&#380;ywa&#263; GITa.
+		<p></p>
+		<h4>Darmowi klienci GITa</h4>
+		<ul>
+			<li><a href="http://git-scm.com">Git</a> - Oficjalny klient, dost&#281;pny przez lini&#281; polece&#324;</li>
+			<li><a href="http://tortoisegit.googlecode.com">TortoiseGit</a> - Rozszerzenie eksploratora Windows (wymaga oficjalnego, dost&#281;pnego przez lini&#281; polece&#324; klienta)</li>
+			<li><a href="http://eclipse.org/egit">Eclipse/EGit</a> - GIT dla edytora Eclipse (oparty o JGit, podobnie jak Gitblit)</li>
+			<li><a href="https://code.google.com/p/gitextensions/">Git Extensions</a> - napisana w C# fasada na GIT, udost&#281;pniaj&#261;ca integracj&#281; dla Windows Explorer oraz Visual Studio</li>
+			<li><a href="http://rowanj.github.io/gitx/">GitX-dev</a> - klient GIT na Mac OS X</li>			
+		</ul>
+		<p></p>
+		<h4>Komercyjni klienci GITa</h4>
+		<ul>
+			<li><a href="http://www.syntevo.com/smartgithg">SmartGit/Hg</a> - aplikacja napisana w Javie (wymaga oficjalnego, dost&#281;pnego przez lini&#281; polece&#324; klienta)</li>
+			<li><a href="http://www.sourcetreeapp.com/">SourceTree</a> - darmowy klient GIT, Mercurial i SVN na Mac OS X</li>
+			<li><a href="http://www.git-tower.com/">Tower</a> - klient GIT na Mac OS X</li>
+		</ul>
+</div>
+</wicket:extend>	
+</body>
+</html>
+
diff --git a/src/main/java/com/gitblit/wicket/pages/EmptyRepositoryPage_pt_BR.html b/src/main/java/com/gitblit/wicket/pages/EmptyRepositoryPage_pt_BR.html
new file mode 100644
index 0000000..cdf1d68
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/EmptyRepositoryPage_pt_BR.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"  
+      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"  
+      xml:lang="pt-br"  
+      lang="pt-br"> 
+
+<body>
+<wicket:extend>
+<div class="container">
+	<h2>Repositório Vazio</h2>
+	<p></p>
+		<div class="row">
+			<div class="span10">
+				<div class="alert alert-success">
+					<span wicket:id="repository" style="font-weight: bold;">[repository]</span> é um repositório vazio e não pode ser visualizado pelo Gitblit.
+					<p></p>		
+					Por favor faça o push de alguns commits para <span wicket:id="pushurl"></span>
+					<p></p>
+					<hr/>
+					Depois de ter feito push você poderá <b>atualizar</b> esta página para visualizar seu repositório.
+				</div>
+			</div>
+		</div>
+		
+		<h3>Sintaxe dos comandos do Git</h3>
+		<span style="padding-bottom:5px;">Se você ainda não tem um repositório local do Git, então você deve primeiro clonar este repositório, fazer commit de alguns arquivos e então fazer push desses commits para o Gitblit.</span>
+		<p></p>
+		<pre style="padding: 5px 30px;" wicket:id="cloneSyntax"></pre>
+		<p></p>
+		<span style="padding-bottom:5px;">Se você já tem um repositório Git local com alguns commits, então você deve adicionar este repositório como uma referência remota e então fazer push.</span>
+		<p></p>
+		<pre wicket:id="remoteSyntax" style="padding: 5px 30px;"></pre>
+		<p></p>
+		<h3>Aprenda Git</h3>
+		Se você estiver com dúvidas sobre como ultilizar essas informações, uma sugestão seria dar uma olhada no livro <a href="http://book.git-scm.com">Git Community Book</a> ou <a href="http://progit.org/book" target="_blank">Pro Git</a> para entender melhor como usar o Git.
+		<p></p>
+		<h4>Alguns clients do Git que são Open Source</h4>
+		<ul>
+			<li><a href="http://git-scm.com">Git</a> - o Git oficial através de linhas de comando</li>
+			<li><a href="http://tortoisegit.googlecode.com">TortoiseGit</a> - Faz integração do Explorer do Windows com o Git (por isso requer o Git Oficial)</li>
+			<li><a href="http://eclipse.org/egit">Eclipse/EGit</a> - Git para a IDE Eclipse (baseada no JGit, como o Gitblit)</li>
+			<li><a href="https://code.google.com/p/gitextensions/">Git Extensions</a> - Interface (em C#) para o Git cuja a característica é a integração com o Windows Explorer e o Visual Studio</li>
+			<li><a href="http://rowanj.github.io/gitx/">GitX-dev</a> - um Cliente do Git para Mac OS X</li>			
+		</ul>
+		<p></p>
+		<h4>Clients do Git proprietários ou com Código Fechado</h4>
+		<ul>
+			<li><a href="http://www.syntevo.com/smartgithg">SmartGit/Hg</a> - Aplicação Client (em Java) para Git, Mercurial, e SVN (por isso requer o Git Oficial)</li>
+			<li><a href="http://www.sourcetreeapp.com/">SourceTree</a> - Client gratuito para o Mac que suporta Git, Mercurial e SVN</li>
+			<li><a href="http://www.git-tower.com/">Tower</a> - um Cliente do Git para Mac OS X</li>
+		</ul>
+</div>
+</wicket:extend>	
+</body>
+</html>
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/pages/EmptyRepositoryPage_zh_CN.html b/src/main/java/com/gitblit/wicket/pages/EmptyRepositoryPage_zh_CN.html
new file mode 100644
index 0000000..6c1ba70
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/EmptyRepositoryPage_zh_CN.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"  
+      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"  
+      xml:lang="zh-CN"  
+      lang="zh-CN"> 
+
+<body>
+<wicket:extend>
+<div class="container">
+	<h2>空版本库</h2>
+	<p></p>
+		<div class="row">
+			<div class="span10">
+				<div class="alert alert-success">
+					<span wicket:id="repository" style="font-weight: bold;">[repository]</span> 版本库目前为空。
+                    Gitblit 无法查看。
+					<p></p>		
+					请往此网址进行推送 <span wicket:id="pushurl"></span>
+					<p></p>
+					<hr/>
+					当你推送完毕后你可以 <b>刷新</b> 此页面重新查看您的版本库。
+				</div>
+			</div>
+		</div>
+		
+		<h3>Git 命令行格式</h3>
+		<span style="padding-bottom:5px;">如果您没有本地 Git 版本库, 您可以克隆此版本库, 提交一些文件, 然后将您的提交推送回Gitblit。</span>
+		<p></p>
+		<pre style="padding: 5px 30px;" wicket:id="cloneSyntax"></pre>
+		<p></p>
+		<span style="padding-bottom:5px;">如果您已经有一个本地的提交过的版本库, 那么您可以将此版本库加为远程
+        版本库,并进行推送。</span>
+		<p></p>
+		<pre wicket:id="remoteSyntax" style="padding: 5px 30px;"></pre>
+		<p></p>
+		<h3>学习 Git</h3>
+		如果您不明白这些信息什么意思, 您可以参考 <a href="http://book.git-scm.com">Git Community Book</a> 或者 <a href="http://progit.org/book" target="_blank">Pro Git</a> 去更加深入的学习 Git 的用法。
+		<p></p>
+		<h4>开源 Git 客户端</h4>
+		<ul>
+			<li><a href="http://git-scm.com">Git</a> - 官方, 命令行版本 Git</li>
+			<li><a href="http://tortoisegit.googlecode.com">TortoiseGit</a> - 与 Windows 资源管理器集成 (需要官方, 命令行 Git 的支持)</li>
+			<li><a href="http://eclipse.org/egit">Eclipse/EGit</a> - Git for the Eclipse IDE (基于 JGit, 类似 Gitblit)</li>
+			<li><a href="https://code.google.com/p/gitextensions/">Git Extensions</a> - C# 版本的 Git 前端,与 Windows 资源管理器和 Visual Studio 集成</li>
+			<li><a href="http://rowanj.github.io/gitx/">GitX-dev</a> - Mac OS X Git 客户端</li>			
+		</ul>
+		<p></p>
+		<h4>商业/闭源 Git 客户端</h4>
+		<ul>
+			<li><a href="http://www.syntevo.com/smartgithg">SmartGit/Hg</a> - Java 版本的支持 Git, Mercurial 和 SVN 客户端应用 (需要官方, 命令行 Git 的支持)</li>
+			<li><a href="http://www.sourcetreeapp.com/">SourceTree</a> - 免费的 Mac Git Mercurial 以及 SVN 客户端, Mercurial, and SVN</li>
+			<li><a href="http://www.git-tower.com/">Tower</a> - Mac OS X Git 客户端</li>
+		</ul>
+</div>
+</wicket:extend>	
+</body>
+</html>
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/pages/FederationPage.html b/src/main/java/com/gitblit/wicket/pages/FederationPage.html
new file mode 100644
index 0000000..ea42947
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/FederationPage.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"  
+      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"  
+      xml:lang="en"  
+      lang="en"> 
+<body>
+<wicket:extend>
+<div class="container">
+	<div wicket:id="federationTokensPanel">[federation tokens panel]</div>
+		
+	<div style="padding-top: 10px;" wicket:id="federationProposalsPanel">[federation proposals panel]</div>
+
+	<div style="padding-top: 10px;" wicket:id="federationRegistrationsPanel">[federation registrations panel]</div>
+</div>
+</wicket:extend>
+</body>
+</html>
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/pages/FederationPage.java b/src/main/java/com/gitblit/wicket/pages/FederationPage.java
similarity index 100%
rename from src/com/gitblit/wicket/pages/FederationPage.java
rename to src/main/java/com/gitblit/wicket/pages/FederationPage.java
diff --git a/src/main/java/com/gitblit/wicket/pages/FederationRegistrationPage.html b/src/main/java/com/gitblit/wicket/pages/FederationRegistrationPage.html
new file mode 100644
index 0000000..2f23668
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/FederationRegistrationPage.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"  
+      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"  
+      xml:lang="en"  
+      lang="en"> 
+
+<body>
+<wicket:extend>
+	<!-- registration info -->
+	<table class="summary">
+		<tr><th><wicket:message key="gb.url">url</wicket:message></th><td><img style="border:0px;vertical-align:middle;" wicket:id="typeIcon" /> <span wicket:id="url">[url]</span></td></tr>
+		<tr><th><wicket:message key="gb.token">token</wicket:message></th><td><span class="sha1" wicket:id="token">[token]</span></td></tr>
+		<tr><th><wicket:message key="gb.folder">folder</wicket:message></th><td><span wicket:id="folder">[folder]</span></td></tr>
+		<tr><th><wicket:message key="gb.frequency">frequency</wicket:message></th><td><span wicket:id="frequency">[frequency]</span></td></tr>
+		<tr><th><wicket:message key="gb.lastPull">lastPull</wicket:message></th><td><span wicket:id="lastPull">[lastPull]</span></td></tr>
+		<tr><th><wicket:message key="gb.nextPull">nextPull</wicket:message></th><td><span wicket:id="nextPull">[nextPull]</span></td></tr>
+		<tr><th valign="top"><wicket:message key="gb.exclusions">exclusions</wicket:message></th><td><span class="sha1" wicket:id="exclusions">[exclusions]</span></td></tr>
+		<tr><th valign="top"><wicket:message key="gb.inclusions">inclusions</wicket:message></th><td><span class="sha1" wicket:id="inclusions">[inclusions]</span></td></tr>
+	</table>
+	
+	<table class="repositories">
+		<tr>
+			<th class="left">
+				<img style="vertical-align: top; border: 1px solid #888; background-color: white;" src="git-black-16x16.png"/>
+				<wicket:message key="gb.repositories">[repositories]</wicket:message>
+			</th>
+			<th class="right"><wicket:message key="gb.status">[status]</wicket:message></th>
+		</tr>
+		<tbody>		
+       		<tr wicket:id="row">
+       			<td class="left"><img style="border:0px;vertical-align:middle;" wicket:id="statusIcon" /><span wicket:id="name">[name]</span></td>
+       			<td class="right"><span wicket:id="status">[status]</span></td>
+       		</tr>
+    	</tbody>
+	</table>
+		
+</wicket:extend>    
+</body>
+</html>
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/pages/FederationRegistrationPage.java b/src/main/java/com/gitblit/wicket/pages/FederationRegistrationPage.java
new file mode 100644
index 0000000..092f8e2
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/FederationRegistrationPage.java
@@ -0,0 +1,100 @@
+/*
+ * 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.wicket.pages;
+
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.wicket.PageParameters;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.repeater.Item;
+import org.apache.wicket.markup.repeater.data.DataView;
+import org.apache.wicket.markup.repeater.data.ListDataProvider;
+
+import com.gitblit.GitBlit;
+import com.gitblit.models.FederationModel;
+import com.gitblit.models.FederationModel.RepositoryStatus;
+import com.gitblit.wicket.WicketUtils;
+
+public class FederationRegistrationPage extends RootSubPage {
+
+	public FederationRegistrationPage(PageParameters params) {
+		super(params);
+		
+		setStatelessHint(true);
+
+		String url = WicketUtils.getUrlParameter(params);
+		String name = WicketUtils.getNameParameter(params);
+
+		FederationModel registration = GitBlit.self().getFederationRegistration(url, name);
+		if (registration == null) {
+			error(getString("gb.couldNotFindFederationRegistration"), true);
+		}
+
+		setupPage(registration.isResultData() ? getString("gb.federationResults")
+				: getString("gb.federationRegistration"), registration.url);
+
+		add(new Label("url", registration.url));
+		add(WicketUtils.getRegistrationImage("typeIcon", registration, this));
+		add(new Label("frequency", registration.frequency));
+		add(new Label("folder", registration.folder));
+		add(new Label("token", showAdmin ? registration.token : "--"));
+		add(WicketUtils.createTimestampLabel("lastPull", registration.lastPull, getTimeZone(), getTimeUtils()));
+		add(WicketUtils.createTimestampLabel("nextPull", registration.nextPull, getTimeZone(), getTimeUtils()));
+
+		StringBuilder inclusions = new StringBuilder();
+		for (String inc : registration.inclusions) {
+			inclusions.append(inc).append("<br/>");
+		}
+		StringBuilder exclusions = new StringBuilder();
+		for (String ex : registration.exclusions) {
+			exclusions.append(ex).append("<br/>");
+		}
+
+		add(new Label("inclusions", inclusions.toString()).setEscapeModelStrings(false));
+
+		add(new Label("exclusions", exclusions.toString()).setEscapeModelStrings(false));
+
+		List<RepositoryStatus> list = registration.getStatusList();
+		Collections.sort(list);
+		DataView<RepositoryStatus> dataView = new DataView<RepositoryStatus>("row",
+				new ListDataProvider<RepositoryStatus>(list)) {
+			private static final long serialVersionUID = 1L;
+			private int counter;
+
+			@Override
+			protected void onBeforeRender() {
+				super.onBeforeRender();
+				counter = 0;
+			}
+
+			public void populateItem(final Item<RepositoryStatus> item) {
+				final RepositoryStatus entry = item.getModelObject();
+				item.add(WicketUtils.getPullStatusImage("statusIcon", entry.status));
+				item.add(new Label("name", entry.name));
+				item.add(new Label("status", entry.status.name()));
+				WicketUtils.setAlternatingBackground(item, counter);
+				counter++;
+			}
+		};
+		add(dataView);
+	}
+	
+	@Override
+	protected Class<? extends BasePage> getRootNavPageClass() {
+		return FederationPage.class;
+	}
+}
diff --git a/src/com/gitblit/wicket/pages/ForkPage.html b/src/main/java/com/gitblit/wicket/pages/ForkPage.html
similarity index 100%
rename from src/com/gitblit/wicket/pages/ForkPage.html
rename to src/main/java/com/gitblit/wicket/pages/ForkPage.html
diff --git a/src/com/gitblit/wicket/pages/ForkPage.java b/src/main/java/com/gitblit/wicket/pages/ForkPage.java
similarity index 100%
rename from src/com/gitblit/wicket/pages/ForkPage.java
rename to src/main/java/com/gitblit/wicket/pages/ForkPage.java
diff --git a/src/com/gitblit/wicket/pages/ForksPage.html b/src/main/java/com/gitblit/wicket/pages/ForksPage.html
similarity index 100%
rename from src/com/gitblit/wicket/pages/ForksPage.html
rename to src/main/java/com/gitblit/wicket/pages/ForksPage.html
diff --git a/src/main/java/com/gitblit/wicket/pages/ForksPage.java b/src/main/java/com/gitblit/wicket/pages/ForksPage.java
new file mode 100644
index 0000000..f59955e
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/ForksPage.java
@@ -0,0 +1,160 @@
+/*
+ * 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.wicket.pages;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.wicket.Component;
+import org.apache.wicket.PageParameters;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.repeater.Item;
+import org.apache.wicket.markup.repeater.data.DataView;
+import org.apache.wicket.markup.repeater.data.ListDataProvider;
+import org.eclipse.jgit.lib.PersonIdent;
+
+import com.gitblit.GitBlit;
+import com.gitblit.Keys;
+import com.gitblit.models.ForkModel;
+import com.gitblit.models.RepositoryModel;
+import com.gitblit.models.UserModel;
+import com.gitblit.utils.StringUtils;
+import com.gitblit.wicket.GitBlitWebSession;
+import com.gitblit.wicket.WicketUtils;
+import com.gitblit.wicket.panels.GravatarImage;
+import com.gitblit.wicket.panels.LinkPanel;
+
+public class ForksPage extends RepositoryPage {
+
+	public ForksPage(PageParameters params) {
+		super(params);
+		
+		final RepositoryModel pageRepository = getRepositoryModel();
+		
+		ForkModel root = GitBlit.self().getForkNetwork(pageRepository.name);
+		List<FlatFork> network = flatten(root);
+		
+		ListDataProvider<FlatFork> forksDp = new ListDataProvider<FlatFork>(network);
+		DataView<FlatFork> forksList = new DataView<FlatFork>("fork", forksDp) {
+			private static final long serialVersionUID = 1L;
+
+			public void populateItem(final Item<FlatFork> item) {
+				FlatFork fork = item.getModelObject();
+				RepositoryModel repository = fork.repository;
+				
+				if (repository.isPersonalRepository()) {
+					UserModel user = GitBlit.self().getUserModel(repository.projectPath.substring(1));
+					if (user == null) {
+						// user account no longer exists
+						user = new UserModel(repository.projectPath.substring(1));
+					}
+					PersonIdent ident = new PersonIdent(user.getDisplayName(), user.emailAddress == null ? user.getDisplayName() : user.emailAddress);
+					item.add(new GravatarImage("anAvatar", ident, 20));
+					if (pageRepository.equals(repository)) {
+						// do not link to self
+						item.add(new Label("aProject", user.getDisplayName()));
+					} else {
+						item.add(new LinkPanel("aProject", null, user.getDisplayName(), UserPage.class, WicketUtils.newUsernameParameter(user.username)));
+					}
+				} else {
+					Component swatch;
+					if (repository.isBare){
+						swatch = new Label("anAvatar", "&nbsp;").setEscapeModelStrings(false);
+					} else {
+						swatch = new Label("anAvatar", "!");
+						WicketUtils.setHtmlTooltip(swatch, getString("gb.workingCopyWarning"));
+					}
+					WicketUtils.setCssClass(swatch,  "repositorySwatch");
+					WicketUtils.setCssBackground(swatch, repository.toString());
+					item.add(swatch);
+					String projectName = repository.projectPath;
+					if (StringUtils.isEmpty(projectName)) {
+						projectName = GitBlit.getString(Keys.web.repositoryRootGroupName, "main");
+					}
+					if (pageRepository.equals(repository)) {
+						// do not link to self
+						item.add(new Label("aProject", projectName));
+					} else {
+						item.add(new LinkPanel("aProject", null, projectName, ProjectPage.class, WicketUtils.newProjectParameter(projectName)));
+					}
+				}
+				
+				String repo = StringUtils.getLastPathElement(repository.name);
+				UserModel user = GitBlitWebSession.get().getUser();
+				if (user == null) {
+					user = UserModel.ANONYMOUS;
+				}
+				if (user.canView(repository)) {
+					if (pageRepository.equals(repository)) {
+						// do not link to self
+						item.add(new Label("aFork", StringUtils.stripDotGit(repo)));
+					} else {
+						item.add(new LinkPanel("aFork", null, StringUtils.stripDotGit(repo), SummaryPage.class, WicketUtils.newRepositoryParameter(repository.name)));
+					}
+					item.add(WicketUtils.createDateLabel("lastChange", repository.lastChange, getTimeZone(), getTimeUtils()));
+				} else {
+					item.add(new Label("aFork", repo));
+					item.add(new Label("lastChange").setVisible(false));
+				}
+				
+				WicketUtils.setCssStyle(item, "margin-left:" + (32*fork.level) + "px;");
+				if (fork.level == 0) {
+					WicketUtils.setCssClass(item, "forkSource");
+				} else {
+					WicketUtils.setCssClass(item,  "forkEntry");
+				}
+			}
+		};
+		
+		add(forksList);
+	}
+
+	@Override
+	protected String getPageName() {
+		return getString("gb.forks");
+	}
+	
+	protected List<FlatFork> flatten(ForkModel root) {
+		List<FlatFork> list = new ArrayList<FlatFork>();
+		list.addAll(flatten(root, 0));
+		return list;
+	}
+	
+	protected List<FlatFork> flatten(ForkModel node, int level) {
+		List<FlatFork> list = new ArrayList<FlatFork>();
+		list.add(new FlatFork(node.repository, level));
+		if (!node.isLeaf()) {
+			for (ForkModel fork : node.forks) {
+				list.addAll(flatten(fork, level + 1));
+			}
+		}
+		return list;
+	}
+	
+	private class FlatFork implements Serializable {
+		
+		private static final long serialVersionUID = 1L;
+
+		public final RepositoryModel repository;
+		public final int level;
+		
+		public FlatFork(RepositoryModel repository, int level) {
+			this.repository = repository;
+			this.level = level;
+		}
+	}
+}
diff --git a/src/main/java/com/gitblit/wicket/pages/GitSearchPage.html b/src/main/java/com/gitblit/wicket/pages/GitSearchPage.html
new file mode 100644
index 0000000..6e1d196
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/GitSearchPage.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"  
+      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"  
+      xml:lang="en"  
+      lang="en"> 
+
+<body>
+<wicket:extend>
+
+	<!-- pager links -->
+	<div class="page_nav2">
+		<a wicket:id="firstPageTop"><wicket:message key="gb.pageFirst"></wicket:message></a> | <a wicket:id="prevPageTop">&laquo; <wicket:message key="gb.pagePrevious"></wicket:message></a> | <a wicket:id="nextPageTop"><wicket:message key="gb.pageNext"></wicket:message> &raquo;</a> 
+	</div>
+	
+	<!-- history -->
+	<div style="margin-top:5px;" wicket:id="searchPanel">[search panel]</div>
+
+	<!-- pager links -->
+	<div style="padding-bottom:5px;">
+		<a wicket:id="firstPageBottom"><wicket:message key="gb.pageFirst"></wicket:message></a> | <a wicket:id="prevPageBottom">&laquo; <wicket:message key="gb.pagePrevious"></wicket:message></a> | <a wicket:id="nextPageBottom"><wicket:message key="gb.pageNext"></wicket:message> &raquo;</a> 
+	</div>
+
+</wicket:extend>
+</body>
+</html>
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/pages/GitSearchPage.java b/src/main/java/com/gitblit/wicket/pages/GitSearchPage.java
new file mode 100644
index 0000000..446531a
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/GitSearchPage.java
@@ -0,0 +1,77 @@
+/*
+ * 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.wicket.pages;
+
+import org.apache.wicket.PageParameters;
+import org.apache.wicket.markup.html.link.BookmarkablePageLink;
+
+import com.gitblit.Constants;
+import com.gitblit.wicket.CacheControl;
+import com.gitblit.wicket.WicketUtils;
+import com.gitblit.wicket.CacheControl.LastModified;
+import com.gitblit.wicket.panels.SearchPanel;
+
+@CacheControl(LastModified.REPOSITORY)
+public class GitSearchPage extends RepositoryPage {
+
+	public GitSearchPage(PageParameters params) {
+		super(params);
+
+		String value = WicketUtils.getSearchString(params);
+		String type = WicketUtils.getSearchType(params);
+		Constants.SearchType searchType = Constants.SearchType.forName(type);
+
+		int pageNumber = WicketUtils.getPage(params);
+		int prevPage = Math.max(0, pageNumber - 1);
+		int nextPage = pageNumber + 1;
+
+		SearchPanel search = new SearchPanel("searchPanel", repositoryName, objectId, value,
+				searchType, getRepository(), -1, pageNumber - 1, getRepositoryModel().showRemoteBranches);
+		boolean hasMore = search.hasMore();
+		add(search);
+
+		add(new BookmarkablePageLink<Void>("firstPageTop", GitSearchPage.class,
+				WicketUtils.newSearchParameter(repositoryName, objectId, value, searchType))
+				.setEnabled(pageNumber > 1));
+		add(new BookmarkablePageLink<Void>("prevPageTop", GitSearchPage.class,
+				WicketUtils.newSearchParameter(repositoryName, objectId, value, searchType,
+						prevPage)).setEnabled(pageNumber > 1));
+		add(new BookmarkablePageLink<Void>("nextPageTop", GitSearchPage.class,
+				WicketUtils.newSearchParameter(repositoryName, objectId, value, searchType,
+						nextPage)).setEnabled(hasMore));
+
+		add(new BookmarkablePageLink<Void>("firstPageBottom", GitSearchPage.class,
+				WicketUtils.newSearchParameter(repositoryName, objectId, value, searchType))
+				.setEnabled(pageNumber > 1));
+		add(new BookmarkablePageLink<Void>("prevPageBottom", GitSearchPage.class,
+				WicketUtils.newSearchParameter(repositoryName, objectId, value, searchType,
+						prevPage)).setEnabled(pageNumber > 1));
+		add(new BookmarkablePageLink<Void>("nextPageBottom", GitSearchPage.class,
+				WicketUtils.newSearchParameter(repositoryName, objectId, value, searchType,
+						nextPage)).setEnabled(hasMore));
+
+	}
+
+	@Override
+	protected String getPageName() {
+		return getString("gb.search");
+	}
+	
+	@Override
+	protected Class<? extends BasePage> getRepoNavPageClass() {
+		return LogPage.class;
+	}
+}
diff --git a/src/main/java/com/gitblit/wicket/pages/GravatarProfilePage.html b/src/main/java/com/gitblit/wicket/pages/GravatarProfilePage.html
new file mode 100644
index 0000000..b4531a1
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/GravatarProfilePage.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"  
+      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"  
+      xml:lang="en"  
+      lang="en"> 
+<body>
+<wicket:extend>
+<div class="container">
+	<div class="pageTitle">
+		<h2>Gravatar<small> / <span wicket:id="username">[username]</span></small></h2>
+	</div>
+	<img class="gravatar" wicket:id="profileImage"></img>
+	<h2 wicket:id="displayName"></h2>
+	<div style="color:#888;"wicket:id="location"></div>
+	<div style="padding-top:5px;" wicket:id="aboutMe"></div>
+	<p></p>
+	<a wicket:id="profileLink"><wicket:message key="gb.completeGravatarProfile">[Complete profile on Gravatar.com]</wicket:message></a>
+	<p></p>
+</div>
+</wicket:extend>
+</body>
+</html>
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/pages/GravatarProfilePage.java b/src/main/java/com/gitblit/wicket/pages/GravatarProfilePage.java
similarity index 100%
rename from src/com/gitblit/wicket/pages/GravatarProfilePage.java
rename to src/main/java/com/gitblit/wicket/pages/GravatarProfilePage.java
diff --git a/src/main/java/com/gitblit/wicket/pages/HistoryPage.html b/src/main/java/com/gitblit/wicket/pages/HistoryPage.html
new file mode 100644
index 0000000..7d55e3d
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/HistoryPage.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"  
+      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"  
+      xml:lang="en"  
+      lang="en"> 
+
+<body>
+<wicket:extend>
+
+	<!-- pager links -->
+	<div class="page_nav2">
+		<a wicket:id="firstPageTop"><wicket:message key="gb.pageFirst"></wicket:message></a> | <a wicket:id="prevPageTop">&laquo; <wicket:message key="gb.pagePrevious"></wicket:message></a> | <a wicket:id="nextPageTop"><wicket:message key="gb.pageNext"></wicket:message> &raquo;</a> 
+	</div>
+	
+	<!-- history -->
+	<div style="margin-top:5px;" wicket:id="historyPanel">[history panel]</div>
+
+	<!-- pager links -->
+	<div style="padding-bottom:5px;">
+		<a wicket:id="firstPageBottom"><wicket:message key="gb.pageFirst"></wicket:message></a> | <a wicket:id="prevPageBottom">&laquo; <wicket:message key="gb.pagePrevious"></wicket:message></a> | <a wicket:id="nextPageBottom"><wicket:message key="gb.pageNext"></wicket:message> &raquo;</a> 
+	</div>
+
+</wicket:extend>
+</body>
+</html>
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/pages/HistoryPage.java b/src/main/java/com/gitblit/wicket/pages/HistoryPage.java
new file mode 100644
index 0000000..33bc54c
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/HistoryPage.java
@@ -0,0 +1,73 @@
+/*
+ * 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.wicket.pages;
+
+import org.apache.wicket.PageParameters;
+import org.apache.wicket.markup.html.link.BookmarkablePageLink;
+
+import com.gitblit.wicket.CacheControl;
+import com.gitblit.wicket.WicketUtils;
+import com.gitblit.wicket.CacheControl.LastModified;
+import com.gitblit.wicket.panels.HistoryPanel;
+
+@CacheControl(LastModified.REPOSITORY)
+public class HistoryPage extends RepositoryPage {
+
+	public HistoryPage(PageParameters params) {
+		super(params);
+
+		String path = WicketUtils.getPath(params);
+		int pageNumber = WicketUtils.getPage(params);
+		int prevPage = Math.max(0, pageNumber - 1);
+		int nextPage = pageNumber + 1;
+
+		HistoryPanel history = new HistoryPanel("historyPanel", repositoryName, objectId, path,
+				getRepository(), -1, pageNumber - 1, getRepositoryModel().showRemoteBranches);
+		boolean hasMore = history.hasMore();
+		add(history);
+
+		add(new BookmarkablePageLink<Void>("firstPageTop", HistoryPage.class,
+				WicketUtils.newPathParameter(repositoryName, objectId, path))
+				.setEnabled(pageNumber > 1));
+		add(new BookmarkablePageLink<Void>("prevPageTop", HistoryPage.class,
+				WicketUtils.newHistoryPageParameter(repositoryName, objectId, path, prevPage))
+				.setEnabled(pageNumber > 1));
+		add(new BookmarkablePageLink<Void>("nextPageTop", HistoryPage.class,
+				WicketUtils.newHistoryPageParameter(repositoryName, objectId, path, nextPage))
+				.setEnabled(hasMore));
+
+		add(new BookmarkablePageLink<Void>("firstPageBottom", HistoryPage.class,
+				WicketUtils.newPathParameter(repositoryName, objectId, path))
+				.setEnabled(pageNumber > 1));
+		add(new BookmarkablePageLink<Void>("prevPageBottom", HistoryPage.class,
+				WicketUtils.newHistoryPageParameter(repositoryName, objectId, path, prevPage))
+				.setEnabled(pageNumber > 1));
+		add(new BookmarkablePageLink<Void>("nextPageBottom", HistoryPage.class,
+				WicketUtils.newHistoryPageParameter(repositoryName, objectId, path, nextPage))
+				.setEnabled(hasMore));
+
+	}
+
+	@Override
+	protected String getPageName() {
+		return getString("gb.history");
+	}
+	
+	@Override
+	protected Class<? extends BasePage> getRepoNavPageClass() {
+		return TreePage.class;
+	}
+}
diff --git a/src/main/java/com/gitblit/wicket/pages/LogPage.html b/src/main/java/com/gitblit/wicket/pages/LogPage.html
new file mode 100644
index 0000000..cb3bb89
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/LogPage.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"  
+      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"  
+      xml:lang="en"  
+      lang="en"> 
+
+<body>
+<wicket:extend>
+
+	<!-- pager links -->
+	<div class="page_nav2">
+		<a wicket:id="firstPageTop"><wicket:message key="gb.pageFirst"></wicket:message></a> | <a wicket:id="prevPageTop">&laquo; <wicket:message key="gb.pagePrevious"></wicket:message></a> | <a wicket:id="nextPageTop"><wicket:message key="gb.pageNext"></wicket:message> &raquo;</a> 
+	</div>
+	
+	<!-- log -->
+	<div style="margin-top:5px;" wicket:id="logPanel">[log panel]</div>
+
+	<!-- pager links -->
+	<div style="padding-bottom:5px;">
+		<a wicket:id="firstPageBottom"><wicket:message key="gb.pageFirst"></wicket:message></a> | <a wicket:id="prevPageBottom">&laquo; <wicket:message key="gb.pagePrevious"></wicket:message></a> | <a wicket:id="nextPageBottom"><wicket:message key="gb.pageNext"></wicket:message> &raquo;</a> 
+	</div>
+	
+</wicket:extend>
+</body>
+</html>
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/pages/LogPage.java b/src/main/java/com/gitblit/wicket/pages/LogPage.java
new file mode 100644
index 0000000..1f4a9bf
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/LogPage.java
@@ -0,0 +1,72 @@
+/*
+ * 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.wicket.pages;
+
+import org.apache.wicket.PageParameters;
+import org.apache.wicket.markup.html.link.BookmarkablePageLink;
+
+import com.gitblit.utils.StringUtils;
+import com.gitblit.wicket.CacheControl;
+import com.gitblit.wicket.WicketUtils;
+import com.gitblit.wicket.CacheControl.LastModified;
+import com.gitblit.wicket.panels.LogPanel;
+
+@CacheControl(LastModified.REPOSITORY)
+public class LogPage extends RepositoryPage {
+
+	public LogPage(PageParameters params) {
+		super(params);
+
+		addSyndicationDiscoveryLink();
+
+		int pageNumber = WicketUtils.getPage(params);
+		int prevPage = Math.max(0, pageNumber - 1);
+		int nextPage = pageNumber + 1;
+		String refid = objectId;
+		if (StringUtils.isEmpty(refid)) {
+			refid = getRepositoryModel().HEAD;
+		}
+		LogPanel logPanel = new LogPanel("logPanel", repositoryName, refid, getRepository(), -1,
+				pageNumber - 1, getRepositoryModel().showRemoteBranches);
+		boolean hasMore = logPanel.hasMore();
+		add(logPanel);
+
+		add(new BookmarkablePageLink<Void>("firstPageTop", LogPage.class,
+				WicketUtils.newObjectParameter(repositoryName, objectId))
+				.setEnabled(pageNumber > 1));
+		add(new BookmarkablePageLink<Void>("prevPageTop", LogPage.class,
+				WicketUtils.newLogPageParameter(repositoryName, objectId, prevPage))
+				.setEnabled(pageNumber > 1));
+		add(new BookmarkablePageLink<Void>("nextPageTop", LogPage.class,
+				WicketUtils.newLogPageParameter(repositoryName, objectId, nextPage))
+				.setEnabled(hasMore));
+
+		add(new BookmarkablePageLink<Void>("firstPageBottom", LogPage.class,
+				WicketUtils.newObjectParameter(repositoryName, objectId))
+				.setEnabled(pageNumber > 1));
+		add(new BookmarkablePageLink<Void>("prevPageBottom", LogPage.class,
+				WicketUtils.newLogPageParameter(repositoryName, objectId, prevPage))
+				.setEnabled(pageNumber > 1));
+		add(new BookmarkablePageLink<Void>("nextPageBottom", LogPage.class,
+				WicketUtils.newLogPageParameter(repositoryName, objectId, nextPage))
+				.setEnabled(hasMore));
+	}
+
+	@Override
+	protected String getPageName() {
+		return getString("gb.log");
+	}
+}
diff --git a/src/main/java/com/gitblit/wicket/pages/LogoutPage.html b/src/main/java/com/gitblit/wicket/pages/LogoutPage.html
new file mode 100644
index 0000000..d407783
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/LogoutPage.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"  
+      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"  
+      xml:lang="en"  
+      lang="en"> 
+<body>
+<wicket:extend>
+	<div class="navbar navbar-fixed-top">
+		<div class="navbar-inner">
+			<div class="container">
+				<a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
+            			<span class="icon-bar"></span>
+            			<span class="icon-bar"></span>
+            			<span class="icon-bar"></span>
+          		</a>
+				<a class="brand" wicket:id="rootLink">
+					<img src="gitblt_25_white.png" width="79" height="25" alt="gitblit" class="logo"/>
+				</a>
+				
+			</div>
+		</div>
+	</div>
+				
+	<!-- subclass content -->
+	<div class="container">
+		<div style="text-align:center" wicket:id="feedback">[Feedback Panel]</div>
+		
+		<h1><wicket:message key="gb.sessionEnded">[Session has ended]</wicket:message></h1>
+		<p><wicket:message key="gb.closeBrowser">[Please close the browser]</wicket:message></p>
+	</div>
+</wicket:extend>
+</body>
+</html>
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/pages/LogoutPage.java b/src/main/java/com/gitblit/wicket/pages/LogoutPage.java
new file mode 100644
index 0000000..1028c5a
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/LogoutPage.java
@@ -0,0 +1,50 @@
+/*
+ * 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.wicket.pages;
+
+import org.apache.wicket.protocol.http.WebRequest;
+import org.apache.wicket.protocol.http.WebResponse;
+
+import com.gitblit.GitBlit;
+import com.gitblit.models.UserModel;
+import com.gitblit.wicket.GitBlitWebSession;
+
+public class LogoutPage extends BasePage {
+
+	public LogoutPage() {
+		super();
+		GitBlitWebSession session = GitBlitWebSession.get();
+		UserModel user = session.getUser();
+		GitBlit.self().setCookie((WebResponse) getResponse(), null);
+		GitBlit.self().logout(user);
+		session.invalidate();		
+		
+		/*
+		 * Now check whether the authentication was realized via the Authorization in the header.
+		 * If so, it is likely to be cached by the browser, and cannot be undone. Effectively, this means
+		 * that you cannot log out...
+		 */
+		if ( ((WebRequest)getRequest()).getHttpServletRequest().getHeader("Authorization") != null ) {
+			// authentication will be done via this route anyway, show a page to close the browser:
+			// this will be done by Wicket.
+			setupPage(null, getString("gb.logout"));
+			
+		} else {
+			setRedirect(true);
+			setResponsePage(getApplication().getHomePage());
+		} // not via WWW-Auth
+	} // LogoutPage
+}
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/pages/LuceneSearchPage.html b/src/main/java/com/gitblit/wicket/pages/LuceneSearchPage.html
new file mode 100644
index 0000000..91a6ef4
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/LuceneSearchPage.html
@@ -0,0 +1,95 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"  
+      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"  
+      xml:lang="en"  
+      lang="en"> 
+
+<!-- contribute google-code-prettify resources to the page header -->
+<wicket:head>
+  <wicket:link>
+   	<link href="prettify/prettify.css" type="text/css" rel="stylesheet" />
+	<script type="text/javascript" src="prettify/prettify.js"></script>
+  </wicket:link>
+</wicket:head>
+      
+<wicket:extend>
+<body onload="document.getElementById('query').focus(); prettyPrint();">
+<div class="container">
+	<!-- page header -->
+	<div class="dashboardTitle">
+		<wicket:message key="gb.search"></wicket:message>				
+	</div>
+	<form class="form-inline" wicket:id="searchForm">
+		<div class="row">
+			<div class="span3">
+				<h3><wicket:message key="gb.repositories"></wicket:message></h3>
+				<select wicket:id="repositories" ></select>
+			</div>
+			<div class="span9" style="margin-left:10px">
+				<div>
+					<h3><wicket:message key="gb.query"></wicket:message></h3>
+					<input class="span8" id="query" type="text" wicket:id="query" placeholder="enter search text"></input>
+					<button class="btn btn-primary" type="submit" value="Search"><wicket:message key="gb.search"></wicket:message></button>
+				</div>
+				<div style="margin-top:10px;">
+					<div style="margin-left:0px;" class="span3">
+						<div class="alert alert">
+							<b>type:</b> commit or blob<br/>
+							<b>commit:</b> commit id<br/>
+							<b>path:</b> "path/to/blob"<br/>
+							<b>branch:</b> "refs/heads/master"<br/>
+							<b>author:</b> or <b>committer:</b>							
+						</div>
+					</div>
+					<div style="margin-left:10px;" class="span4">						
+						<div class="alert alert-info">
+							type:commit AND "bug fix"<br/>
+							type:commit AND author:james*<br/>
+							type:blob AND "int errorCode"<br/>
+							type:blob AND test AND path:*.java<br/>
+							commit:d91e5*
+						</div>
+					</div>
+					<div style="margin-left:10px;" class="span2">
+						<wicket:message key="gb.queryHelp"></wicket:message>
+					</div>
+				</div>
+			</div>
+		</div>
+	</form>
+
+	<div class="row-fluid">	
+	<!-- results header -->
+	<div class="span8">
+		<h3><span wicket:id="resultsHeader"></span> <small><br/><span wicket:id="resultsCount"></span></small></h3>
+	</div>
+	<!-- pager links -->
+	<div class="span4" wicket:id="topPager"></div>
+	</div>
+	
+	<div class="row-fluid">	
+	<!--  search result repeater -->
+	<div class="searchResult" wicket:id="searchResults">
+		<div><i wicket:id="type"></i><span class="summary" wicket:id="summary"></span> <span wicket:id="tags" style="padding-left:10px;"></span></div>
+		<div class="body">
+			<div class="fragment" wicket:id="fragment"></div>
+			<div><span class="author" wicket:id="author"></span> <span class="date" ><wicket:message key="gb.authored"></wicket:message> <span class="date" wicket:id="date"></span></span></div>
+			<span class="repository" wicket:id="repository"></span>:<span class="branch" wicket:id="branch"></span>		
+		</div>
+	</div>
+
+	<!-- pager links -->
+	<div wicket:id="bottomPager"></div>
+
+	</div>
+	</div>
+</body>
+
+	<wicket:fragment wicket:id="tagsPanel">
+		<span wicket:id="tag">
+			<span wicket:id="tagLink">[tag]</span>
+		</span>	
+	</wicket:fragment>
+
+</wicket:extend>
+</html>
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/pages/LuceneSearchPage.java b/src/main/java/com/gitblit/wicket/pages/LuceneSearchPage.java
similarity index 100%
rename from src/com/gitblit/wicket/pages/LuceneSearchPage.java
rename to src/main/java/com/gitblit/wicket/pages/LuceneSearchPage.java
diff --git a/src/com/gitblit/wicket/pages/MarkdownPage.html b/src/main/java/com/gitblit/wicket/pages/MarkdownPage.html
similarity index 100%
rename from src/com/gitblit/wicket/pages/MarkdownPage.html
rename to src/main/java/com/gitblit/wicket/pages/MarkdownPage.html
diff --git a/src/main/java/com/gitblit/wicket/pages/MarkdownPage.java b/src/main/java/com/gitblit/wicket/pages/MarkdownPage.java
new file mode 100644
index 0000000..df078c7
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/MarkdownPage.java
@@ -0,0 +1,81 @@
+/*
+ * 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.wicket.pages;
+
+import java.text.MessageFormat;
+import java.text.ParseException;
+
+import org.apache.wicket.PageParameters;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.link.BookmarkablePageLink;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+
+import com.gitblit.GitBlit;
+import com.gitblit.utils.JGitUtils;
+import com.gitblit.utils.MarkdownUtils;
+import com.gitblit.utils.StringUtils;
+import com.gitblit.wicket.CacheControl;
+import com.gitblit.wicket.WicketUtils;
+import com.gitblit.wicket.CacheControl.LastModified;
+
+@CacheControl(LastModified.BOOT)
+public class MarkdownPage extends RepositoryPage {
+
+	public MarkdownPage(PageParameters params) {
+		super(params);
+
+		final String markdownPath = WicketUtils.getPath(params);
+
+		Repository r = getRepository();
+		RevCommit commit = JGitUtils.getCommit(r, objectId);
+		String [] encodings = GitBlit.getEncodings();
+		
+		// markdown page links
+		add(new BookmarkablePageLink<Void>("blameLink", BlamePage.class,
+				WicketUtils.newPathParameter(repositoryName, objectId, markdownPath)));
+		add(new BookmarkablePageLink<Void>("historyLink", HistoryPage.class,
+				WicketUtils.newPathParameter(repositoryName, objectId, markdownPath)));
+		add(new BookmarkablePageLink<Void>("rawLink", RawPage.class, WicketUtils.newPathParameter(
+				repositoryName, objectId, markdownPath)));
+		add(new BookmarkablePageLink<Void>("headLink", MarkdownPage.class,
+				WicketUtils.newPathParameter(repositoryName, Constants.HEAD, markdownPath)));
+
+		// Read raw markdown content and transform it to html
+		String markdownText = JGitUtils.getStringContent(r, commit.getTree(), markdownPath, encodings);
+		String htmlText;
+		try {
+			htmlText = MarkdownUtils.transformMarkdown(markdownText);
+		} catch (ParseException p) {
+			markdownText = MessageFormat.format("<div class=\"alert alert-error\"><strong>{0}:</strong> {1}</div>{2}", getString("gb.error"), getString("gb.markdownFailure"), markdownText);
+			htmlText = StringUtils.breakLinesForHtml(markdownText);
+		}
+
+		// Add the html to the page
+		add(new Label("markdownText", htmlText).setEscapeModelStrings(false));
+	}
+
+	@Override
+	protected String getPageName() {
+		return getString("gb.markdown");
+	}
+	
+	@Override
+	protected Class<? extends BasePage> getRepoNavPageClass() {
+		return DocsPage.class;
+	}
+}
diff --git a/src/com/gitblit/wicket/pages/MetricsPage.html b/src/main/java/com/gitblit/wicket/pages/MetricsPage.html
similarity index 100%
rename from src/com/gitblit/wicket/pages/MetricsPage.html
rename to src/main/java/com/gitblit/wicket/pages/MetricsPage.html
diff --git a/src/main/java/com/gitblit/wicket/pages/MetricsPage.java b/src/main/java/com/gitblit/wicket/pages/MetricsPage.java
new file mode 100644
index 0000000..3aa1fcc
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/MetricsPage.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright 2011 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit.wicket.pages;
+
+import java.awt.Color;
+import java.awt.Dimension;
+import java.text.MessageFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+import org.apache.wicket.PageParameters;
+import org.apache.wicket.markup.html.basic.Label;
+import org.eclipse.jgit.lib.Repository;
+import org.wicketstuff.googlecharts.ChartAxis;
+import org.wicketstuff.googlecharts.ChartAxisType;
+import org.wicketstuff.googlecharts.ChartProvider;
+import org.wicketstuff.googlecharts.ChartType;
+import org.wicketstuff.googlecharts.IChartData;
+import org.wicketstuff.googlecharts.LineStyle;
+import org.wicketstuff.googlecharts.MarkerType;
+import org.wicketstuff.googlecharts.ShapeMarker;
+
+import com.gitblit.models.Metric;
+import com.gitblit.utils.MetricUtils;
+import com.gitblit.utils.StringUtils;
+import com.gitblit.wicket.CacheControl;
+import com.gitblit.wicket.WicketUtils;
+import com.gitblit.wicket.CacheControl.LastModified;
+import com.gitblit.wicket.charting.SecureChart;
+
+@CacheControl(LastModified.REPOSITORY)
+public class MetricsPage extends RepositoryPage {
+
+	public MetricsPage(PageParameters params) {
+		super(params);
+		Repository r = getRepository();
+		if (StringUtils.isEmpty(objectId)) {
+			add(new Label("branchTitle", getRepositoryModel().HEAD));
+		} else {
+			add(new Label("branchTitle", objectId));
+		}
+		Metric metricsTotal = null;
+		List<Metric> metrics = MetricUtils.getDateMetrics(r, objectId, true, null, getTimeZone());
+		metricsTotal = metrics.remove(0);
+		if (metricsTotal == null) {
+			add(new Label("branchStats", ""));
+		} else {
+			add(new Label("branchStats",
+					MessageFormat.format(getString("gb.branchStats"), metricsTotal.count,
+							metricsTotal.tag, getTimeUtils().duration(metricsTotal.duration))));
+		}
+		insertLinePlot("commitsChart", metrics);
+		insertBarPlot("dayOfWeekChart", getDayOfWeekMetrics(r, objectId));
+		insertPieChart("authorsChart", getAuthorMetrics(r, objectId));
+	}
+
+	private void insertLinePlot(String wicketId, List<Metric> metrics) {
+		if ((metrics != null) && (metrics.size() > 0)) {
+			IChartData data = WicketUtils.getChartData(metrics);
+
+			ChartProvider provider = new ChartProvider(new Dimension(400, 100), ChartType.LINE,
+					data);
+			ChartAxis dateAxis = new ChartAxis(ChartAxisType.BOTTOM);
+			dateAxis.setLabels(new String[] { metrics.get(0).name,
+					metrics.get(metrics.size() / 2).name, metrics.get(metrics.size() - 1).name });
+			provider.addAxis(dateAxis);
+
+			ChartAxis commitAxis = new ChartAxis(ChartAxisType.LEFT);
+			commitAxis.setLabels(new String[] { "",
+					String.valueOf((int) WicketUtils.maxValue(metrics)) });
+			provider.addAxis(commitAxis);
+
+			provider.setLineStyles(new LineStyle[] { new LineStyle(2, 4, 0), new LineStyle(0, 4, 1) });
+			provider.addShapeMarker(new ShapeMarker(MarkerType.CIRCLE, Color.decode("#002060"), 1, -1, 5));
+
+			add(new SecureChart(wicketId, provider));
+		} else {
+			add(WicketUtils.newBlankImage(wicketId));
+		}
+	}
+
+	private void insertBarPlot(String wicketId, List<Metric> metrics) {
+		if ((metrics != null) && (metrics.size() > 0)) {
+			IChartData data = WicketUtils.getChartData(metrics);
+
+			ChartProvider provider = new ChartProvider(new Dimension(400, 100),
+					ChartType.BAR_VERTICAL_SET, data);
+			ChartAxis dateAxis = new ChartAxis(ChartAxisType.BOTTOM);
+			List<String> labels = new ArrayList<String>();
+			for (Metric metric : metrics) {
+				labels.add(metric.name);
+			}
+			dateAxis.setLabels(labels.toArray(new String[labels.size()]));
+			provider.addAxis(dateAxis);
+
+			ChartAxis commitAxis = new ChartAxis(ChartAxisType.LEFT);
+			commitAxis.setLabels(new String[] { "",
+					String.valueOf((int) WicketUtils.maxValue(metrics)) });
+			provider.addAxis(commitAxis);
+
+			add(new SecureChart(wicketId, provider));
+		} else {
+			add(WicketUtils.newBlankImage(wicketId));
+		}
+	}
+
+	private void insertPieChart(String wicketId, List<Metric> metrics) {
+		if ((metrics != null) && (metrics.size() > 0)) {
+			IChartData data = WicketUtils.getChartData(metrics);
+			List<String> labels = new ArrayList<String>();
+			for (Metric metric : metrics) {
+				labels.add(metric.name);
+			}
+			ChartProvider provider = new ChartProvider(new Dimension(800, 200), ChartType.PIE, data);
+			provider.setPieLabels(labels.toArray(new String[labels.size()]));
+			add(new SecureChart(wicketId, provider));
+		} else {
+			add(WicketUtils.newBlankImage(wicketId));
+		}
+	}
+
+	private List<Metric> getDayOfWeekMetrics(Repository repository, String objectId) {
+		List<Metric> list = MetricUtils.getDateMetrics(repository, objectId, false, "E", getTimeZone());
+		SimpleDateFormat sdf = new SimpleDateFormat("E");
+		Calendar cal = Calendar.getInstance();
+
+		List<Metric> sorted = new ArrayList<Metric>();
+		int firstDayOfWeek = cal.getFirstDayOfWeek();
+		int dayOfWeek = cal.get(Calendar.DAY_OF_WEEK);
+
+		// rewind date to first day of week
+		cal.add(Calendar.DATE, firstDayOfWeek - dayOfWeek);
+		for (int i = 0; i < 7; i++) {
+			String day = sdf.format(cal.getTime());
+			for (Metric metric : list) {
+				if (metric.name.equals(day)) {
+					sorted.add(metric);
+					list.remove(metric);
+					break;
+				}
+			}
+			cal.add(Calendar.DATE, 1);
+		}
+		return sorted;
+	}
+
+	private List<Metric> getAuthorMetrics(Repository repository, String objectId) {
+		List<Metric> authors = MetricUtils.getAuthorMetrics(repository, objectId, true);
+		Collections.sort(authors, new Comparator<Metric>() {
+			@Override
+			public int compare(Metric o1, Metric o2) {
+				if (o1.count > o2.count) {
+					return -1;
+				} else if (o1.count < o2.count) {
+					return 1;
+				}
+				return 0;
+			}
+		});
+		if (authors.size() > 10) {
+			return authors.subList(0, 9);
+		}
+		return authors;
+	}
+
+	@Override
+	protected String getPageName() {
+		return getString("gb.metrics");
+	}
+	
+	@Override
+	protected Class<? extends BasePage> getRepoNavPageClass() {
+		return SummaryPage.class;
+	}
+}
diff --git a/src/main/java/com/gitblit/wicket/pages/MyDashboardPage.html b/src/main/java/com/gitblit/wicket/pages/MyDashboardPage.html
new file mode 100644
index 0000000..b55688c
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/MyDashboardPage.html
@@ -0,0 +1,79 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"  
+      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"  
+      xml:lang="en"  
+      lang="en"> 
+
+<body>
+<wicket:extend>
+<div class="container">
+
+	<div class="row" style="padding-top:10px;">
+		<div class="span7">
+			<div class="hidden-phone hidden-tablet markdown" style="padding-bottom: 10px;" wicket:id="repositoriesMessage">[repositories message]</div>
+			<div ng-non-bindable wicket:id="activity"></div>
+		</div>
+		<div class="span5">
+			<div wicket:id="repositoryTabs"></div>
+		</div>		
+	</div>
+</div>
+
+<wicket:fragment wicket:id="anonymousTabsFragment">
+	<ul class="nav nav-pills">
+		<li class="active"><a href="#recent" data-toggle="tab"><wicket:message key="gb.active">[active]</wicket:message></a></li>
+		<li><a href="#projects" data-toggle="tab"><wicket:message key="gb.projects">[projects]</wicket:message></a></li>
+	</ul>
+	<div class="tab-content">
+		<div class="tab-pane active" id="recent">
+			<div wicket:id="active">[recently active]</div>
+		</div>
+		<div class="tab-pane" id="projects">
+			<div wicket:id="projects">[all projects]</div>
+		</div>
+	</div>
+</wicket:fragment>
+
+<wicket:fragment wicket:id="authenticatedTabsFragment">
+	<ul class="nav nav-pills">
+		<li class="active"><a href="#starred" data-toggle="tab"><wicket:message key="gb.starred">[starred]</wicket:message></a></li>
+		<li><a href="#owned" data-toggle="tab"><wicket:message key="gb.owned">[owned]</wicket:message></a></li>
+		<li><a href="#recent" data-toggle="tab"><wicket:message key="gb.active">[active]</wicket:message></a></li>
+		<li><a href="#projects" data-toggle="tab"><wicket:message key="gb.projects">[projects]</wicket:message></a></li>
+	</ul>
+	<div class="tab-content">
+		<div class="tab-pane active" id="starred">
+			<div wicket:id="starred">[starred repositories]</div>
+		</div>
+		<div class="tab-pane" id="owned">
+			<div wicket:id="owned">[my repositories]</div>
+		</div>
+		<div class="tab-pane" id="recent">
+			<div wicket:id="active">[recently active]</div>
+		</div>
+		<div class="tab-pane" id="projects">
+			<div wicket:id="projects">[all projects]</div>
+		</div>
+	</div>
+</wicket:fragment>
+
+<wicket:fragment wicket:id="activityFragment">
+	<div class="dashboardTitle"><span class="hidden-phone hidden-tablet" wicket:id="feedTitle"></span> <small><span wicket:id="feedheader"></span></small></div>
+	<div class="hidden-phone hidden-tablet"  style="text-align:center;">
+		<div wicket:id="charts"></div>
+	</div>
+	<div wicket:id="digests"></div>
+</wicket:fragment>
+
+<wicket:fragment wicket:id="chartsFragment">
+	<table>
+		<tr>
+			<td><div id="chartRepositories" style="display:inline-block;width: 175px; height:175px"></div></td>
+			<td><div id="chartAuthors" style="display:inline-block;width: 175px; height: 175px;"></div></td>
+		</tr>
+	</table>
+</wicket:fragment>
+
+</wicket:extend>
+</body>
+</html>
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/pages/MyDashboardPage.java b/src/main/java/com/gitblit/wicket/pages/MyDashboardPage.java
new file mode 100644
index 0000000..32c128d
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/MyDashboardPage.java
@@ -0,0 +1,271 @@
+/*
+ * Copyright 2013 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit.wicket.pages;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.wicket.Component;
+import org.apache.wicket.PageParameters;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.panel.Fragment;
+import org.eclipse.jgit.lib.Constants;
+
+import com.gitblit.GitBlit;
+import com.gitblit.Keys;
+import com.gitblit.models.ProjectModel;
+import com.gitblit.models.RepositoryModel;
+import com.gitblit.models.UserModel;
+import com.gitblit.utils.ArrayUtils;
+import com.gitblit.utils.MarkdownUtils;
+import com.gitblit.utils.StringUtils;
+import com.gitblit.wicket.CacheControl;
+import com.gitblit.wicket.GitBlitWebSession;
+import com.gitblit.wicket.WicketUtils;
+import com.gitblit.wicket.CacheControl.LastModified;
+import com.gitblit.wicket.panels.FilterableProjectList;
+import com.gitblit.wicket.panels.FilterableRepositoryList;
+
+@CacheControl(LastModified.ACTIVITY)
+public class MyDashboardPage extends DashboardPage {
+
+	public MyDashboardPage() {
+		super();
+		setup(null);
+	}
+
+	public MyDashboardPage(PageParameters params) {
+		super(params);
+		setup(params);
+	}
+
+	@Override
+	protected boolean reusePageParameters() {
+		return true;
+	}
+
+	private void setup(PageParameters params) {
+		setupPage("", "");
+		// check to see if we should display a login message
+		boolean authenticateView = GitBlit.getBoolean(Keys.web.authenticateViewPages, true);
+		if (authenticateView && !GitBlitWebSession.get().isLoggedIn()) {
+			String messageSource = GitBlit.getString(Keys.web.loginMessage, "gitblit");
+			String message = readMarkdown(messageSource, "login.mkd");
+			Component repositoriesMessage = new Label("repositoriesMessage", message);
+			add(repositoriesMessage.setEscapeModelStrings(false));
+			add(new Label("activity").setVisible(false));
+			add(new Label("repositoryTabs").setVisible(false));
+			return;
+		}
+
+		// Load the markdown welcome message
+		String messageSource = GitBlit.getString(Keys.web.repositoriesMessage, "gitblit");
+		String message = readMarkdown(messageSource, "welcome.mkd");
+		Component repositoriesMessage = new Label("repositoriesMessage", message)
+				.setEscapeModelStrings(false).setVisible(message.length() > 0);
+		add(repositoriesMessage);
+
+		UserModel user = GitBlitWebSession.get().getUser();
+		if (user == null) {
+			user = UserModel.ANONYMOUS;
+		}
+
+		// parameters
+		int daysBack = params == null ? 0 : WicketUtils.getDaysBack(params);
+		if (daysBack < 1) {
+			daysBack = GitBlit.getInteger(Keys.web.activityDuration, 7);
+		}
+		Calendar c = Calendar.getInstance();
+		c.add(Calendar.DATE, -1*daysBack);
+		Date minimumDate = c.getTime();
+		
+		// build repo lists 
+		List<RepositoryModel> starred = new ArrayList<RepositoryModel>();
+		List<RepositoryModel> owned = new ArrayList<RepositoryModel>();
+		List<RepositoryModel> active = new ArrayList<RepositoryModel>();
+
+		for (RepositoryModel model : getRepositoryModels()) {
+			if (model.isUsersPersonalRepository(user.username) || model.isOwner(user.username)) {
+				owned.add(model);
+			}
+			
+			if (user.getPreferences().isStarredRepository(model.name)) {
+				starred.add(model);
+			}
+			
+			if (model.isShowActivity() && model.lastChange.after(minimumDate)) {
+				active.add(model);
+			}
+		}
+		
+		Comparator<RepositoryModel> lastUpdateSort = new Comparator<RepositoryModel>() {
+			@Override
+			public int compare(RepositoryModel o1, RepositoryModel o2) {
+				return o2.lastChange.compareTo(o1.lastChange);
+			}
+		};
+		
+		Collections.sort(owned, lastUpdateSort);
+		Collections.sort(starred, lastUpdateSort);
+		Collections.sort(active, lastUpdateSort);
+		
+		String activityTitle;
+		Set<RepositoryModel> feed = new HashSet<RepositoryModel>();
+		feed.addAll(starred);
+		feed.addAll(owned);
+		if (feed.isEmpty()) {
+			// no starred or owned, go with recent activity
+			activityTitle = getString("gb.recentActivity");
+			feed.addAll(active);
+		} else if (starred.isEmpty()){
+			// no starred, owned repos feed
+			activityTitle = getString("gb.owned");
+		} else if (owned.isEmpty()){
+			// no owned, starred repos feed
+			activityTitle = getString("gb.starred");
+		} else {
+			// starred and owned repositories
+			activityTitle = getString("gb.starredAndOwned");
+		}
+		
+		addActivity(user, feed, activityTitle, daysBack);
+		
+		Fragment repositoryTabs;
+		if (UserModel.ANONYMOUS.equals(user)) {
+			repositoryTabs = new Fragment("repositoryTabs", "anonymousTabsFragment", this);
+		} else {
+			repositoryTabs = new Fragment("repositoryTabs", "authenticatedTabsFragment", this);
+		}
+		
+		add(repositoryTabs);
+		
+		// projects list
+		List<ProjectModel> projects = GitBlit.self().getProjectModels(getRepositoryModels(), false);
+		repositoryTabs.add(new FilterableProjectList("projects", projects));
+		
+		// active repository list
+		if (active.isEmpty()) {
+			repositoryTabs.add(new Label("active").setVisible(false));
+		} else {
+			FilterableRepositoryList repoList = new FilterableRepositoryList("active", active);
+			repoList.setTitle(getString("gb.activeRepositories"), "icon-time");
+			repositoryTabs.add(repoList);
+		}
+		
+		// starred repository list
+		if (ArrayUtils.isEmpty(starred)) {
+			repositoryTabs.add(new Label("starred").setVisible(false));
+		} else {
+			FilterableRepositoryList repoList = new FilterableRepositoryList("starred", starred);
+			repoList.setTitle(getString("gb.starredRepositories"), "icon-star");
+			repositoryTabs.add(repoList);
+		}
+		
+		// owned repository list
+		if (ArrayUtils.isEmpty(owned)) {
+			repositoryTabs.add(new Label("owned").setVisible(false));
+		} else {
+			FilterableRepositoryList repoList = new FilterableRepositoryList("owned", owned);
+			repoList.setTitle(getString("gb.myRepositories"), "icon-user");
+			repoList.setAllowCreate(user.canCreate() || user.canAdmin());
+			repositoryTabs.add(repoList);
+		}
+	}
+	
+	private String readMarkdown(String messageSource, String resource) {
+		String message = "";
+		if (messageSource.equalsIgnoreCase("gitblit")) {
+			// Read default message
+			message = readDefaultMarkdown(resource);
+		} else {
+			// Read user-supplied message
+			if (!StringUtils.isEmpty(messageSource)) {
+				File file = GitBlit.getFileOrFolder(messageSource);
+				if (file.exists()) {
+					try {
+						FileInputStream fis = new FileInputStream(file);
+						InputStreamReader reader = new InputStreamReader(fis,
+								Constants.CHARACTER_ENCODING);
+						message = MarkdownUtils.transformMarkdown(reader);
+						reader.close();
+					} catch (Throwable t) {
+						message = getString("gb.failedToRead") + " " + file;
+						warn(message, t);
+					}
+				} else {
+					message = messageSource + " " + getString("gb.isNotValidFile");
+				}
+			}
+		}
+		return message;
+	}
+
+	private String readDefaultMarkdown(String file) {
+		String base = file.substring(0, file.lastIndexOf('.'));
+		String ext = file.substring(file.lastIndexOf('.'));
+		String lc = getLanguageCode();
+		String cc = getCountryCode();
+
+		// try to read file_en-us.ext, file_en.ext, file.ext
+		List<String> files = new ArrayList<String>();
+		if (!StringUtils.isEmpty(lc)) {
+			if (!StringUtils.isEmpty(cc)) {
+				files.add(base + "_" + lc + "-" + cc + ext);
+				files.add(base + "_" + lc + "_" + cc + ext);
+			}
+			files.add(base + "_" + lc + ext);
+		}
+		files.add(file);
+
+		for (String name : files) {
+			String message;
+			InputStreamReader reader = null;
+			try {
+				InputStream is = getClass().getResourceAsStream("/" + name);
+				if (is == null) {
+					continue;
+				}
+				reader = new InputStreamReader(is, Constants.CHARACTER_ENCODING);
+				message = MarkdownUtils.transformMarkdown(reader);
+				reader.close();
+				return message;
+			} catch (Throwable t) {
+				message = MessageFormat.format(getString("gb.failedToReadMessage"), file);
+				error(message, t, false);
+				return message;
+			} finally {
+				if (reader != null) {
+					try {
+						reader.close();
+					} catch (Exception e) {
+					}
+				}
+			}			
+		}
+		return MessageFormat.format(getString("gb.failedToReadMessage"), file);
+	}
+}
diff --git a/src/main/java/com/gitblit/wicket/pages/OverviewPage.html b/src/main/java/com/gitblit/wicket/pages/OverviewPage.html
new file mode 100644
index 0000000..995f8df
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/OverviewPage.html
@@ -0,0 +1,60 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"  
+      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"  
+      xml:lang="en"  
+      lang="en"> 
+<body>
+<wicket:extend>
+
+	<div class="row" style="clear:both;">
+	<div class="span6 hidden-phone" style="border-right: 1px solid #ddd; margin-right: -1px;">
+	
+		<!-- Repository info -->
+		<div>
+			<table class="summary">
+				<tr><th><wicket:message key="gb.description">[description]</wicket:message></th><td><span wicket:id="repositoryDescription">[repository description]</span></td></tr>
+				<tr><th><wicket:message key="gb.owners">[owner]</wicket:message></th><td><span wicket:id="repositoryOwners"><span wicket:id="owner"></span><span wicket:id="comma"></span></span></td></tr>
+				<tr><th><wicket:message key="gb.lastChange">[last change]</wicket:message></th><td><span wicket:id="repositoryLastChange">[repository last change]</span></td></tr>
+				<tr><th><wicket:message key="gb.size">[size]</wicket:message></th><td><span wicket:id="repositorySize">[repository size]</span></td></tr>
+				<tr class="hidden-phone hidden-tablet"><th style="vertical-align: top;"><wicket:message key="gb.stats">[stats]</wicket:message></th>
+				<td><div><span wicket:id="branchStats">[branch stats]</span> <span class="link"><a wicket:id="metrics"><wicket:message key="gb.metrics">[metrics]</wicket:message></a></span></div>
+					<span class="hidden-tablet" style="margin: 10px 0px;" id="chartDaily"></span>
+				</td></tr>
+			</table>
+
+			<ul class="nav nav-tabs" style="padding-top:10px;">
+				<li class="active"><a data-toggle="tab" href="#branches"><wicket:message key="gb.branches">[branches]</wicket:message></a></li>
+				<li><a data-toggle="tab" href="#tags"><wicket:message key="gb.tags">[tags]</wicket:message></a></li>
+				<li><a data-toggle="tab" href="#forks"><wicket:message key="gb.forks">[forks]</wicket:message></a></li>
+			</ul>
+ 
+			<div class="tab-content">
+				<div class="tab-pane active" id="branches">
+					<!-- branches -->
+					<div style="padding: 0 15px 15px 0px;" wicket:id="branchesPanel">[branches panel]</div>
+				</div>
+				<div class="tab-pane" id="tags">
+					<!-- tags -->
+					<div style="padding: 0 15px 15px 0px;" wicket:id="tagsPanel">[tags panel]</div>
+				</div>
+				<div class="tab-pane" id="forks">
+				</div>
+			</div>						
+		</div>
+		
+	</div>
+
+		<!-- pushes -->
+		<div class="span6">
+			<div class="hidden-tablet" style="padding-bottom: 10px; margin-bottom: 10px; border-bottom: 1px solid #ddd;" wicket:id="repositoryUrlPanel">[repository url panel]</div>
+		
+			<div wicket:id="reflogPanel">[reflog panel]</div>	
+		</div>
+	</div>
+
+	<wicket:fragment wicket:id="ownersFragment">
+		
+	</wicket:fragment>
+</wicket:extend>	
+</body>
+</html>
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/pages/OverviewPage.java b/src/main/java/com/gitblit/wicket/pages/OverviewPage.java
new file mode 100644
index 0000000..e1de9f3
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/OverviewPage.java
@@ -0,0 +1,152 @@
+/*
+ * 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.wicket.pages;
+
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.wicket.PageParameters;
+import org.apache.wicket.behavior.HeaderContributor;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.link.BookmarkablePageLink;
+import org.apache.wicket.markup.repeater.Item;
+import org.apache.wicket.markup.repeater.data.DataView;
+import org.apache.wicket.markup.repeater.data.ListDataProvider;
+import org.eclipse.jgit.lib.Repository;
+
+import com.gitblit.GitBlit;
+import com.gitblit.Keys;
+import com.gitblit.models.Metric;
+import com.gitblit.models.RepositoryModel;
+import com.gitblit.models.UserModel;
+import com.gitblit.utils.JGitUtils;
+import com.gitblit.wicket.CacheControl;
+import com.gitblit.wicket.GitBlitWebSession;
+import com.gitblit.wicket.WicketUtils;
+import com.gitblit.wicket.CacheControl.LastModified;
+import com.gitblit.wicket.charting.GoogleChart;
+import com.gitblit.wicket.charting.GoogleCharts;
+import com.gitblit.wicket.charting.GoogleLineChart;
+import com.gitblit.wicket.panels.BranchesPanel;
+import com.gitblit.wicket.panels.LinkPanel;
+import com.gitblit.wicket.panels.ReflogPanel;
+import com.gitblit.wicket.panels.RepositoryUrlPanel;
+import com.gitblit.wicket.panels.TagsPanel;
+
+@CacheControl(LastModified.REPOSITORY)
+public class OverviewPage extends RepositoryPage {
+
+	public OverviewPage(PageParameters params) {
+		super(params);
+
+		int numberRefs = GitBlit.getInteger(Keys.web.summaryRefsCount, 5);
+
+		Repository r = getRepository();
+		final RepositoryModel model = getRepositoryModel();
+		UserModel user = GitBlitWebSession.get().getUser();
+		if (user == null) {
+			user = UserModel.ANONYMOUS;
+		}
+
+		List<Metric> metrics = null;
+		Metric metricsTotal = null;
+		if (!model.skipSummaryMetrics && GitBlit.getBoolean(Keys.web.generateActivityGraph, true)) {
+			metrics = GitBlit.self().getRepositoryDefaultMetrics(model, r);
+			metricsTotal = metrics.remove(0);
+		}
+
+		addSyndicationDiscoveryLink();
+
+		// repository description
+		add(new Label("repositoryDescription", getRepositoryModel().description));
+		
+		// owner links
+		final List<String> owners = new ArrayList<String>(getRepositoryModel().owners);
+		ListDataProvider<String> ownersDp = new ListDataProvider<String>(owners);
+		DataView<String> ownersView = new DataView<String>("repositoryOwners", ownersDp) {
+			private static final long serialVersionUID = 1L;
+			int counter = 0;
+			public void populateItem(final Item<String> item) {
+				String ownername = item.getModelObject();
+				UserModel ownerModel = GitBlit.self().getUserModel(ownername);
+				if (ownerModel != null) {
+					item.add(new LinkPanel("owner", null, ownerModel.getDisplayName(), UserPage.class,
+							WicketUtils.newUsernameParameter(ownerModel.username)).setRenderBodyOnly(true));
+				} else {
+					Label owner = new Label("owner", ownername);
+					WicketUtils.setCssStyle(owner, "text-decoration: line-through;");
+					WicketUtils.setHtmlTooltip(owner,  MessageFormat.format(getString("gb.failedToFindAccount"), ownername));
+					item.add(owner);
+				}
+				counter++;
+				item.add(new Label("comma", ",").setVisible(counter < owners.size()));
+				item.setRenderBodyOnly(true);
+			}
+		};
+		ownersView.setRenderBodyOnly(true);
+		add(ownersView);
+		
+		add(WicketUtils.createTimestampLabel("repositoryLastChange",
+				JGitUtils.getLastChange(r).when, getTimeZone(), getTimeUtils()));
+		add(new Label("repositorySize", model.size));
+		
+		if (metricsTotal == null) {
+			add(new Label("branchStats", ""));
+		} else {
+			add(new Label("branchStats",
+					MessageFormat.format(getString("gb.branchStats"), metricsTotal.count,
+							metricsTotal.tag, getTimeUtils().duration(metricsTotal.duration))));
+		}
+		add(new BookmarkablePageLink<Void>("metrics", MetricsPage.class,
+				WicketUtils.newRepositoryParameter(repositoryName)));
+
+		add(new RepositoryUrlPanel("repositoryUrlPanel", false, user, model));
+
+		int reflogCount = GitBlit.getInteger(Keys.web.overviewReflogCount, 5);
+		ReflogPanel reflog = new ReflogPanel("reflogPanel", getRepositoryModel(), r, reflogCount, 0);
+		add(reflog);
+		add(new TagsPanel("tagsPanel", repositoryName, r, numberRefs).hideIfEmpty());
+		add(new BranchesPanel("branchesPanel", getRepositoryModel(), r, numberRefs, false).hideIfEmpty());
+
+		// Display an activity line graph
+		insertActivityGraph(metrics);
+	}
+
+	@Override
+	protected String getPageName() {
+		return getString("gb.overview");
+	}
+
+	private void insertActivityGraph(List<Metric> metrics) {
+		if ((metrics != null) && (metrics.size() > 0)
+				&& GitBlit.getBoolean(Keys.web.generateActivityGraph, true)) {
+			
+			// daily line chart
+			GoogleChart chart = new GoogleLineChart("chartDaily", "", "unit",
+					getString("gb.commits"));
+			for (Metric metric : metrics) {
+				chart.addValue(metric.name, metric.count);
+			}
+			chart.setWidth(375);
+			chart.setHeight(150);
+			
+			GoogleCharts charts = new GoogleCharts();
+			charts.addChart(chart);
+			add(new HeaderContributor(charts));
+		}
+	}
+}
diff --git a/src/com/gitblit/wicket/pages/PatchPage.html b/src/main/java/com/gitblit/wicket/pages/PatchPage.html
similarity index 100%
rename from src/com/gitblit/wicket/pages/PatchPage.html
rename to src/main/java/com/gitblit/wicket/pages/PatchPage.html
diff --git a/src/main/java/com/gitblit/wicket/pages/PatchPage.java b/src/main/java/com/gitblit/wicket/pages/PatchPage.java
new file mode 100644
index 0000000..be959d0
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/PatchPage.java
@@ -0,0 +1,72 @@
+/*
+ * 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.wicket.pages;
+
+import org.apache.wicket.PageParameters;
+import org.apache.wicket.markup.html.WebPage;
+import org.apache.wicket.markup.html.basic.Label;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+
+import com.gitblit.GitBlit;
+import com.gitblit.utils.DiffUtils;
+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.GitBlitWebSession;
+import com.gitblit.wicket.WicketUtils;
+
+@CacheControl(LastModified.BOOT)
+public class PatchPage extends WebPage {
+
+	public PatchPage(PageParameters params) {
+		super(params);
+
+		if (!params.containsKey("r")) {
+			GitBlitWebSession.get().cacheErrorMessage(getString("gb.repositoryNotSpecified"));
+			redirectToInterceptPage(new RepositoriesPage());
+			return;
+		}
+
+		final String repositoryName = WicketUtils.getRepositoryName(params);
+		final String baseObjectId = WicketUtils.getBaseObjectId(params);
+		final String objectId = WicketUtils.getObject(params);
+		final String blobPath = WicketUtils.getPath(params);
+
+		Repository r = GitBlit.self().getRepository(repositoryName);
+		if (r == null) {
+			GitBlitWebSession.get().cacheErrorMessage(getString("gb.canNotLoadRepository") + " " + repositoryName);
+			redirectToInterceptPage(new RepositoriesPage());
+			return;
+		}
+
+		RevCommit commit = JGitUtils.getCommit(r, objectId);
+		if (commit == null) {
+			GitBlitWebSession.get().cacheErrorMessage(getString("gb.commitIsNull"));
+			redirectToInterceptPage(new RepositoriesPage());
+			return;
+		}
+
+		RevCommit baseCommit = null;
+		if (!StringUtils.isEmpty(baseObjectId)) {
+			baseCommit = JGitUtils.getCommit(r, baseObjectId);
+		}
+		String patch = DiffUtils.getCommitPatch(r, baseCommit, commit, blobPath);
+		add(new Label("patchText", patch));
+		r.close();
+	}
+}
diff --git a/src/main/java/com/gitblit/wicket/pages/ProjectPage.html b/src/main/java/com/gitblit/wicket/pages/ProjectPage.html
new file mode 100644
index 0000000..d19496b
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/ProjectPage.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"  
+      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"  
+      xml:lang="en"  
+      lang="en"> 
+
+<body>
+<wicket:extend>
+		<div class="container">
+			<div class="row">
+				<div class="span12">
+					<div class="dashboardTitle">
+						<span wicket:id="projectTitle"></span>
+						<small class="hidden-phone"><span wicket:id="projectDescription"></span></small>
+						
+						<a
+							class="hidden-phone hidden-tablet brand"
+							style="text-decoration: none;" wicket:id="syndication"
+							wicket:message="title:gb.feed"> <img
+							style="border: 0px; vertical-align: middle;" src="feed_16x16.png"></img>
+						</a>
+					</div>
+				</div>
+			</div>
+			
+			<div class="row">
+				<div class="span7">					
+					<div class="hidden-phone hidden-tablet markdown" style="padding-bottom: 30px;" wicket:id="projectMessage">[project message]</div>
+					<div ng-non-bindable wicket:id="activity">[activity panel]</div>
+				</div>
+				<div class="span5">
+					<div class="hidden-phone hidden-tablet markdown" wicket:id="repositoriesMessage">[repositories message]</div>
+					<div wicket:id="repositoryList">[repository list]</div>
+				</div>
+			</div>
+		</div>
+
+<wicket:fragment wicket:id="activityFragment">
+	<div class="dashboardTitle"><span class="hidden-phone hidden-tablet" wicket:id="feedTitle"></span> <small><span wicket:id="feedheader"></span></small></div>
+	<div class="hidden-phone hidden-tablet"  style="text-align:center;">
+		<div wicket:id="charts"></div>
+	</div>
+	<div wicket:id="digests"></div>
+</wicket:fragment>
+
+<wicket:fragment wicket:id="chartsFragment">
+	<table>
+		<tr>
+			<td><div id="chartRepositories" style="display:inline-block;width: 175px; height:175px"></div></td>
+			<td><div id="chartAuthors" style="display:inline-block;width: 175px; height: 175px;"></div></td>
+		</tr>
+	</table>
+</wicket:fragment>
+
+</wicket:extend>
+</body>
+</html>
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/pages/ProjectPage.java b/src/main/java/com/gitblit/wicket/pages/ProjectPage.java
new file mode 100644
index 0000000..c938891
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/ProjectPage.java
@@ -0,0 +1,253 @@
+/*
+ * 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.wicket.pages;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+import org.apache.wicket.Component;
+import org.apache.wicket.PageParameters;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.link.ExternalLink;
+
+import com.gitblit.GitBlit;
+import com.gitblit.Keys;
+import com.gitblit.SyndicationServlet;
+import com.gitblit.models.ProjectModel;
+import com.gitblit.models.RepositoryModel;
+import com.gitblit.models.UserModel;
+import com.gitblit.utils.MarkdownUtils;
+import com.gitblit.utils.StringUtils;
+import com.gitblit.wicket.CacheControl;
+import com.gitblit.wicket.CacheControl.LastModified;
+import com.gitblit.wicket.GitBlitWebApp;
+import com.gitblit.wicket.GitBlitWebSession;
+import com.gitblit.wicket.GitblitRedirectException;
+import com.gitblit.wicket.PageRegistration;
+import com.gitblit.wicket.PageRegistration.DropDownMenuItem;
+import com.gitblit.wicket.PageRegistration.DropDownMenuRegistration;
+import com.gitblit.wicket.WicketUtils;
+import com.gitblit.wicket.panels.FilterableRepositoryList;
+
+@CacheControl(LastModified.PROJECT)
+public class ProjectPage extends DashboardPage {
+	
+	List<ProjectModel> projectModels = new ArrayList<ProjectModel>();
+
+	public ProjectPage() {
+		super();
+		throw new GitblitRedirectException(GitBlitWebApp.get().getHomePage());
+	}
+
+	public ProjectPage(PageParameters params) {
+		super(params);
+		setup(params);
+	}
+	
+	protected Class<? extends BasePage> getRootNavPageClass() {
+		return RepositoriesPage.class;
+	}
+
+	@Override
+	protected void setLastModified() {
+		if (getClass().isAnnotationPresent(CacheControl.class)) {
+			CacheControl cacheControl = getClass().getAnnotation(CacheControl.class);
+			switch (cacheControl.value()) {
+			case PROJECT:
+				String projectName = WicketUtils.getProjectName(getPageParameters());
+				if (!StringUtils.isEmpty(projectName)) {
+					ProjectModel project = getProjectModel(projectName);
+					if (project != null) {
+						setLastModified(project.lastChange);
+					}
+				}
+				break;
+			default:
+				super.setLastModified();
+			}
+		}
+	}
+	
+	private void setup(PageParameters params) {
+		setupPage("", "");
+		// check to see if we should display a login message
+		boolean authenticateView = GitBlit.getBoolean(Keys.web.authenticateViewPages, true);
+		if (authenticateView && !GitBlitWebSession.get().isLoggedIn()) {
+			authenticationError("Please login");
+			return;
+		}
+
+		String projectName = WicketUtils.getProjectName(params);
+		if (StringUtils.isEmpty(projectName)) {
+			throw new GitblitRedirectException(GitBlitWebApp.get().getHomePage());
+		}
+		
+		ProjectModel project = getProjectModel(projectName);
+		if (project == null) {
+			throw new GitblitRedirectException(GitBlitWebApp.get().getHomePage());
+		}
+		
+		add(new Label("projectTitle", project.getDisplayName()));
+		add(new Label("projectDescription", project.description));
+		
+		String feedLink = SyndicationServlet.asLink(getRequest().getRelativePathPrefixToContextRoot(), projectName, null, 0);
+		add(new ExternalLink("syndication", feedLink));
+
+		add(WicketUtils.syndicationDiscoveryLink(SyndicationServlet.getTitle(project.getDisplayName(),
+				null), feedLink));
+		
+		// project markdown message
+		String pmessage = transformMarkdown(project.projectMarkdown);
+		Component projectMessage = new Label("projectMessage", pmessage)
+				.setEscapeModelStrings(false).setVisible(pmessage.length() > 0);
+		add(projectMessage);
+
+		// markdown message above repositories list
+		String rmessage = transformMarkdown(project.repositoriesMarkdown);
+		Component repositoriesMessage = new Label("repositoriesMessage", rmessage)
+				.setEscapeModelStrings(false).setVisible(rmessage.length() > 0);
+		add(repositoriesMessage);
+
+		UserModel user = GitBlitWebSession.get().getUser();
+		if (user == null) {
+			user = UserModel.ANONYMOUS;
+		}
+		int daysBack = params == null ? 0 : WicketUtils.getDaysBack(params);
+		if (daysBack < 1) {
+			daysBack = GitBlit.getInteger(Keys.web.activityDuration, 7);
+		}
+		// reset the daysback parameter so that we have a complete project
+		// repository list.  the recent activity will be built up by the
+		// reflog utils.
+		params.remove("db");
+		
+		List<RepositoryModel> repositories = getRepositories(params);
+		Collections.sort(repositories, new Comparator<RepositoryModel>() {
+			@Override
+			public int compare(RepositoryModel o1, RepositoryModel o2) {
+				// reverse-chronological sort
+				return o2.lastChange.compareTo(o1.lastChange);
+			}
+		});
+
+		addActivity(user, repositories, getString("gb.recentActivity"), daysBack);
+		
+		if (repositories.isEmpty()) {
+			add(new Label("repositoryList").setVisible(false));
+		} else {
+			FilterableRepositoryList repoList = new FilterableRepositoryList("repositoryList", repositories);
+			repoList.setAllowCreate(user.canCreate(project.name + "/"));
+			add(repoList);
+		}
+	}
+	
+	@Override
+	protected void addDropDownMenus(List<PageRegistration> pages) {
+		PageParameters params = getPageParameters();
+
+		DropDownMenuRegistration menu = new DropDownMenuRegistration("gb.filters",
+				ProjectPage.class);
+		// preserve time filter option on repository choices
+		menu.menuItems.addAll(getRepositoryFilterItems(params));
+
+		// preserve repository filter option on time choices
+		menu.menuItems.addAll(getTimeFilterItems(params));
+
+		if (menu.menuItems.size() > 0) {
+			// Reset Filter
+			menu.menuItems.add(new DropDownMenuItem(getString("gb.reset"), "p", WicketUtils.getProjectName(params)));
+		}
+
+		pages.add(menu);
+		
+		DropDownMenuRegistration projects = new DropDownMenuRegistration("gb.projects",
+				ProjectPage.class);
+		projects.menuItems.addAll(getProjectsMenu());
+		pages.add(projects);
+	}
+	
+	@Override
+	protected List<ProjectModel> getProjectModels() {
+		if (projectModels.isEmpty()) {
+			List<RepositoryModel> repositories = getRepositoryModels();
+			List<ProjectModel> projects = GitBlit.self().getProjectModels(repositories, false);
+			projectModels.addAll(projects);
+		}
+		return projectModels;
+	}
+	
+	private ProjectModel getProjectModel(String name) {
+		for (ProjectModel project : getProjectModels()) {
+			if (name.equalsIgnoreCase(project.name)) {
+				return project;
+			}
+		}
+		return null;
+	}
+	
+	protected List<DropDownMenuItem> getProjectsMenu() {
+		List<DropDownMenuItem> menu = new ArrayList<DropDownMenuItem>();
+		List<ProjectModel> projects = new ArrayList<ProjectModel>();
+		for (ProjectModel model : getProjectModels()) {
+			if (!model.isUserProject()) {
+				projects.add(model);
+			}
+		}
+		int maxProjects = 15;
+		boolean showAllProjects = projects.size() > maxProjects;
+		if (showAllProjects) {
+
+			// sort by last changed
+			Collections.sort(projects, new Comparator<ProjectModel>() {
+				@Override
+				public int compare(ProjectModel o1, ProjectModel o2) {
+					return o2.lastChange.compareTo(o1.lastChange);
+				}
+			});
+
+			// take most recent subset
+			projects = projects.subList(0, maxProjects);
+
+			// sort those by name
+			Collections.sort(projects);
+		}
+
+		for (ProjectModel project : projects) {
+			menu.add(new DropDownMenuItem(project.getDisplayName(), "p", project.name));
+		}
+		if (showAllProjects) {
+			menu.add(new DropDownMenuItem());
+			menu.add(new DropDownMenuItem("all projects", null, null));
+		}
+		return menu;
+	}
+	
+	private String transformMarkdown(String markdown) {
+		String message = "";
+		if (!StringUtils.isEmpty(markdown)) {
+			// Read user-supplied message
+			try {
+				message = MarkdownUtils.transformMarkdown(markdown);
+			} catch (Throwable t) {
+				message = getString("gb.failedToRead") + " " + markdown;
+				warn(message, t);
+			}
+		}
+		return message;
+	}
+}
diff --git a/src/main/java/com/gitblit/wicket/pages/ProjectsPage.html b/src/main/java/com/gitblit/wicket/pages/ProjectsPage.html
new file mode 100644
index 0000000..2d446ec
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/ProjectsPage.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"  
+      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"  
+      xml:lang="en"  
+      lang="en"> 
+
+<body>
+<wicket:extend>
+<div class="container">
+	
+	<table class="repositories">
+		<thead>
+			<tr>	
+				<th class="left">
+					<i class="icon-folder-close" ></i>
+					<wicket:message key="gb.project">Project</wicket:message>
+				</th>
+				<th class="hidden-phone" ><span><wicket:message key="gb.description">Description</wicket:message></span></th>
+				<th class="hidden-phone"><wicket:message key="gb.repositories">Repositories</wicket:message></th>
+				<th><wicket:message key="gb.lastChange">Last Change</wicket:message></th>
+				<th class="right"></th>
+			</tr>
+		</thead>
+		<tbody>		
+       		<tr wicket:id="project">
+				<td class="left" style="padding-left:3px;" ><span style="padding-left:3px;" wicket:id="projectTitle">[project title]</span></td>
+        		<td class="hidden-phone"><span class="list" wicket:id="projectDescription">[project description]</span></td>
+        		<td class="hidden-phone" style="padding-right:15px;"><span style="font-size:0.8em;" wicket:id="repositoryCount">[repository count]</span></td>
+        		<td><span wicket:id="projectLastChange">[last change]</span></td>
+		        <td class="rightAlign"></td>				       			
+       		</tr>
+    	</tbody>
+	</table>
+</div>
+</wicket:extend>
+</body>
+</html>
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/pages/ProjectsPage.java b/src/main/java/com/gitblit/wicket/pages/ProjectsPage.java
new file mode 100644
index 0000000..d0001ec
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/ProjectsPage.java
@@ -0,0 +1,136 @@
+/*
+ * 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.wicket.pages;
+
+import java.util.List;
+
+import org.apache.wicket.PageParameters;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.repeater.Item;
+import org.apache.wicket.markup.repeater.data.DataView;
+import org.apache.wicket.markup.repeater.data.ListDataProvider;
+
+import com.gitblit.GitBlit;
+import com.gitblit.Keys;
+import com.gitblit.models.ProjectModel;
+import com.gitblit.wicket.GitBlitWebSession;
+import com.gitblit.wicket.PageRegistration;
+import com.gitblit.wicket.PageRegistration.DropDownMenuItem;
+import com.gitblit.wicket.PageRegistration.DropDownMenuRegistration;
+import com.gitblit.wicket.WicketUtils;
+import com.gitblit.wicket.panels.LinkPanel;
+
+public class ProjectsPage extends RootPage {
+
+	public ProjectsPage() {
+		super();
+		setup(null);
+	}
+
+	public ProjectsPage(PageParameters params) {
+		super(params);
+		setup(params);
+	}
+
+	@Override
+	protected boolean reusePageParameters() {
+		return true;
+	}
+	
+	@Override
+	protected Class<? extends BasePage> getRootNavPageClass() {
+		return RepositoriesPage.class;
+	}
+
+	@Override
+	protected List<ProjectModel> getProjectModels() {
+		return GitBlit.self().getProjectModels(getRepositoryModels(), false);
+	}
+
+	private void setup(PageParameters params) {
+		setupPage("", "");
+		// check to see if we should display a login message
+		boolean authenticateView = GitBlit.getBoolean(Keys.web.authenticateViewPages, true);
+		if (authenticateView && !GitBlitWebSession.get().isLoggedIn()) {
+			add(new Label("projectsPanel"));
+			return;
+		}
+
+		List<ProjectModel> projects = getProjects(params);
+
+		ListDataProvider<ProjectModel> dp = new ListDataProvider<ProjectModel>(projects);
+
+		DataView<ProjectModel> dataView = new DataView<ProjectModel>("project", dp) {
+			private static final long serialVersionUID = 1L;
+			int counter;
+
+			@Override
+			protected void onBeforeRender() {
+				super.onBeforeRender();
+				counter = 0;
+			}
+
+			public void populateItem(final Item<ProjectModel> item) {
+				final ProjectModel entry = item.getModelObject();
+
+				PageParameters pp = WicketUtils.newProjectParameter(entry.name);
+				item.add(new LinkPanel("projectTitle", "list", entry.getDisplayName(),
+						ProjectPage.class, pp));
+				item.add(new LinkPanel("projectDescription", "list", entry.description,
+						ProjectPage.class, pp));
+
+				item.add(new Label("repositoryCount", entry.repositories.size()
+						+ " "
+						+ (entry.repositories.size() == 1 ? getString("gb.repository")
+								: getString("gb.repositories"))));
+
+				String lastChange;
+				if (entry.lastChange.getTime() == 0) {
+					lastChange = "--";
+				} else {
+					lastChange = getTimeUtils().timeAgo(entry.lastChange);
+				}
+				Label lastChangeLabel = new Label("projectLastChange", lastChange);
+				item.add(lastChangeLabel);
+				WicketUtils.setCssClass(lastChangeLabel, getTimeUtils()
+						.timeAgoCss(entry.lastChange));
+				WicketUtils.setAlternatingBackground(item, counter);
+				counter++;
+			}
+		};
+		add(dataView);
+	}
+
+	@Override
+	protected void addDropDownMenus(List<PageRegistration> pages) {
+		PageParameters params = getPageParameters();
+		
+		DropDownMenuRegistration menu = new DropDownMenuRegistration("gb.filters",
+				ProjectsPage.class);
+		// preserve time filter option on repository choices
+		menu.menuItems.addAll(getRepositoryFilterItems(params));
+
+		// preserve repository filter option on time choices
+		menu.menuItems.addAll(getTimeFilterItems(params));
+
+		if (menu.menuItems.size() > 0) {
+			// Reset Filter
+			menu.menuItems.add(new DropDownMenuItem(getString("gb.reset"), null, null));
+		}
+
+		pages.add(menu);
+	}
+}
diff --git a/src/main/java/com/gitblit/wicket/pages/RawPage.java b/src/main/java/com/gitblit/wicket/pages/RawPage.java
new file mode 100644
index 0000000..27a01f9
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/RawPage.java
@@ -0,0 +1,173 @@
+/*
+ * 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.wicket.pages;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.wicket.IRequestTarget;
+import org.apache.wicket.PageParameters;
+import org.apache.wicket.RequestCycle;
+import org.apache.wicket.protocol.http.WebResponse;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.gitblit.GitBlit;
+import com.gitblit.Keys;
+import com.gitblit.models.RepositoryModel;
+import com.gitblit.models.UserModel;
+import com.gitblit.utils.JGitUtils;
+import com.gitblit.utils.StringUtils;
+import com.gitblit.wicket.GitBlitWebSession;
+import com.gitblit.wicket.WicketUtils;
+
+public class RawPage extends SessionPage {
+
+	private final Logger logger = LoggerFactory.getLogger(getClass().getSimpleName());
+
+	public RawPage(final PageParameters params) {
+		super(params);
+		
+		if (!params.containsKey("r")) {
+			error(getString("gb.repositoryNotSpecified"));
+			redirectToInterceptPage(new RepositoriesPage());
+		}
+
+		getRequestCycle().setRequestTarget(new IRequestTarget() {
+			@Override
+			public void detach(RequestCycle requestCycle) {
+			}
+
+			@Override
+			public void respond(RequestCycle requestCycle) {
+				WebResponse response = (WebResponse) requestCycle.getResponse();
+
+				final String repositoryName = WicketUtils.getRepositoryName(params);
+				final String objectId = WicketUtils.getObject(params);
+				final String blobPath = WicketUtils.getPath(params);
+				String[] encodings = GitBlit.getEncodings();
+				GitBlitWebSession session = GitBlitWebSession.get();
+				UserModel user = session.getUser();
+				
+				RepositoryModel model = GitBlit.self().getRepositoryModel(user, repositoryName);
+				if (model == null) {
+					// user does not have permission
+					error(getString("gb.canNotLoadRepository") + " " + repositoryName);
+					redirectToInterceptPage(new RepositoriesPage());
+					return;
+				}
+				
+				Repository r = GitBlit.self().getRepository(repositoryName);
+				if (r == null) {
+					error(getString("gb.canNotLoadRepository") + " " + repositoryName);
+					redirectToInterceptPage(new RepositoriesPage());
+					return;
+				}
+
+				if (StringUtils.isEmpty(blobPath)) {
+					// objectid referenced raw view
+					byte [] binary = JGitUtils.getByteContent(r, objectId);
+					response.setContentType("application/octet-stream");
+					response.setContentLength(binary.length);
+					try {
+						response.getOutputStream().write(binary);
+					} catch (Exception e) {
+						logger.error("Failed to write binary response", e);
+					}
+				} else {
+					// standard raw blob view
+					RevCommit commit = JGitUtils.getCommit(r, objectId);
+
+					String filename = blobPath;
+					if (blobPath.indexOf('/') > -1) {
+						filename = blobPath.substring(blobPath.lastIndexOf('/') + 1);
+					}
+
+					String extension = null;
+					if (blobPath.lastIndexOf('.') > -1) {
+						extension = blobPath.substring(blobPath.lastIndexOf('.') + 1);
+					}
+
+					// Map the extensions to types
+					Map<String, Integer> map = new HashMap<String, Integer>();
+					for (String ext : GitBlit.getStrings(Keys.web.imageExtensions)) {
+						map.put(ext.toLowerCase(), 2);
+					}
+					for (String ext : GitBlit.getStrings(Keys.web.binaryExtensions)) {
+						map.put(ext.toLowerCase(), 3);
+					}
+
+					if (extension != null) {
+						int type = 0;
+						if (map.containsKey(extension)) {
+							type = map.get(extension);
+						}
+						switch (type) {
+						case 2:
+							// image blobs
+							byte[] image = JGitUtils.getByteContent(r, commit.getTree(), blobPath, true);
+							response.setContentType("image/" + extension.toLowerCase());
+							response.setContentLength(image.length);
+							try {
+								response.getOutputStream().write(image);
+							} catch (IOException e) {
+								logger.error("Failed to write image response", e);
+							}
+							break;
+						case 3:
+							// binary blobs (download)
+							byte[] binary = JGitUtils.getByteContent(r, commit.getTree(), blobPath, true);
+							response.setContentLength(binary.length);
+							response.setContentType("application/octet-stream");
+							response.setHeader("Content-Disposition", "attachment; filename=\"" + filename + "\"");
+							try {
+								response.getOutputStream().write(binary);
+							} catch (IOException e) {
+								logger.error("Failed to write binary response", e);
+							}
+							break;
+						default:
+							// plain text
+							String content = JGitUtils.getStringContent(r, commit.getTree(),
+									blobPath, encodings);
+							response.setContentType("text/plain; charset=UTF-8");
+							try {
+								response.getOutputStream().write(content.getBytes("UTF-8"));
+							} catch (Exception e) {
+								logger.error("Failed to write text response", e);
+							}
+						}
+
+					} else {
+						// plain text
+						String content = JGitUtils.getStringContent(r, commit.getTree(), blobPath,
+								encodings);
+						response.setContentType("text/plain; charset=UTF-8");
+						try {
+							response.getOutputStream().write(content.getBytes("UTF-8"));
+						} catch (Exception e) {
+							logger.error("Failed to write text response", e);
+						}
+					}
+				}
+				r.close();
+			}
+		});
+	}
+}
diff --git a/src/main/java/com/gitblit/wicket/pages/ReflogPage.html b/src/main/java/com/gitblit/wicket/pages/ReflogPage.html
new file mode 100644
index 0000000..c0ac7eb
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/ReflogPage.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"  
+      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"  
+      xml:lang="en"  
+      lang="en"> 
+
+<body>
+<wicket:extend>
+
+	<!-- pager links -->
+	<div class="page_nav2">
+		<a wicket:id="firstPage"><wicket:message key="gb.pageFirst"></wicket:message></a> | <a wicket:id="prevPage">&laquo; <wicket:message key="gb.pagePrevious"></wicket:message></a> | <a wicket:id="nextPage"><wicket:message key="gb.pageNext"></wicket:message> &raquo;</a> 
+	</div>
+	
+	<!-- ref log -->
+	<div style="margin-top:5px;" wicket:id="reflogPanel">[reflog panel]</div>
+
+	<!-- pager links -->
+	<div style="padding-bottom:5px;">
+		<a wicket:id="firstPage"><wicket:message key="gb.pageFirst"></wicket:message></a> | <a wicket:id="prevPage">&laquo; <wicket:message key="gb.pagePrevious"></wicket:message></a> | <a wicket:id="nextPage"><wicket:message key="gb.pageNext"></wicket:message> &raquo;</a> 
+	</div>
+	
+</wicket:extend>
+</body>
+</html>
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/pages/ReflogPage.java b/src/main/java/com/gitblit/wicket/pages/ReflogPage.java
new file mode 100644
index 0000000..c97b2cc
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/ReflogPage.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2013 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit.wicket.pages;
+
+import org.apache.wicket.PageParameters;
+import org.apache.wicket.markup.html.link.BookmarkablePageLink;
+
+import com.gitblit.wicket.CacheControl;
+import com.gitblit.wicket.WicketUtils;
+import com.gitblit.wicket.CacheControl.LastModified;
+import com.gitblit.wicket.panels.ReflogPanel;
+
+@CacheControl(LastModified.REPOSITORY)
+public class ReflogPage extends RepositoryPage {
+
+	public ReflogPage(PageParameters params) {
+		super(params);
+
+		addSyndicationDiscoveryLink();
+
+		int pageNumber = WicketUtils.getPage(params);
+		int prevPage = Math.max(0, pageNumber - 1);
+		int nextPage = pageNumber + 1;
+
+		ReflogPanel reflogPanel = new ReflogPanel("reflogPanel", getRepositoryModel(), getRepository(), -1,
+				pageNumber - 1);
+		boolean hasMore = reflogPanel.hasMore();
+		add(reflogPanel);
+
+		add(new BookmarkablePageLink<Void>("firstPage", ReflogPage.class,
+				WicketUtils.newObjectParameter(repositoryName, objectId))
+				.setEnabled(pageNumber > 1));
+		add(new BookmarkablePageLink<Void>("prevPage", ReflogPage.class,
+				WicketUtils.newLogPageParameter(repositoryName, objectId, prevPage))
+				.setEnabled(pageNumber > 1));
+		add(new BookmarkablePageLink<Void>("nextPage", ReflogPage.class,
+				WicketUtils.newLogPageParameter(repositoryName, objectId, nextPage))
+				.setEnabled(hasMore));
+	}
+
+	@Override
+	protected String getPageName() {
+		return getString("gb.reflog");
+	}
+}
diff --git a/src/main/java/com/gitblit/wicket/pages/RepositoriesPage.html b/src/main/java/com/gitblit/wicket/pages/RepositoriesPage.html
new file mode 100644
index 0000000..6ef274b
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/RepositoriesPage.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"  
+      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"  
+      xml:lang="en"  
+      lang="en"> 
+
+<body>
+<wicket:extend>
+<div class="container">
+	<div class="markdown" style="padding: 10px 0px 5px 0px;" wicket:id="repositoriesMessage">[repositories message]</div>
+	
+	<div wicket:id="repositoriesPanel">[repositories panel]</div>
+</div>
+</wicket:extend>
+</body>
+</html>
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/pages/RepositoriesPage.java b/src/main/java/com/gitblit/wicket/pages/RepositoriesPage.java
new file mode 100644
index 0000000..a4a1ab5
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/RepositoriesPage.java
@@ -0,0 +1,187 @@
+/*
+ * 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.wicket.pages;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.wicket.Component;
+import org.apache.wicket.PageParameters;
+import org.apache.wicket.markup.html.basic.Label;
+import org.eclipse.jgit.lib.Constants;
+
+import com.gitblit.GitBlit;
+import com.gitblit.Keys;
+import com.gitblit.models.RepositoryModel;
+import com.gitblit.utils.MarkdownUtils;
+import com.gitblit.utils.StringUtils;
+import com.gitblit.wicket.CacheControl;
+import com.gitblit.wicket.GitBlitWebSession;
+import com.gitblit.wicket.PageRegistration;
+import com.gitblit.wicket.CacheControl.LastModified;
+import com.gitblit.wicket.PageRegistration.DropDownMenuItem;
+import com.gitblit.wicket.PageRegistration.DropDownMenuRegistration;
+import com.gitblit.wicket.WicketUtils;
+import com.gitblit.wicket.panels.RepositoriesPanel;
+
+@CacheControl(LastModified.ACTIVITY)
+public class RepositoriesPage extends RootPage {
+
+	public RepositoriesPage() {
+		super();
+		setup(null);
+	}
+
+	public RepositoriesPage(PageParameters params) {
+		super(params);
+		setup(params);
+	}
+
+	@Override
+	protected boolean reusePageParameters() {
+		return true;
+	}
+
+	private void setup(PageParameters params) {
+		setupPage("", "");
+		// check to see if we should display a login message
+		boolean authenticateView = GitBlit.getBoolean(Keys.web.authenticateViewPages, true);
+		if (authenticateView && !GitBlitWebSession.get().isLoggedIn()) {
+			String messageSource = GitBlit.getString(Keys.web.loginMessage, "gitblit");
+			String message = readMarkdown(messageSource, "login.mkd");
+			Component repositoriesMessage = new Label("repositoriesMessage", message);
+			add(repositoriesMessage.setEscapeModelStrings(false));
+			add(new Label("repositoriesPanel"));
+			return;
+		}
+
+		// Load the markdown welcome message
+		String messageSource = GitBlit.getString(Keys.web.repositoriesMessage, "gitblit");
+		String message = readMarkdown(messageSource, "welcome.mkd");
+		Component repositoriesMessage = new Label("repositoriesMessage", message)
+				.setEscapeModelStrings(false).setVisible(message.length() > 0);
+		add(repositoriesMessage);
+
+		List<RepositoryModel> repositories = getRepositories(params);
+
+		RepositoriesPanel repositoriesPanel = new RepositoriesPanel("repositoriesPanel", showAdmin,
+				true, repositories, true, getAccessRestrictions());
+		// push the panel down if we are hiding the admin controls and the
+		// welcome message
+		if (!showAdmin && !repositoriesMessage.isVisible()) {
+			WicketUtils.setCssStyle(repositoriesPanel, "padding-top:5px;");
+		}
+		add(repositoriesPanel);
+	}
+
+	@Override
+	protected void addDropDownMenus(List<PageRegistration> pages) {
+		PageParameters params = getPageParameters();
+
+		DropDownMenuRegistration menu = new DropDownMenuRegistration("gb.filters",
+				RepositoriesPage.class);
+		// preserve time filter option on repository choices
+		menu.menuItems.addAll(getRepositoryFilterItems(params));
+
+		// preserve repository filter option on time choices
+		menu.menuItems.addAll(getTimeFilterItems(params));
+
+		if (menu.menuItems.size() > 0) {
+			// Reset Filter
+			menu.menuItems.add(new DropDownMenuItem(getString("gb.reset"), null, null));
+		}
+
+		pages.add(menu);
+	}
+
+	private String readMarkdown(String messageSource, String resource) {
+		String message = "";
+		if (messageSource.equalsIgnoreCase("gitblit")) {
+			// Read default message
+			message = readDefaultMarkdown(resource);
+		} else {
+			// Read user-supplied message
+			if (!StringUtils.isEmpty(messageSource)) {
+				File file = GitBlit.getFileOrFolder(messageSource);
+				if (file.exists()) {
+					try {
+						FileInputStream fis = new FileInputStream(file);
+						InputStreamReader reader = new InputStreamReader(fis,
+								Constants.CHARACTER_ENCODING);
+						message = MarkdownUtils.transformMarkdown(reader);
+						reader.close();
+					} catch (Throwable t) {
+						message = getString("gb.failedToRead") + " " + file;
+						warn(message, t);
+					}
+				} else {
+					message = messageSource + " " + getString("gb.isNotValidFile");
+				}
+			}
+		}
+		return message;
+	}
+
+	private String readDefaultMarkdown(String file) {
+		String base = file.substring(0, file.lastIndexOf('.'));
+		String ext = file.substring(file.lastIndexOf('.'));
+		String lc = getLanguageCode();
+		String cc = getCountryCode();
+
+		// try to read file_en-us.ext, file_en.ext, file.ext
+		List<String> files = new ArrayList<String>();
+		if (!StringUtils.isEmpty(lc)) {
+			if (!StringUtils.isEmpty(cc)) {
+				files.add(base + "_" + lc + "-" + cc + ext);
+				files.add(base + "_" + lc + "_" + cc + ext);
+			}
+			files.add(base + "_" + lc + ext);
+		}
+		files.add(file);
+
+		for (String name : files) {
+			String message;
+			InputStreamReader reader = null;
+			try {
+				InputStream is = getClass().getResourceAsStream("/" + name);
+				if (is == null) {
+					continue;
+				}
+				reader = new InputStreamReader(is, Constants.CHARACTER_ENCODING);
+				message = MarkdownUtils.transformMarkdown(reader);
+				reader.close();
+				return message;
+			} catch (Throwable t) {
+				message = MessageFormat.format(getString("gb.failedToReadMessage"), file);
+				error(message, t, false);
+				return message;
+			} finally {
+				if (reader != null) {
+					try {
+						reader.close();
+					} catch (Exception e) {
+					}
+				}
+			}			
+		}
+		return MessageFormat.format(getString("gb.failedToReadMessage"), file);
+	}
+}
diff --git a/src/main/java/com/gitblit/wicket/pages/RepositoryPage.html b/src/main/java/com/gitblit/wicket/pages/RepositoryPage.html
new file mode 100644
index 0000000..1af9127
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/RepositoryPage.html
@@ -0,0 +1,68 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"  
+      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"  
+      xml:lang="en"  
+      lang="en"> 
+
+<body>
+	<wicket:extend>
+		<div class="repositorynavbar">
+			<div class="repositorynavbar-inner">
+				<div class="container">
+		
+					<div class="title">
+						<div class="row">
+							<!-- search form -->
+							<div class="hidden-phone pull-right">
+								<form class="form-search" style="margin: 0px;" wicket:id="searchForm">
+									<div class="input-append">
+										<select class="span2" style="border-radius: 4px;" wicket:id="searchType"/>
+										<input type="text" class="search-query" style="width: 170px;border-radius: 14px 0 0 14px; padding-left: 14px;" id="searchBox" wicket:id="searchBox" value=""/>
+										<button class="btn" style="border-radius: 0 14px 14px 0px;margin-left:-5px;" type="submit"><i class="icon-search"></i></button>
+									</div>
+								</form>
+							</div>
+							
+							<div class="span7">
+								<div>
+									<span class="project" wicket:id="projectTitle">[project title]</span>/<span class="repository" wicket:id="repositoryName">[repository name]</span>
+									<a class="hidden-phone hidden-tablet" style="text-decoration: none;" wicket:id="syndication" wicket:message="title:gb.feed">
+										<img style="border:0px;vertical-align:baseline;" src="feed_16x16.png"></img>
+									</a>
+								</div>
+								<span wicket:id="originRepository">[origin repository]</span>								
+							</div>
+						</div>
+					</div>
+			
+					<div>
+						<div class="hidden-phone btn-group pull-right" style="margin-top:5px;">
+							<!-- future spot for other repo buttons -->
+							<a class="btn" wicket:id="starLink"></a>
+							<a class="btn" wicket:id="unstarLink"></a>
+							<a class="btn" wicket:id="myForkLink"><img style="border:0px;vertical-align:middle;" src="fork-black_16x16.png"></img> <wicket:message key="gb.myFork"></wicket:message></a>
+							<a class="btn" wicket:id="forkLink"><img style="border:0px;vertical-align:middle;" src="fork-black_16x16.png"></img> <wicket:message key="gb.fork"></wicket:message></a>
+							<a class="btn" wicket:id="editLink"><i class="icon-cog"></i> <wicket:message key="gb.edit"></wicket:message></a>
+						</div>
+						
+						<div wicket:id="repositoryNavPanel"></div>
+					</div>
+				</div>
+			</div>
+		</div>
+				
+		<div class="container">
+			<wicket:child />
+		</div>
+		
+		<wicket:fragment wicket:id="toolbarLinkFragment">
+			<i wicket:id="icon" style="padding-right:3px;"></i><span wicket:id="label"></span>
+		</wicket:fragment>
+		
+		<wicket:fragment wicket:id="originFragment">
+			<p class="originRepository"><wicket:message key="gb.forkedFrom">[forked from]</wicket:message> <span wicket:id="originRepository">[origin repository]</span></p>
+		</wicket:fragment>
+				
+	</wicket:extend>
+</body>
+</html>
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/pages/RepositoryPage.java b/src/main/java/com/gitblit/wicket/pages/RepositoryPage.java
new file mode 100644
index 0000000..f5b8c96
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/RepositoryPage.java
@@ -0,0 +1,685 @@
+/*
+ * 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.wicket.pages;
+
+import java.io.Serializable;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.wicket.Component;
+import org.apache.wicket.PageParameters;
+import org.apache.wicket.behavior.SimpleAttributeModifier;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.form.DropDownChoice;
+import org.apache.wicket.markup.html.form.TextField;
+import org.apache.wicket.markup.html.link.ExternalLink;
+import org.apache.wicket.markup.html.panel.Fragment;
+import org.apache.wicket.model.IModel;
+import org.apache.wicket.model.Model;
+import org.apache.wicket.protocol.http.RequestUtils;
+import org.apache.wicket.request.target.basic.RedirectRequestTarget;
+import org.eclipse.jgit.diff.DiffEntry.ChangeType;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.gitblit.Constants;
+import com.gitblit.GitBlit;
+import com.gitblit.GitBlitException;
+import com.gitblit.Keys;
+import com.gitblit.PagesServlet;
+import com.gitblit.SyndicationServlet;
+import com.gitblit.models.ProjectModel;
+import com.gitblit.models.RefModel;
+import com.gitblit.models.RepositoryModel;
+import com.gitblit.models.SubmoduleModel;
+import com.gitblit.models.UserModel;
+import com.gitblit.models.UserRepositoryPreferences;
+import com.gitblit.utils.ArrayUtils;
+import com.gitblit.utils.DeepCopier;
+import com.gitblit.utils.JGitUtils;
+import com.gitblit.utils.RefLogUtils;
+import com.gitblit.utils.StringUtils;
+import com.gitblit.utils.TicgitUtils;
+import com.gitblit.wicket.CacheControl;
+import com.gitblit.wicket.GitBlitWebSession;
+import com.gitblit.wicket.PageRegistration;
+import com.gitblit.wicket.PageRegistration.OtherPageLink;
+import com.gitblit.wicket.SessionlessForm;
+import com.gitblit.wicket.WicketUtils;
+import com.gitblit.wicket.panels.LinkPanel;
+import com.gitblit.wicket.panels.NavigationPanel;
+import com.gitblit.wicket.panels.RefsPanel;
+
+public abstract class RepositoryPage extends RootPage {
+	
+	private final Logger logger = LoggerFactory.getLogger(getClass());
+
+	private final String PARAM_STAR = "star";
+	
+	protected final String projectName;
+	protected final String repositoryName;
+	protected final String objectId;
+	
+	private transient Repository r;
+
+	private RepositoryModel m;
+
+	private Map<String, SubmoduleModel> submodules;
+	
+	private final Map<String, PageRegistration> registeredPages;
+	private boolean showAdmin;
+	private boolean isOwner;
+	
+	public RepositoryPage(PageParameters params) {
+		super(params);
+		repositoryName = WicketUtils.getRepositoryName(params);
+		String root =StringUtils.getFirstPathElement(repositoryName);
+		if (StringUtils.isEmpty(root)) {
+			projectName = GitBlit.getString(Keys.web.repositoryRootGroupName, "main");
+		} else {
+			projectName = root;
+		}
+		objectId = WicketUtils.getObject(params);
+		
+		if (StringUtils.isEmpty(repositoryName)) {
+			error(MessageFormat.format(getString("gb.repositoryNotSpecifiedFor"), getPageName()), true);
+		}
+
+		if (!getRepositoryModel().hasCommits) {
+			setResponsePage(EmptyRepositoryPage.class, params);
+		}
+		
+		if (getRepositoryModel().isCollectingGarbage) {
+			error(MessageFormat.format(getString("gb.busyCollectingGarbage"), getRepositoryModel().name), true);
+		}
+		
+		if (objectId != null) {
+			RefModel branch = null;
+			if ((branch = JGitUtils.getBranch(getRepository(), objectId)) != null) {
+				UserModel user = GitBlitWebSession.get().getUser();
+				if (user == null) {
+					// workaround until get().getUser() is reviewed throughout the app
+					user = UserModel.ANONYMOUS;
+				}
+				boolean canAccess = user.canView(getRepositoryModel(),
+								branch.reference.getName());
+				if (!canAccess) {
+					error(getString("gb.accessDenied"), true);
+				}
+			}
+		}
+		
+		if (params.containsKey(PARAM_STAR)) {
+			// set starred state
+			boolean star = params.getBoolean(PARAM_STAR);
+			UserModel user = GitBlitWebSession.get().getUser();
+			if (user != null && user.isAuthenticated) {
+				UserRepositoryPreferences prefs = user.getPreferences().getRepositoryPreferences(getRepositoryModel().name);
+				prefs.starred = star;
+				try {
+					GitBlit.self().updateUserModel(user.username, user, false);
+				} catch (GitBlitException e) {
+					logger.error("Failed to update user " + user.username, e);
+					error(getString("gb.failedToUpdateUser"), false);
+				}
+			}
+		}
+
+		// register the available page links for this page and user
+		registeredPages = registerPages();
+
+		// standard page links
+		List<PageRegistration> pages = new ArrayList<PageRegistration>(registeredPages.values());
+		NavigationPanel navigationPanel = new NavigationPanel("repositoryNavPanel", getRepoNavPageClass(), pages);
+		add(navigationPanel);
+
+		add(new ExternalLink("syndication", SyndicationServlet.asLink(getRequest()
+				.getRelativePathPrefixToContextRoot(), repositoryName, null, 0)));
+
+		// add floating search form
+		SearchForm searchForm = new SearchForm("searchForm", repositoryName);
+		add(searchForm);
+		searchForm.setTranslatedAttributes();
+
+		// set stateless page preference
+		setStatelessHint(true);
+	}
+	
+	@Override
+	protected Class<? extends BasePage> getRootNavPageClass() {
+		return RepositoriesPage.class;
+	}
+
+	protected Class<? extends BasePage> getRepoNavPageClass() {
+		return getClass();
+	}
+	
+	private Map<String, PageRegistration> registerPages() {
+		PageParameters params = null;
+		if (!StringUtils.isEmpty(repositoryName)) {
+			params = WicketUtils.newRepositoryParameter(repositoryName);
+		}
+		Map<String, PageRegistration> pages = new LinkedHashMap<String, PageRegistration>();
+
+		Repository r = getRepository();
+		RepositoryModel model = getRepositoryModel();
+
+		// standard links
+		if (RefLogUtils.getRefLogBranch(r) == null) {
+			pages.put("summary", new PageRegistration("gb.summary", SummaryPage.class, params));
+		} else {
+			pages.put("summary", new PageRegistration("gb.summary", SummaryPage.class, params));
+//			pages.put("overview", new PageRegistration("gb.overview", OverviewPage.class, params));
+			pages.put("reflog", new PageRegistration("gb.reflog", ReflogPage.class, params));
+		}		
+		pages.put("commits", new PageRegistration("gb.commits", LogPage.class, params));
+		pages.put("tree", new PageRegistration("gb.tree", TreePage.class, params));
+		pages.put("compare", new PageRegistration("gb.compare", ComparePage.class, params, true));
+		if (GitBlit.getBoolean(Keys.web.allowForking, true)) {
+			pages.put("forks", new PageRegistration("gb.forks", ForksPage.class, params, true));
+		}
+
+		// conditional links
+		// per-repository extra page links
+		if (model.useTickets && TicgitUtils.getTicketsBranch(r) != null) {
+			pages.put("tickets", new PageRegistration("gb.tickets", TicketsPage.class, params, true));
+		}
+		if (model.showReadme || model.useDocs) {
+			pages.put("docs", new PageRegistration("gb.docs", DocsPage.class, params, true));
+		}
+		if (JGitUtils.getPagesBranch(r) != null) {
+			OtherPageLink pagesLink = new OtherPageLink("gb.pages", PagesServlet.asLink(
+					getRequest().getRelativePathPrefixToContextRoot(), repositoryName, null), true);
+			pages.put("pages", pagesLink);
+		}
+
+		// Conditionally add edit link
+		showAdmin = false;
+		if (GitBlit.getBoolean(Keys.web.authenticateAdminPages, true)) {
+			boolean allowAdmin = GitBlit.getBoolean(Keys.web.allowAdministration, false);
+			showAdmin = allowAdmin && GitBlitWebSession.get().canAdmin();
+		} else {
+			showAdmin = GitBlit.getBoolean(Keys.web.allowAdministration, false);
+		}
+		isOwner = GitBlitWebSession.get().isLoggedIn()
+				&& (model.isOwner(GitBlitWebSession.get()
+						.getUsername()));
+		return pages;
+	}
+	
+	protected boolean allowForkControls() {
+		return GitBlit.getBoolean(Keys.web.allowForking, true);
+	}
+
+	@Override
+	protected void setupPage(String repositoryName, String pageName) {
+		String projectName = StringUtils.getFirstPathElement(repositoryName);
+		ProjectModel project = GitBlit.self().getProjectModel(projectName);
+		if (project.isUserProject()) {
+			// user-as-project
+			add(new LinkPanel("projectTitle", null, project.getDisplayName(),
+					UserPage.class, WicketUtils.newUsernameParameter(project.name.substring(1))));
+		} else {
+			// project
+			add(new LinkPanel("projectTitle", null, project.name,
+					ProjectPage.class, WicketUtils.newProjectParameter(project.name)));
+		}
+		
+		String name = StringUtils.stripDotGit(repositoryName);
+		if (!StringUtils.isEmpty(projectName) && name.startsWith(projectName)) {
+			name = name.substring(projectName.length() + 1);
+		}
+		add(new LinkPanel("repositoryName", null, name, SummaryPage.class,
+				WicketUtils.newRepositoryParameter(repositoryName)));
+		add(new Label("pageName", pageName).setRenderBodyOnly(true));
+		
+		UserModel user = GitBlitWebSession.get().getUser();
+		if (user == null) {
+			user = UserModel.ANONYMOUS;
+		}
+
+		// indicate origin repository
+		RepositoryModel model = getRepositoryModel();
+		if (StringUtils.isEmpty(model.originRepository)) {
+			add(new Label("originRepository").setVisible(false));
+		} else {
+			RepositoryModel origin = GitBlit.self().getRepositoryModel(model.originRepository);
+			if (origin == null) {
+				// no origin repository
+				add(new Label("originRepository").setVisible(false));
+			} else if (!user.canView(origin)) {
+				// show origin repository without link
+				Fragment forkFrag = new Fragment("originRepository", "originFragment", this);
+				forkFrag.add(new Label("originRepository", StringUtils.stripDotGit(model.originRepository)));
+				add(forkFrag);
+			} else {
+				// link to origin repository
+				Fragment forkFrag = new Fragment("originRepository", "originFragment", this);
+				forkFrag.add(new LinkPanel("originRepository", null, StringUtils.stripDotGit(model.originRepository), 
+						SummaryPage.class, WicketUtils.newRepositoryParameter(model.originRepository)));
+				add(forkFrag);
+			}
+		}
+		
+		// (un)star link allows a user to star a repository
+		if (user.isAuthenticated) {
+			PageParameters starParams = DeepCopier.copy(getPageParameters());
+			starParams.put(PARAM_STAR, !user.getPreferences().isStarredRepository(model.name));
+			String toggleStarUrl = getRequestCycle().urlFor(getClass(), starParams).toString();
+			if (user.getPreferences().isStarredRepository(model.name)) {
+				// show unstar button
+				add(new Label("starLink").setVisible(false));
+				addToolbarButton("unstarLink", "icon-star-empty", getString("gb.unstar"), toggleStarUrl);
+			} else {
+				// show star button
+				addToolbarButton("starLink", "icon-star", getString("gb.star"), toggleStarUrl);
+				add(new Label("unstarLink").setVisible(false));
+			}
+		} else {
+			// anonymous user
+			add(new Label("starLink").setVisible(false));
+			add(new Label("unstarLink").setVisible(false));
+		}
+
+		// fork controls
+		if (!allowForkControls() || user == null || !user.isAuthenticated) {
+			// must be logged-in to fork, hide all fork controls
+			add(new ExternalLink("forkLink", "").setVisible(false));
+			add(new ExternalLink("myForkLink", "").setVisible(false));
+		} else {
+			String fork = GitBlit.self().getFork(user.username, model.name);
+			boolean hasFork = fork != null;
+			boolean canFork = user.canFork(model);
+
+			if (hasFork || !canFork) {
+				// user not allowed to fork or fork already exists or repo forbids forking
+				add(new ExternalLink("forkLink", "").setVisible(false));
+				
+				if (hasFork && !fork.equals(model.name)) {
+					// user has fork, view my fork link
+					String url = getRequestCycle().urlFor(SummaryPage.class, WicketUtils.newRepositoryParameter(fork)).toString();
+					add(new ExternalLink("myForkLink", url));
+				} else {
+					// no fork, hide view my fork link
+					add(new ExternalLink("myForkLink", "").setVisible(false));
+				}
+			} else if (canFork) {
+				// can fork and we do not have one
+				add(new ExternalLink("myForkLink", "").setVisible(false));
+				String url = getRequestCycle().urlFor(ForkPage.class, WicketUtils.newRepositoryParameter(model.name)).toString();
+				add(new ExternalLink("forkLink", url));
+			}
+		}
+		
+		if (showAdmin || isOwner) {
+			String url = getRequestCycle().urlFor(EditRepositoryPage.class, WicketUtils.newRepositoryParameter(model.name)).toString();
+			add(new ExternalLink("editLink", url)); 
+		} else {
+			add(new Label("editLink").setVisible(false));
+		}
+		
+		super.setupPage(repositoryName, pageName);
+	}
+	
+	protected void addToolbarButton(String wicketId, String iconClass, String label, String url) {
+		Fragment button = new Fragment(wicketId, "toolbarLinkFragment", this);
+		Label icon = new Label("icon");
+		WicketUtils.setCssClass(icon, iconClass);
+		button.add(icon);
+		button.add(new Label("label", label));
+		button.add(new SimpleAttributeModifier("href", url));
+		add(button);
+	}
+
+	protected void addSyndicationDiscoveryLink() {
+		add(WicketUtils.syndicationDiscoveryLink(SyndicationServlet.getTitle(repositoryName,
+				objectId), SyndicationServlet.asLink(getRequest()
+				.getRelativePathPrefixToContextRoot(), repositoryName, objectId, 0)));
+	}
+
+	protected Repository getRepository() {
+		if (r == null) {
+			Repository r = GitBlit.self().getRepository(repositoryName);
+			if (r == null) {
+				error(getString("gb.canNotLoadRepository") + " " + repositoryName, true);
+				return null;
+			}
+			this.r = r;
+		}
+		return r;
+	}
+
+	protected RepositoryModel getRepositoryModel() {
+		if (m == null) {
+			RepositoryModel model = GitBlit.self().getRepositoryModel(
+					GitBlitWebSession.get().getUser(), repositoryName);
+			if (model == null) {
+				if (GitBlit.self().hasRepository(repositoryName, true)) {
+					// has repository, but unauthorized
+					authenticationError(getString("gb.unauthorizedAccessForRepository") + " " + repositoryName);
+				} else {
+					// does not have repository
+					error(getString("gb.canNotLoadRepository") + " " + repositoryName, true);
+				}
+				return null;
+			}
+			m = model;
+		}
+		return m;
+	}
+
+	protected RevCommit getCommit() {
+		RevCommit commit = JGitUtils.getCommit(r, objectId);
+		if (commit == null) {
+			error(MessageFormat.format(getString("gb.failedToFindCommit"),
+					objectId, repositoryName, getPageName()), null, LogPage.class,
+					WicketUtils.newRepositoryParameter(repositoryName));
+		}
+		getSubmodules(commit);
+		return commit;
+	}
+	
+	protected Map<String, SubmoduleModel> getSubmodules(RevCommit commit) {	
+		if (submodules == null) {
+			submodules = new HashMap<String, SubmoduleModel>();
+			for (SubmoduleModel model : JGitUtils.getSubmodules(r, commit.getTree())) {
+				submodules.put(model.path, model);
+			}
+		}
+		return submodules;
+	}
+	
+	protected SubmoduleModel getSubmodule(String path) {
+		SubmoduleModel model = null;
+		if (submodules != null) {
+			model = submodules.get(path);
+		}
+		if (model == null) {
+			// undefined submodule?!
+			model = new SubmoduleModel(path.substring(path.lastIndexOf('/') + 1), path, path);
+			model.hasSubmodule = false;
+			model.gitblitPath = model.name;
+			return model;
+		} else {
+			// extract the repository name from the clone url
+			List<String> patterns = GitBlit.getStrings(Keys.git.submoduleUrlPatterns);
+			String submoduleName = StringUtils.extractRepositoryPath(model.url, patterns.toArray(new String[0]));
+			
+			// determine the current path for constructing paths relative
+			// to the current repository
+			String currentPath = "";
+			if (repositoryName.indexOf('/') > -1) {
+				currentPath = repositoryName.substring(0, repositoryName.lastIndexOf('/') + 1);
+			}
+
+			// try to locate the submodule repository
+			// prefer bare to non-bare names
+			List<String> candidates = new ArrayList<String>();
+
+			// relative
+			candidates.add(currentPath + StringUtils.stripDotGit(submoduleName));
+			candidates.add(candidates.get(candidates.size() - 1) + ".git");
+
+			// relative, no subfolder
+			if (submoduleName.lastIndexOf('/') > -1) {
+				String name = submoduleName.substring(submoduleName.lastIndexOf('/') + 1);
+				candidates.add(currentPath + StringUtils.stripDotGit(name));
+				candidates.add(candidates.get(candidates.size() - 1) + ".git");
+			}
+
+			// absolute
+			candidates.add(StringUtils.stripDotGit(submoduleName));
+			candidates.add(candidates.get(candidates.size() - 1) + ".git");
+
+			// absolute, no subfolder
+			if (submoduleName.lastIndexOf('/') > -1) {
+				String name = submoduleName.substring(submoduleName.lastIndexOf('/') + 1);
+				candidates.add(StringUtils.stripDotGit(name));
+				candidates.add(candidates.get(candidates.size() - 1) + ".git");
+			}
+
+			// create a unique, ordered set of candidate paths
+			Set<String> paths = new LinkedHashSet<String>(candidates);
+			for (String candidate : paths) {
+				if (GitBlit.self().hasRepository(candidate)) {
+					model.hasSubmodule = true;
+					model.gitblitPath = candidate;
+					return model;
+				}
+			}
+			
+			// we do not have a copy of the submodule, but we need a path
+			model.gitblitPath = candidates.get(0);
+			return model;
+		}		
+	}
+
+	protected String getShortObjectId(String objectId) {
+		return objectId.substring(0, GitBlit.getInteger(Keys.web.shortCommitIdLength, 6));
+	}
+
+	protected void addRefs(Repository r, RevCommit c) {
+		add(new RefsPanel("refsPanel", repositoryName, c, JGitUtils.getAllRefs(r, getRepositoryModel().showRemoteBranches)));
+	}
+
+	protected void addFullText(String wicketId, String text, boolean substituteRegex) {
+		String html = StringUtils.escapeForHtml(text, false);
+		if (substituteRegex) {
+			html = GitBlit.self().processCommitMessage(repositoryName, html);
+		} else {
+			html = StringUtils.breakLinesForHtml(html);
+		}
+		add(new Label(wicketId, html).setEscapeModelStrings(false));
+	}
+
+	protected abstract String getPageName();
+
+	protected Component createPersonPanel(String wicketId, PersonIdent identity,
+			Constants.SearchType searchType) {
+		String name = identity == null ? "" : identity.getName();
+		String address = identity == null ? "" : identity.getEmailAddress();
+		name = StringUtils.removeNewlines(name);
+		address = StringUtils.removeNewlines(address);
+		boolean showEmail = GitBlit.getBoolean(Keys.web.showEmailAddresses, false);
+		if (!showEmail || StringUtils.isEmpty(name) || StringUtils.isEmpty(address)) {
+			String value = name;
+			if (StringUtils.isEmpty(value)) {
+				if (showEmail) {
+					value = address;
+				} else {
+					value = getString("gb.missingUsername");
+				}
+			}
+			Fragment partial = new Fragment(wicketId, "partialPersonIdent", this);
+			LinkPanel link = new LinkPanel("personName", "list", value, GitSearchPage.class,
+					WicketUtils.newSearchParameter(repositoryName, objectId, value, searchType));
+			setPersonSearchTooltip(link, value, searchType);
+			partial.add(link);
+			return partial;
+		} else {
+			Fragment fullPerson = new Fragment(wicketId, "fullPersonIdent", this);
+			LinkPanel nameLink = new LinkPanel("personName", "list", name, GitSearchPage.class,
+					WicketUtils.newSearchParameter(repositoryName, objectId, name, searchType));
+			setPersonSearchTooltip(nameLink, name, searchType);
+			fullPerson.add(nameLink);
+
+			LinkPanel addressLink = new LinkPanel("personAddress", "hidden-phone list", "<" + address + ">",
+					GitSearchPage.class, WicketUtils.newSearchParameter(repositoryName, objectId,
+							address, searchType));
+			setPersonSearchTooltip(addressLink, address, searchType);
+			fullPerson.add(addressLink);
+			return fullPerson;
+		}
+	}
+
+	protected void setPersonSearchTooltip(Component component, String value,
+			Constants.SearchType searchType) {
+		if (searchType.equals(Constants.SearchType.AUTHOR)) {
+			WicketUtils.setHtmlTooltip(component, getString("gb.searchForAuthor") + " " + value);
+		} else if (searchType.equals(Constants.SearchType.COMMITTER)) {
+			WicketUtils.setHtmlTooltip(component, getString("gb.searchForCommitter") + " " + value);
+		}
+	}
+
+	protected void setChangeTypeTooltip(Component container, ChangeType type) {
+		switch (type) {
+		case ADD:
+			WicketUtils.setHtmlTooltip(container, getString("gb.addition"));
+			break;
+		case COPY:
+		case RENAME:
+			WicketUtils.setHtmlTooltip(container, getString("gb.rename"));
+			break;
+		case DELETE:
+			WicketUtils.setHtmlTooltip(container, getString("gb.deletion"));
+			break;
+		case MODIFY:
+			WicketUtils.setHtmlTooltip(container, getString("gb.modification"));
+			break;
+		}
+	}
+
+	@Override
+	protected void onBeforeRender() {
+		// dispose of repository object
+		if (r != null) {
+			r.close();
+			r = null;
+		}
+		// setup page header and footer
+		setupPage(repositoryName, "/ " + getPageName());
+		super.onBeforeRender();
+	}
+	
+	@Override
+	protected void setLastModified() {
+		if (getClass().isAnnotationPresent(CacheControl.class)) {
+			CacheControl cacheControl = getClass().getAnnotation(CacheControl.class);
+			switch (cacheControl.value()) {
+			case REPOSITORY:
+				RepositoryModel repository = getRepositoryModel();
+				if (repository != null) {
+					setLastModified(repository.lastChange);
+				}
+				break;
+			case COMMIT:
+				RevCommit commit = getCommit();
+				if (commit != null) {
+					Date commitDate = JGitUtils.getCommitDate(commit);
+					setLastModified(commitDate);
+				}
+				break;
+			default:
+				super.setLastModified();
+			}
+		}
+	}
+
+	protected PageParameters newRepositoryParameter() {
+		return WicketUtils.newRepositoryParameter(repositoryName);
+	}
+
+	protected PageParameters newCommitParameter() {
+		return WicketUtils.newObjectParameter(repositoryName, objectId);
+	}
+
+	protected PageParameters newCommitParameter(String commitId) {
+		return WicketUtils.newObjectParameter(repositoryName, commitId);
+	}
+
+	public boolean isShowAdmin() {
+		return showAdmin;
+	}
+	
+	public boolean isOwner() {
+		return isOwner;
+	}
+	
+	private class SearchForm extends SessionlessForm<Void> implements Serializable {
+		private static final long serialVersionUID = 1L;
+
+		private final String repositoryName;
+
+		private final IModel<String> searchBoxModel = new Model<String>("");
+
+		private final IModel<Constants.SearchType> searchTypeModel = new Model<Constants.SearchType>(
+				Constants.SearchType.COMMIT);
+
+		public SearchForm(String id, String repositoryName) {
+			super(id, RepositoryPage.this.getClass(), RepositoryPage.this.getPageParameters());
+			this.repositoryName = repositoryName;
+			DropDownChoice<Constants.SearchType> searchType = new DropDownChoice<Constants.SearchType>(
+					"searchType", Arrays.asList(Constants.SearchType.values()));
+			searchType.setModel(searchTypeModel);
+			add(searchType.setVisible(GitBlit.getBoolean(Keys.web.showSearchTypeSelection, false)));
+			TextField<String> searchBox = new TextField<String>("searchBox", searchBoxModel);
+			add(searchBox);
+		}
+
+		void setTranslatedAttributes() {
+			WicketUtils.setHtmlTooltip(get("searchType"), getString("gb.searchTypeTooltip"));
+			WicketUtils.setHtmlTooltip(get("searchBox"),
+					MessageFormat.format(getString("gb.searchTooltip"), repositoryName));
+			WicketUtils.setInputPlaceholder(get("searchBox"), getString("gb.search"));
+		}
+
+		@Override
+		public void onSubmit() {
+			Constants.SearchType searchType = searchTypeModel.getObject();
+			String searchString = searchBoxModel.getObject();
+			if (StringUtils.isEmpty(searchString)) {
+				// redirect to self to avoid wicket page update bug 
+				PageParameters params = RepositoryPage.this.getPageParameters();
+				String relativeUrl = urlFor(RepositoryPage.this.getClass(), params).toString();
+				String absoluteUrl = RequestUtils.toAbsolutePath(relativeUrl);
+				getRequestCycle().setRequestTarget(new RedirectRequestTarget(absoluteUrl));
+				return;
+			}
+			for (Constants.SearchType type : Constants.SearchType.values()) {
+				if (searchString.toLowerCase().startsWith(type.name().toLowerCase() + ":")) {
+					searchType = type;
+					searchString = searchString.substring(type.name().toLowerCase().length() + 1)
+							.trim();
+					break;
+				}
+			}
+			Class<? extends BasePage> searchPageClass = GitSearchPage.class;
+			RepositoryModel model = GitBlit.self().getRepositoryModel(repositoryName);
+			if (GitBlit.getBoolean(Keys.web.allowLuceneIndexing, true)
+					&& !ArrayUtils.isEmpty(model.indexedBranches)) {
+				// this repository is Lucene-indexed
+				searchPageClass = LuceneSearchPage.class;
+			}
+			// use an absolute url to workaround Wicket-Tomcat problems with
+			// mounted url parameters (issue-111)
+			PageParameters params = WicketUtils.newSearchParameter(repositoryName, null, searchString, searchType);
+			String relativeUrl = urlFor(searchPageClass, params).toString();
+			String absoluteUrl = RequestUtils.toAbsolutePath(relativeUrl);
+			getRequestCycle().setRequestTarget(new RedirectRequestTarget(absoluteUrl));
+		}
+	}
+}
diff --git a/src/com/gitblit/wicket/pages/ReviewProposalPage.html b/src/main/java/com/gitblit/wicket/pages/ReviewProposalPage.html
similarity index 100%
rename from src/com/gitblit/wicket/pages/ReviewProposalPage.html
rename to src/main/java/com/gitblit/wicket/pages/ReviewProposalPage.html
diff --git a/src/com/gitblit/wicket/pages/ReviewProposalPage.java b/src/main/java/com/gitblit/wicket/pages/ReviewProposalPage.java
similarity index 100%
rename from src/com/gitblit/wicket/pages/ReviewProposalPage.java
rename to src/main/java/com/gitblit/wicket/pages/ReviewProposalPage.java
diff --git a/src/main/java/com/gitblit/wicket/pages/RootPage.html b/src/main/java/com/gitblit/wicket/pages/RootPage.html
new file mode 100644
index 0000000..11f7f38
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/RootPage.html
@@ -0,0 +1,67 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"  
+      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"  
+      xml:lang="en"  
+      lang="en"> 
+<body>
+<wicket:extend>
+	<div class="navbar navbar-fixed-top">
+		<div class="navbar-inner">
+			<div class="container">
+				<a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
+            			<span class="icon-bar"></span>
+            			<span class="icon-bar"></span>
+            			<span class="icon-bar"></span>
+          		</a>
+				<a class="brand" wicket:id="rootLink">
+					<img src="logo.png" height="45" width="120" class="logo"/>
+				</a>
+				
+				<div class="nav-collapse">
+					<div wicket:id="navPanel"></div>
+					<ul class="nav pull-right">
+					 	<span wicket:id="userPanel"></span>
+					</ul>
+				</div>
+			</div>
+		</div>
+	</div>
+				
+	<!-- subclass content -->
+	<div class="container">
+		<div style="text-align:center" wicket:id="feedback">[Feedback Panel]</div>
+	</div>
+	
+	<wicket:child/>
+	
+	<wicket:fragment wicket:id="loginFormFragment">
+		<li>
+			<form class="pull-right" wicket:id="loginForm">
+				<span class="form-search">
+					<input wicket:id="username" class="input-small" type="text" />
+					<input wicket:id="password" class="input-small" type="password" />
+					<button class="btn" type="submit"><wicket:message key="gb.login"></wicket:message></button>
+				</span>
+			</form>
+		</li>
+	</wicket:fragment>
+	
+	<!-- user fragment -->
+	<wicket:fragment wicket:id="userMenuFragment">
+		<li class="dropdown">
+			<a data-toggle="dropdown" class="dropdown-toggle" style="text-decoration: none;" href="#"><span wicket:id="username"></span> <b class="caret"></b></a>
+        	<ul class="dropdown-menu">
+        		<li style="color:#ccc;padding-left:15px;font-weight:bold;"><span wicket:id="displayName"></span></li>
+        		<li class="divider"></li>
+            	<li><a wicket:id="newRepository"><wicket:message key="gb.newRepository"></wicket:message></a></li>
+            	<li><a wicket:id="myProfile"><wicket:message key="gb.myProfile"></wicket:message></a></li>
+            	<li><a wicket:id="changePassword"><wicket:message key="gb.changePassword"></wicket:message></a></li>
+            	<li class="divider"></li>
+        		<li><a wicket:id="logout"><wicket:message key="gb.logout"></wicket:message></a></li>
+			</ul>
+		</li>
+	</wicket:fragment>
+	
+</wicket:extend>
+</body>
+</html>
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/pages/RootPage.java b/src/main/java/com/gitblit/wicket/pages/RootPage.java
new file mode 100644
index 0000000..7739e6d
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/RootPage.java
@@ -0,0 +1,591 @@
+/*
+ * 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.wicket.pages;
+
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.regex.Pattern;
+
+import org.apache.wicket.MarkupContainer;
+import org.apache.wicket.PageParameters;
+import org.apache.wicket.behavior.HeaderContributor;
+import org.apache.wicket.markup.html.IHeaderContributor;
+import org.apache.wicket.markup.html.IHeaderResponse;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.form.PasswordTextField;
+import org.apache.wicket.markup.html.form.TextField;
+import org.apache.wicket.markup.html.link.BookmarkablePageLink;
+import org.apache.wicket.markup.html.panel.Fragment;
+import org.apache.wicket.model.IModel;
+import org.apache.wicket.model.Model;
+import org.apache.wicket.protocol.http.WebResponse;
+
+import com.gitblit.Constants;
+import com.gitblit.GitBlit;
+import com.gitblit.Keys;
+import com.gitblit.models.RepositoryModel;
+import com.gitblit.models.TeamModel;
+import com.gitblit.models.UserModel;
+import com.gitblit.utils.StringUtils;
+import com.gitblit.wicket.GitBlitWebSession;
+import com.gitblit.wicket.PageRegistration;
+import com.gitblit.wicket.PageRegistration.DropDownMenuItem;
+import com.gitblit.wicket.PageRegistration.DropDownToggleItem;
+import com.gitblit.wicket.SessionlessForm;
+import com.gitblit.wicket.WicketUtils;
+import com.gitblit.wicket.panels.GravatarImage;
+import com.gitblit.wicket.panels.NavigationPanel;
+
+/**
+ * Root page is a topbar, navigable page like Repositories, Users, or
+ * Federation.
+ * 
+ * @author James Moger
+ * 
+ */
+public abstract class RootPage extends BasePage {
+
+	boolean showAdmin;
+
+	IModel<String> username = new Model<String>("");
+	IModel<String> password = new Model<String>("");
+	List<RepositoryModel> repositoryModels = new ArrayList<RepositoryModel>();
+
+	public RootPage() {
+		super();
+	}
+
+	public RootPage(PageParameters params) {
+		super(params);
+	}
+
+	@Override
+	protected void setupPage(String repositoryName, String pageName) {
+
+		// CSS header overrides
+		add(new HeaderContributor(new IHeaderContributor() {
+			private static final long serialVersionUID = 1L;
+
+			public void renderHead(IHeaderResponse response) {
+				StringBuilder buffer = new StringBuilder();
+				buffer.append("<style type=\"text/css\">\n");
+				buffer.append(".navbar-inner {\n");
+				final String headerBackground = GitBlit.getString(Keys.web.headerBackgroundColor, null);
+				if (!StringUtils.isEmpty(headerBackground)) {
+					buffer.append(MessageFormat.format("background-color: {0};\n", headerBackground));
+				}
+				final String headerBorder = GitBlit.getString(Keys.web.headerBorderColor, null);
+				if (!StringUtils.isEmpty(headerBorder)) {
+					buffer.append(MessageFormat.format("border-bottom: 1px solid {0} !important;\n", headerBorder));
+				}
+				buffer.append("}\n");
+				final String headerBorderFocus = GitBlit.getString(Keys.web.headerBorderFocusColor, null);
+				if (!StringUtils.isEmpty(headerBorderFocus)) {
+					buffer.append(".navbar ul li:focus, .navbar .active {\n");
+					buffer.append(MessageFormat.format("border-bottom: 4px solid {0};\n", headerBorderFocus));
+					buffer.append("}\n");
+				}
+				final String headerForeground = GitBlit.getString(Keys.web.headerForegroundColor, null);
+				if (!StringUtils.isEmpty(headerForeground)) {
+					buffer.append(".navbar ul.nav li a {\n");
+					buffer.append(MessageFormat.format("color: {0};\n", headerForeground));
+					buffer.append("}\n");
+					buffer.append(".navbar ul.nav .active a {\n");
+					buffer.append(MessageFormat.format("color: {0};\n", headerForeground));
+					buffer.append("}\n");
+				}
+				final String headerHover = GitBlit.getString(Keys.web.headerHoverColor, null);
+				if (!StringUtils.isEmpty(headerHover)) {
+					buffer.append(".navbar ul.nav li a:hover {\n");
+					buffer.append(MessageFormat.format("color: {0} !important;\n", headerHover));
+					buffer.append("}\n");
+				}
+				buffer.append("</style>\n");
+				response.renderString(buffer.toString());
+				}
+			}));
+		
+		boolean authenticateView = GitBlit.getBoolean(Keys.web.authenticateViewPages, false);
+		boolean authenticateAdmin = GitBlit.getBoolean(Keys.web.authenticateAdminPages, true);
+		boolean allowAdmin = GitBlit.getBoolean(Keys.web.allowAdministration, true);
+
+		if (authenticateAdmin) {
+			showAdmin = allowAdmin && GitBlitWebSession.get().canAdmin();
+			// authentication requires state and session
+			setStatelessHint(false);
+		} else {
+			showAdmin = allowAdmin;
+			if (authenticateView) {
+				// authentication requires state and session
+				setStatelessHint(false);
+			} else {
+				// no authentication required, no state and no session required
+				setStatelessHint(true);
+			}
+		}
+		
+		if (authenticateView || authenticateAdmin) {
+			if (GitBlitWebSession.get().isLoggedIn()) {
+				UserMenu userFragment = new UserMenu("userPanel", "userMenuFragment", RootPage.this);
+				add(userFragment);
+			} else {
+				LoginForm loginForm = new LoginForm("userPanel", "loginFormFragment", RootPage.this);
+				add(loginForm);
+			}
+		} else {
+			add(new Label("userPanel").setVisible(false));
+		}
+		
+		boolean showRegistrations = GitBlit.canFederate()
+				&& GitBlit.getBoolean(Keys.web.showFederationRegistrations, false);
+
+		// navigation links
+		List<PageRegistration> pages = new ArrayList<PageRegistration>();
+		if (!authenticateView || (authenticateView && GitBlitWebSession.get().isLoggedIn())) {
+			pages.add(new PageRegistration(GitBlitWebSession.get().isLoggedIn() ? "gb.myDashboard" : "gb.dashboard", MyDashboardPage.class,
+					getRootPageParameters()));
+			pages.add(new PageRegistration("gb.repositories", RepositoriesPage.class,
+					getRootPageParameters()));
+			pages.add(new PageRegistration("gb.activity", ActivityPage.class, getRootPageParameters()));
+			if (GitBlit.getBoolean(Keys.web.allowLuceneIndexing, true)) {
+				pages.add(new PageRegistration("gb.search", LuceneSearchPage.class));
+			}
+			if (showAdmin) {
+				pages.add(new PageRegistration("gb.users", UsersPage.class));
+			}
+			if (showAdmin || showRegistrations) {
+				pages.add(new PageRegistration("gb.federation", FederationPage.class));
+			}
+
+			if (!authenticateView || (authenticateView && GitBlitWebSession.get().isLoggedIn())) {
+				addDropDownMenus(pages);
+			}
+		}
+		
+		NavigationPanel navPanel = new NavigationPanel("navPanel", getRootNavPageClass(), pages);
+		add(navPanel);
+
+		// display an error message cached from a redirect
+		String cachedMessage = GitBlitWebSession.get().clearErrorMessage();
+		if (!StringUtils.isEmpty(cachedMessage)) {
+			error(cachedMessage);
+		} else if (showAdmin) {
+			int pendingProposals = GitBlit.self().getPendingFederationProposals().size();
+			if (pendingProposals == 1) {
+				info(getString("gb.OneProposalToReview"));
+			} else if (pendingProposals > 1) {
+				info(MessageFormat.format(getString("gb.nFederationProposalsToReview"),
+						pendingProposals));
+			}
+		}
+
+		super.setupPage(repositoryName, pageName);
+	}
+	
+	protected Class<? extends BasePage> getRootNavPageClass() {
+		return getClass();
+	}
+
+	private PageParameters getRootPageParameters() {
+		if (reusePageParameters()) {
+			PageParameters pp = getPageParameters();
+			if (pp != null) {
+				PageParameters params = new PageParameters(pp);
+				// remove named project parameter
+				params.remove("p");
+
+				// remove named repository parameter
+				params.remove("r");
+
+				// remove named user parameter
+				params.remove("user");
+
+				// remove days back parameter if it is the default value
+				if (params.containsKey("db")
+						&& params.getInt("db") == GitBlit.getInteger(Keys.web.activityDuration, 7)) {
+					params.remove("db");
+				}
+				return params;
+			}			
+		}
+		return null;
+	}
+
+	protected boolean reusePageParameters() {
+		return false;
+	}
+
+	private void loginUser(UserModel user) {
+		if (user != null) {
+			// Set the user into the session
+			GitBlitWebSession session = GitBlitWebSession.get();
+			// issue 62: fix session fixation vulnerability
+			session.replaceSession();
+			session.setUser(user);
+
+			// Set Cookie
+			if (GitBlit.getBoolean(Keys.web.allowCookieAuthentication, false)) {
+				WebResponse response = (WebResponse) getRequestCycle().getResponse();
+				GitBlit.self().setCookie(response, user);
+			}
+
+			if (!session.continueRequest()) {
+				PageParameters params = getPageParameters();
+				if (params == null) {
+					// redirect to this page
+					setResponsePage(getClass());
+				} else {
+					// Strip username and password and redirect to this page
+					params.remove("username");
+					params.remove("password");
+					setResponsePage(getClass(), params);
+				}
+			}
+		}
+	}
+	
+	protected List<RepositoryModel> getRepositoryModels() {
+		if (repositoryModels.isEmpty()) {
+			final UserModel user = GitBlitWebSession.get().getUser();
+			List<RepositoryModel> repositories = GitBlit.self().getRepositoryModels(user);
+			repositoryModels.addAll(repositories);
+			Collections.sort(repositoryModels);
+		}
+		return repositoryModels;
+	}
+
+	protected void addDropDownMenus(List<PageRegistration> pages) {
+
+	}
+
+	protected List<DropDownMenuItem> getRepositoryFilterItems(PageParameters params) {
+		final UserModel user = GitBlitWebSession.get().getUser();
+		Set<DropDownMenuItem> filters = new LinkedHashSet<DropDownMenuItem>();
+		List<RepositoryModel> repositories = getRepositoryModels();
+
+		// accessible repositories by federation set
+		Map<String, AtomicInteger> setMap = new HashMap<String, AtomicInteger>();
+		for (RepositoryModel repository : repositories) {
+			for (String set : repository.federationSets) {
+				String key = set.toLowerCase();
+				if (setMap.containsKey(key)) {
+					setMap.get(key).incrementAndGet();
+				} else {
+					setMap.put(key, new AtomicInteger(1));
+				}
+			}
+		}
+		if (setMap.size() > 0) {
+			List<String> sets = new ArrayList<String>(setMap.keySet());
+			Collections.sort(sets);
+			for (String set : sets) {
+				filters.add(new DropDownToggleItem(MessageFormat.format("{0} ({1})", set,
+						setMap.get(set).get()), "set", set, params));
+			}
+			// divider
+			filters.add(new DropDownMenuItem());
+		}
+
+		// user's team memberships
+		if (user != null && user.teams.size() > 0) {
+			List<TeamModel> teams = new ArrayList<TeamModel>(user.teams);
+			Collections.sort(teams);
+			for (TeamModel team : teams) {
+				filters.add(new DropDownToggleItem(MessageFormat.format("{0} ({1})", team.name,
+						team.repositories.size()), "team", team.name, params));
+			}
+			// divider
+			filters.add(new DropDownMenuItem());
+		}
+
+		// custom filters
+		String customFilters = GitBlit.getString(Keys.web.customFilters, null);
+		if (!StringUtils.isEmpty(customFilters)) {
+			boolean addedExpression = false;
+			List<String> expressions = StringUtils.getStringsFromValue(customFilters, "!!!");
+			for (String expression : expressions) {
+				if (!StringUtils.isEmpty(expression)) {
+					addedExpression = true;
+					filters.add(new DropDownToggleItem(null, "x", expression, params));
+				}
+			}
+			// if we added any custom expressions, add a divider
+			if (addedExpression) {
+				filters.add(new DropDownMenuItem());
+			}
+		}
+		return new ArrayList<DropDownMenuItem>(filters);
+	}
+
+	protected List<DropDownMenuItem> getTimeFilterItems(PageParameters params) {
+		// days back choices - additive parameters
+		int daysBack = GitBlit.getInteger(Keys.web.activityDuration, 7);
+		if (daysBack < 1) {
+			daysBack = 7;
+		}
+		PageParameters clonedParams;
+		if (params == null) {
+			clonedParams = new PageParameters();
+		} else {
+			clonedParams = new PageParameters(params);
+		}
+		
+		if (!clonedParams.containsKey("db")) {
+			clonedParams.put("db",  daysBack);
+		}
+		
+		List<DropDownMenuItem> items = new ArrayList<DropDownMenuItem>();
+		Set<Integer> choicesSet = new TreeSet<Integer>(GitBlit.getIntegers(Keys.web.activityDurationChoices));
+		if (choicesSet.isEmpty()) {
+			 choicesSet.addAll(Arrays.asList(1, 3, 7, 14, 21, 28));
+		}
+		List<Integer> choices = new ArrayList<Integer>(choicesSet);
+		Collections.sort(choices);
+		String lastDaysPattern = getString("gb.lastNDays");
+		for (Integer db : choices) {
+			if (db == 1) {
+				items.add(new DropDownMenuItem(getString("gb.time.today"), "db", db.toString(), clonedParams));
+			} else {
+				String txt = MessageFormat.format(lastDaysPattern, db);
+				items.add(new DropDownMenuItem(txt, "db", db.toString(), clonedParams));
+			}
+		}
+		items.add(new DropDownMenuItem());
+		return items;
+	}
+
+	protected List<RepositoryModel> getRepositories(PageParameters params) {
+		if (params == null) {
+			return getRepositoryModels();
+		}
+
+		boolean hasParameter = false;
+		String projectName = WicketUtils.getProjectName(params);
+		String userName = WicketUtils.getUsername(params);
+		if (StringUtils.isEmpty(projectName)) {
+			if (!StringUtils.isEmpty(userName)) {
+				projectName = "~" + userName;
+			}
+		}
+		String repositoryName = WicketUtils.getRepositoryName(params);
+		String set = WicketUtils.getSet(params);
+		String regex = WicketUtils.getRegEx(params);
+		String team = WicketUtils.getTeam(params);
+		int daysBack = params.getInt("db", 0);
+
+		List<RepositoryModel> availableModels = getRepositoryModels();
+		Set<RepositoryModel> models = new HashSet<RepositoryModel>();
+
+		if (!StringUtils.isEmpty(repositoryName)) {
+			// try named repository
+			hasParameter = true;
+			for (RepositoryModel model : availableModels) {
+				if (model.name.equalsIgnoreCase(repositoryName)) {
+					models.add(model);
+					break;
+				}
+			}
+		}
+
+		if (!StringUtils.isEmpty(projectName)) {
+			// try named project
+			hasParameter = true;			
+			if (projectName.equalsIgnoreCase(GitBlit.getString(Keys.web.repositoryRootGroupName, "main"))) {
+				// root project/group
+				for (RepositoryModel model : availableModels) {
+					if (model.name.indexOf('/') == -1) {
+						models.add(model);
+					}
+				}
+			} else {
+				// named project/group
+				String group = projectName.toLowerCase() + "/";
+				for (RepositoryModel model : availableModels) {
+					if (model.name.toLowerCase().startsWith(group)) {
+						models.add(model);
+					}
+				}
+			}
+		}
+
+		if (!StringUtils.isEmpty(regex)) {
+			// filter the repositories by the regex
+			hasParameter = true;
+			Pattern pattern = Pattern.compile(regex);
+			for (RepositoryModel model : availableModels) {
+				if (pattern.matcher(model.name).find()) {
+					models.add(model);
+				}
+			}
+		}
+
+		if (!StringUtils.isEmpty(set)) {
+			// filter the repositories by the specified sets
+			hasParameter = true;
+			List<String> sets = StringUtils.getStringsFromValue(set, ",");
+			for (RepositoryModel model : availableModels) {
+				for (String curr : sets) {
+					if (model.federationSets.contains(curr)) {
+						models.add(model);
+					}
+				}
+			}
+		}
+
+		if (!StringUtils.isEmpty(team)) {
+			// filter the repositories by the specified teams
+			hasParameter = true;
+			List<String> teams = StringUtils.getStringsFromValue(team, ",");
+
+			// need TeamModels first
+			List<TeamModel> teamModels = new ArrayList<TeamModel>();
+			for (String name : teams) {
+				TeamModel teamModel = GitBlit.self().getTeamModel(name);
+				if (teamModel != null) {
+					teamModels.add(teamModel);
+				}
+			}
+
+			// brute-force our way through finding the matching models
+			for (RepositoryModel repositoryModel : availableModels) {
+				for (TeamModel teamModel : teamModels) {
+					if (teamModel.hasRepositoryPermission(repositoryModel.name)) {
+						models.add(repositoryModel);
+					}
+				}
+			}
+		}
+
+		if (!hasParameter) {
+			models.addAll(availableModels);
+		}
+
+		// time-filter the list
+		if (daysBack > 0) {
+			Calendar cal = Calendar.getInstance();
+			cal.set(Calendar.HOUR_OF_DAY, 0);
+			cal.set(Calendar.MINUTE, 0);
+			cal.set(Calendar.SECOND, 0);
+			cal.set(Calendar.MILLISECOND, 0);
+			cal.add(Calendar.DATE, -1 * daysBack);
+			Date threshold = cal.getTime();
+			Set<RepositoryModel> timeFiltered = new HashSet<RepositoryModel>();
+			for (RepositoryModel model : models) {
+				if (model.lastChange.after(threshold)) {
+					timeFiltered.add(model);
+				}
+			}
+			models = timeFiltered;
+		}
+		
+		List<RepositoryModel> list = new ArrayList<RepositoryModel>(models);
+		Collections.sort(list);
+		return list;
+	}
+	
+	/**
+	 * Inline login form. 
+	 */
+	private class LoginForm extends Fragment {
+		private static final long serialVersionUID = 1L;
+
+		public LoginForm(String id, String markupId, MarkupContainer markupProvider) {
+			super(id, markupId, markupProvider);
+			setRenderBodyOnly(true);
+
+			SessionlessForm<Void> loginForm = new SessionlessForm<Void>("loginForm", RootPage.this.getClass(), getPageParameters()) {
+
+				private static final long serialVersionUID = 1L;
+
+				@Override
+				public void onSubmit() {
+					String username = RootPage.this.username.getObject();
+					char[] password = RootPage.this.password.getObject().toCharArray();
+
+					UserModel user = GitBlit.self().authenticate(username, password);
+					if (user == null) {
+						error(getString("gb.invalidUsernameOrPassword"));
+					} else if (user.username.equals(Constants.FEDERATION_USER)) {
+						// disallow the federation user from logging in via the
+						// web ui
+						error(getString("gb.invalidUsernameOrPassword"));
+						user = null;
+					} else {
+						loginUser(user);
+					}
+				}
+			};
+			TextField<String> unameField = new TextField<String>("username", username);
+			WicketUtils.setInputPlaceholder(unameField, markupProvider.getString("gb.username"));
+			loginForm.add(unameField);
+			PasswordTextField pwField = new PasswordTextField("password", password);
+			WicketUtils.setInputPlaceholder(pwField, markupProvider.getString("gb.password"));
+			loginForm.add(pwField);
+			add(loginForm);
+		}
+	}
+	
+	/**
+	 * Menu for the authenticated user.
+	 */
+	static class UserMenu extends Fragment {
+
+		private static final long serialVersionUID = 1L;
+
+		public UserMenu(String id, String markupId, MarkupContainer markupProvider) {
+			super(id, markupId, markupProvider);
+			setRenderBodyOnly(true);
+
+			GitBlitWebSession session = GitBlitWebSession.get();
+			UserModel user = session.getUser();
+			boolean editCredentials = GitBlit.self().supportsCredentialChanges(user);
+			boolean standardLogin = session.authenticationType.isStandard();
+
+			if (GitBlit.getBoolean(Keys.web.allowGravatar, true)) {
+				add(new GravatarImage("username", user.getDisplayName(),
+						user.emailAddress, "navbarGravatar", 20, false, false));
+			} else {
+				add(new Label("username", user.getDisplayName()));
+			}
+
+			add(new Label("displayName", user.getDisplayName()));
+
+			add(new BookmarkablePageLink<Void>("newRepository",
+					EditRepositoryPage.class).setVisible(user.canAdmin() || user.canCreate()));
+
+			add(new BookmarkablePageLink<Void>("myProfile", 
+					UserPage.class, WicketUtils.newUsernameParameter(user.username)));
+
+			add(new BookmarkablePageLink<Void>("changePassword", 
+					ChangePasswordPage.class).setVisible(editCredentials));
+			
+			add(new BookmarkablePageLink<Void>("logout",
+					LogoutPage.class).setVisible(standardLogin));
+		}
+	}
+}
diff --git a/src/main/java/com/gitblit/wicket/pages/RootSubPage.html b/src/main/java/com/gitblit/wicket/pages/RootSubPage.html
new file mode 100644
index 0000000..864cada
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/RootSubPage.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"  
+      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"  
+      xml:lang="en"  
+      lang="en"> 
+
+<body>
+<wicket:extend>
+	<div class="repositorynavbar">
+		<div class="repositorynavbar-inner">
+			<div class="container">
+				<!-- page header -->
+				<div class="title" style="font-size: 22px; color: rgb(0, 32, 96);padding: 3px 0px 7px;">
+					<span class="project" wicket:id="pageName">[page name]</span> <span class="repository" wicket:id="pageSubName">[sub name]</span>
+				</div>
+			</div>
+		</div>
+	</div>
+	
+	<div class="container">	
+		<!-- Subclass Content -->
+		<wicket:child/>
+	</div>
+</wicket:extend>
+</body>
+</html>
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/pages/RootSubPage.java b/src/main/java/com/gitblit/wicket/pages/RootSubPage.java
similarity index 100%
rename from src/com/gitblit/wicket/pages/RootSubPage.java
rename to src/main/java/com/gitblit/wicket/pages/RootSubPage.java
diff --git a/src/com/gitblit/wicket/pages/SendProposalPage.html b/src/main/java/com/gitblit/wicket/pages/SendProposalPage.html
similarity index 100%
rename from src/com/gitblit/wicket/pages/SendProposalPage.html
rename to src/main/java/com/gitblit/wicket/pages/SendProposalPage.html
diff --git a/src/main/java/com/gitblit/wicket/pages/SendProposalPage.java b/src/main/java/com/gitblit/wicket/pages/SendProposalPage.java
new file mode 100644
index 0000000..a070669
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/SendProposalPage.java
@@ -0,0 +1,157 @@
+/*
+ * 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.wicket.pages;
+
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.wicket.PageParameters;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.form.Button;
+import org.apache.wicket.markup.html.form.Form;
+import org.apache.wicket.markup.html.form.TextField;
+import org.apache.wicket.model.CompoundPropertyModel;
+
+import com.gitblit.Constants.FederationProposalResult;
+import com.gitblit.GitBlit;
+import com.gitblit.models.FederationProposal;
+import com.gitblit.models.RepositoryModel;
+import com.gitblit.utils.FederationUtils;
+import com.gitblit.utils.StringUtils;
+import com.gitblit.wicket.RequiresAdminRole;
+import com.gitblit.wicket.WicketUtils;
+import com.gitblit.wicket.panels.RepositoriesPanel;
+
+@RequiresAdminRole
+public class SendProposalPage extends RootSubPage {
+
+	public String myUrl;
+
+	public String destinationUrl;
+
+	public String message;
+
+	public SendProposalPage(PageParameters params) {
+		super(params);
+
+		setupPage(getString("gb.sendProposal"), "");
+		setStatelessHint(true);
+
+		final String token = WicketUtils.getToken(params);
+
+		myUrl = WicketUtils.getGitblitURL(getRequest());
+		destinationUrl = "https://";
+
+		// temporary proposal
+		FederationProposal proposal = GitBlit.self().createFederationProposal(myUrl, token);
+		if (proposal == null) {
+			error(getString("gb.couldNotCreateFederationProposal"), true);
+		}
+
+		CompoundPropertyModel<SendProposalPage> model = new CompoundPropertyModel<SendProposalPage>(
+				this);
+
+		Form<SendProposalPage> form = new Form<SendProposalPage>("editForm", model) {
+			private static final long serialVersionUID = 1L;
+
+			@Override
+			protected void onSubmit() {
+				// confirm a repository name was entered
+				if (StringUtils.isEmpty(myUrl)) {
+					error(getString("gb.pleaseSetGitblitUrl"));
+					return;
+				}
+				if (StringUtils.isEmpty(destinationUrl)) {
+					error(getString("gb.pleaseSetDestinationUrl"));
+					return;
+				}
+
+				// build new proposal
+				FederationProposal proposal = GitBlit.self().createFederationProposal(myUrl, token);
+				proposal.url = myUrl;
+				proposal.message = message;
+				try {
+					FederationProposalResult res = FederationUtils
+							.propose(destinationUrl, proposal);
+					switch (res) {
+					case ACCEPTED:
+						info(MessageFormat.format(getString("gb.proposalReceived"),
+								destinationUrl));
+						setResponsePage(RepositoriesPage.class);
+						break;
+					case NO_POKE:
+						error(MessageFormat.format(getString("noGitblitFound"),
+								destinationUrl, myUrl));
+						break;
+					case NO_PROPOSALS:
+						error(MessageFormat.format(getString("gb.noProposals"),
+								destinationUrl));
+						break;
+					case FEDERATION_DISABLED:
+						error(MessageFormat
+								.format(getString("gb.noFederation"),
+										destinationUrl));
+						break;
+					case MISSING_DATA:
+						error(MessageFormat.format(getString("gb.proposalFailed"),
+								destinationUrl));
+						break;
+					case ERROR:
+						error(MessageFormat.format(getString("gb.proposalError"),
+								destinationUrl));
+						break;
+					}
+				} catch (Exception e) {
+					if (!StringUtils.isEmpty(e.getMessage())) {
+						error(e.getMessage());
+					} else {
+						error(getString("gb.failedToSendProposal"));
+					}
+				}
+			}
+		};
+		form.add(new TextField<String>("myUrl"));
+		form.add(new TextField<String>("destinationUrl"));
+		form.add(new TextField<String>("message"));
+		form.add(new Label("tokenType", proposal.tokenType.name()));
+		form.add(new Label("token", proposal.token));
+
+		form.add(new Button("save"));
+		Button cancel = new Button("cancel") {
+			private static final long serialVersionUID = 1L;
+
+			@Override
+			public void onSubmit() {
+				setResponsePage(FederationPage.class);
+			}
+		};
+		cancel.setDefaultFormProcessing(false);
+		form.add(cancel);
+		add(form);
+
+		List<RepositoryModel> repositories = new ArrayList<RepositoryModel>(
+				proposal.repositories.values());
+		RepositoriesPanel repositoriesPanel = new RepositoriesPanel("repositoriesPanel", false,
+				false, repositories, false, getAccessRestrictions());
+		add(repositoriesPanel);
+	}
+	
+	@Override
+	protected Class<? extends BasePage> getRootNavPageClass() {
+		return FederationPage.class;
+	}
+}
diff --git a/src/main/java/com/gitblit/wicket/pages/SessionPage.java b/src/main/java/com/gitblit/wicket/pages/SessionPage.java
new file mode 100644
index 0000000..5a255ec
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/SessionPage.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2013 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit.wicket.pages;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.wicket.PageParameters;
+import org.apache.wicket.markup.html.WebPage;
+import org.apache.wicket.protocol.http.WebRequest;
+import org.apache.wicket.protocol.http.WebResponse;
+
+import com.gitblit.GitBlit;
+import com.gitblit.models.UserModel;
+import com.gitblit.wicket.GitBlitWebSession;
+
+public abstract class SessionPage extends WebPage {
+
+	public SessionPage() {
+		super();
+		login();
+	}
+
+	public SessionPage(final PageParameters params) {
+		super(params);
+		login();
+	}
+
+	private void login() {
+		GitBlitWebSession session = GitBlitWebSession.get();
+		if (session.isLoggedIn() && !session.isSessionInvalidated()) {
+			// already have a session, refresh usermodel to pick up
+			// any changes to permissions or roles (issue-186)
+			UserModel user = GitBlit.self().getUserModel(session.getUser().username);
+			session.setUser(user);
+			return;
+		}
+
+		// try to authenticate by servlet request
+		HttpServletRequest httpRequest = ((WebRequest) getRequestCycle().getRequest())
+				.getHttpServletRequest();
+		UserModel user = GitBlit.self().authenticate(httpRequest);
+
+		// Login the user
+		if (user != null) {
+			// issue 62: fix session fixation vulnerability
+			session.replaceSession();
+			session.setUser(user);
+
+			// Set Cookie
+			WebResponse response = (WebResponse) getRequestCycle().getResponse();
+			GitBlit.self().setCookie(response, user);
+
+			session.continueRequest();
+		}
+	}
+}
diff --git a/src/main/java/com/gitblit/wicket/pages/SummaryPage.html b/src/main/java/com/gitblit/wicket/pages/SummaryPage.html
new file mode 100644
index 0000000..3dd1eb4
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/SummaryPage.html
@@ -0,0 +1,58 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"  
+      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"  
+      xml:lang="en"  
+      lang="en"> 
+<body>
+<wicket:extend>
+	
+	<div style="clear:both;">
+		<!-- Repository Activity Chart -->	
+		<div class="hidden-phone" style="float:right;">
+			<img class="activityGraph" wicket:id="commitsChart" />
+		</div>	
+	
+		<!-- Repository info -->
+		<div class="hidden-phone"> 
+			<table class="summary">
+				<tr><th><wicket:message key="gb.description">[description]</wicket:message></th><td><span wicket:id="repositoryDescription">[repository description]</span></td></tr>
+				<tr><th><wicket:message key="gb.owners">[owner]</wicket:message></th><td><span wicket:id="repositoryOwners"><span wicket:id="owner"></span><span wicket:id="comma"></span></span></td></tr>
+				<tr><th><wicket:message key="gb.lastChange">[last change]</wicket:message></th><td><span wicket:id="repositoryLastChange">[repository last change]</span></td></tr>
+				<tr><th><wicket:message key="gb.size">[size]</wicket:message></th><td><span wicket:id="repositorySize">[repository size]</span></td></tr>
+				<tr><th><wicket:message key="gb.stats">[stats]</wicket:message></th><td><span wicket:id="branchStats">[branch stats]</span> <span class="link"><a wicket:id="metrics"><wicket:message key="gb.metrics">[metrics]</wicket:message></a></span></td></tr>
+				<tr class="hidden-tablet"><th style="vertical-align:top;padding-top:4px;"><wicket:message key="gb.repositoryUrl">[URL]</wicket:message></th>
+				    <td><div wicket:id="repositoryUrlPanel">[repository url panel]</div></td>
+				</tr>
+			</table>
+		</div>
+	</div>
+
+	<!-- commits -->
+	<div style="padding-bottom:15px;padding-top:5px;" wicket:id="commitsPanel">[commits panel]</div>	
+
+	<!-- tags -->
+	<div style="padding-bottom:15px;" wicket:id="tagsPanel">[tags panel]</div>
+
+	<!-- branches -->
+	<div style="padding-bottom:15px;" wicket:id="branchesPanel">[branches panel]</div>
+	
+	<!-- markdown readme -->
+	<div wicket:id="readme"></div>
+	
+	<wicket:fragment wicket:id="markdownPanel">
+		<div class="header" style="margin-top:0px;" >
+			<i style="vertical-align: middle;" class="icon-book"></i>
+			<span style="font-weight:bold;vertical-align:middle;" wicket:id="readmeFile"></span>
+		</div>
+		<div style="border:1px solid #ddd;border-radius: 0 0 3px 3px;padding: 20px;">
+			<div wicket:id="readmeContent" class="markdown"></div>
+		</div>
+	</wicket:fragment>
+	
+	<wicket:fragment wicket:id="ownersFragment">
+		
+	</wicket:fragment>
+	
+</wicket:extend>	
+</body>
+</html>
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/pages/SummaryPage.java b/src/main/java/com/gitblit/wicket/pages/SummaryPage.java
new file mode 100644
index 0000000..17c41eb
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/SummaryPage.java
@@ -0,0 +1,215 @@
+/*
+ * 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.wicket.pages;
+
+import java.awt.Color;
+import java.awt.Dimension;
+import java.text.MessageFormat;
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.wicket.Component;
+import org.apache.wicket.PageParameters;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.link.BookmarkablePageLink;
+import org.apache.wicket.markup.html.panel.Fragment;
+import org.apache.wicket.markup.repeater.Item;
+import org.apache.wicket.markup.repeater.data.DataView;
+import org.apache.wicket.markup.repeater.data.ListDataProvider;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.wicketstuff.googlecharts.ChartAxis;
+import org.wicketstuff.googlecharts.ChartAxisType;
+import org.wicketstuff.googlecharts.ChartProvider;
+import org.wicketstuff.googlecharts.ChartType;
+import org.wicketstuff.googlecharts.IChartData;
+import org.wicketstuff.googlecharts.LineStyle;
+import org.wicketstuff.googlecharts.MarkerType;
+import org.wicketstuff.googlecharts.ShapeMarker;
+
+import com.gitblit.GitBlit;
+import com.gitblit.Keys;
+import com.gitblit.models.Metric;
+import com.gitblit.models.PathModel;
+import com.gitblit.models.RepositoryModel;
+import com.gitblit.models.UserModel;
+import com.gitblit.utils.JGitUtils;
+import com.gitblit.utils.MarkdownUtils;
+import com.gitblit.utils.StringUtils;
+import com.gitblit.wicket.CacheControl;
+import com.gitblit.wicket.GitBlitWebSession;
+import com.gitblit.wicket.WicketUtils;
+import com.gitblit.wicket.CacheControl.LastModified;
+import com.gitblit.wicket.charting.SecureChart;
+import com.gitblit.wicket.panels.BranchesPanel;
+import com.gitblit.wicket.panels.LinkPanel;
+import com.gitblit.wicket.panels.LogPanel;
+import com.gitblit.wicket.panels.RepositoryUrlPanel;
+import com.gitblit.wicket.panels.TagsPanel;
+
+@CacheControl(LastModified.REPOSITORY)
+public class SummaryPage extends RepositoryPage {
+
+	public SummaryPage(PageParameters params) {
+		super(params);
+
+		int numberCommits = GitBlit.getInteger(Keys.web.summaryCommitCount, 20);
+		if (numberCommits <= 0) {
+			numberCommits = 20;
+		}
+		int numberRefs = GitBlit.getInteger(Keys.web.summaryRefsCount, 5);
+
+		Repository r = getRepository();
+		final RepositoryModel model = getRepositoryModel();
+		UserModel user = GitBlitWebSession.get().getUser();
+		if (user == null) {
+			user = UserModel.ANONYMOUS;
+		}
+
+		List<Metric> metrics = null;
+		Metric metricsTotal = null;
+		if (!model.skipSummaryMetrics && GitBlit.getBoolean(Keys.web.generateActivityGraph, true)) {
+			metrics = GitBlit.self().getRepositoryDefaultMetrics(model, r);
+			metricsTotal = metrics.remove(0);
+		}
+
+		addSyndicationDiscoveryLink();
+
+		// repository description
+		add(new Label("repositoryDescription", getRepositoryModel().description));
+		
+		// owner links
+		final List<String> owners = new ArrayList<String>(getRepositoryModel().owners);
+		ListDataProvider<String> ownersDp = new ListDataProvider<String>(owners);
+		DataView<String> ownersView = new DataView<String>("repositoryOwners", ownersDp) {
+			private static final long serialVersionUID = 1L;
+			int counter = 0;
+			public void populateItem(final Item<String> item) {
+				String ownername = item.getModelObject();
+				UserModel ownerModel = GitBlit.self().getUserModel(ownername);
+				if (ownerModel != null) {
+					item.add(new LinkPanel("owner", null, ownerModel.getDisplayName(), UserPage.class,
+							WicketUtils.newUsernameParameter(ownerModel.username)).setRenderBodyOnly(true));
+				} else {
+					Label owner = new Label("owner", ownername);
+					WicketUtils.setCssStyle(owner, "text-decoration: line-through;");
+					WicketUtils.setHtmlTooltip(owner,  MessageFormat.format(getString("gb.failedToFindAccount"), ownername));
+					item.add(owner);
+				}
+				counter++;
+				item.add(new Label("comma", ",").setVisible(counter < owners.size()));
+				item.setRenderBodyOnly(true);
+			}
+		};
+		ownersView.setRenderBodyOnly(true);
+		add(ownersView);
+		
+		add(WicketUtils.createTimestampLabel("repositoryLastChange",
+				JGitUtils.getLastChange(r).when, getTimeZone(), getTimeUtils()));
+		add(new Label("repositorySize", getRepositoryModel().size));
+		if (metricsTotal == null) {
+			add(new Label("branchStats", ""));
+		} else {
+			add(new Label("branchStats",
+					MessageFormat.format(getString("gb.branchStats"), metricsTotal.count,
+							metricsTotal.tag, getTimeUtils().duration(metricsTotal.duration))));
+		}
+		add(new BookmarkablePageLink<Void>("metrics", MetricsPage.class,
+				WicketUtils.newRepositoryParameter(repositoryName)));
+
+		add(new RepositoryUrlPanel("repositoryUrlPanel", false, user, model));
+				
+		add(new LogPanel("commitsPanel", repositoryName, getRepositoryModel().HEAD, r, numberCommits, 0, getRepositoryModel().showRemoteBranches));
+		add(new TagsPanel("tagsPanel", repositoryName, r, numberRefs).hideIfEmpty());
+		add(new BranchesPanel("branchesPanel", getRepositoryModel(), r, numberRefs, false).hideIfEmpty());
+
+		if (getRepositoryModel().showReadme) {
+			String htmlText = null;
+			String markdownText = null;
+			String readme = null;
+			try {
+				RevCommit head = JGitUtils.getCommit(r, null);
+				List<String> markdownExtensions = GitBlit.getStrings(Keys.web.markdownExtensions);
+				List<PathModel> paths = JGitUtils.getFilesInPath(r, null, head);				
+				for (PathModel path : paths) {
+					if (!path.isTree()) {
+						String name = path.name.toLowerCase();
+
+						if (name.startsWith("readme")) {
+							if (name.indexOf('.') > -1) {
+								String ext = name.substring(name.lastIndexOf('.') + 1);
+								if (markdownExtensions.contains(ext)) {
+									readme = path.name;
+									break;
+								}
+							}
+						}
+					}
+				}
+				if (!StringUtils.isEmpty(readme)) {
+					String [] encodings = GitBlit.getEncodings();
+					markdownText = JGitUtils.getStringContent(r, head.getTree(), readme, encodings);
+					htmlText = MarkdownUtils.transformMarkdown(markdownText);
+				}
+			} catch (ParseException p) {
+				markdownText = MessageFormat.format("<div class=\"alert alert-error\"><strong>{0}:</strong> {1}</div>{2}", getString("gb.error"), getString("gb.markdownFailure"), markdownText);
+				htmlText = StringUtils.breakLinesForHtml(markdownText);
+			}
+			Fragment fragment = new Fragment("readme", "markdownPanel");
+			fragment.add(new Label("readmeFile", readme));
+			// Add the html to the page
+			Component content = new Label("readmeContent", htmlText).setEscapeModelStrings(false);
+			fragment.add(content.setVisible(!StringUtils.isEmpty(htmlText)));
+			add(fragment);
+		} else {
+			add(new Label("readme").setVisible(false));
+		}
+
+		// Display an activity line graph
+		insertActivityGraph(metrics);
+	}
+
+	@Override
+	protected String getPageName() {
+		return getString("gb.summary");
+	}
+
+	private void insertActivityGraph(List<Metric> metrics) {
+		if ((metrics != null) && (metrics.size() > 0)
+				&& GitBlit.getBoolean(Keys.web.generateActivityGraph, true)) {
+			IChartData data = WicketUtils.getChartData(metrics);
+
+			ChartProvider provider = new ChartProvider(new Dimension(290, 100), ChartType.LINE,
+					data);
+			ChartAxis dateAxis = new ChartAxis(ChartAxisType.BOTTOM);
+			dateAxis.setLabels(new String[] { metrics.get(0).name,
+					metrics.get(metrics.size() / 2).name, metrics.get(metrics.size() - 1).name });
+			provider.addAxis(dateAxis);
+
+			ChartAxis commitAxis = new ChartAxis(ChartAxisType.LEFT);
+			commitAxis.setLabels(new String[] { "",
+					String.valueOf((int) WicketUtils.maxValue(metrics)) });
+			provider.addAxis(commitAxis);
+			provider.setLineStyles(new LineStyle[] { new LineStyle(2, 4, 0), new LineStyle(0, 4, 1) });
+			provider.addShapeMarker(new ShapeMarker(MarkerType.CIRCLE, Color.decode("#002060"), 1, -1, 5));
+
+			add(new SecureChart("commitsChart", provider));
+		} else {
+			add(WicketUtils.newBlankImage("commitsChart"));
+		}
+	}
+}
diff --git a/src/main/java/com/gitblit/wicket/pages/TagPage.html b/src/main/java/com/gitblit/wicket/pages/TagPage.html
new file mode 100644
index 0000000..e719a39
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/TagPage.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"  
+      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"  
+      xml:lang="en"  
+      lang="en"> 
+
+<body>
+<wicket:extend>
+	
+	<!-- Tagger Gravatar -->
+	<span style="float:right;vertical-align: top;" wicket:id="taggerAvatar" />
+	
+	<div style="padding-bottom:10px;">
+	<!-- commit info -->
+	<table class="summary">
+		<tr><th><wicket:message key="gb.name">[name]</wicket:message></th><td><span wicket:id="tagName">[tag name]</span></td></tr>
+		<tr><th><wicket:message key="gb.tag">[tag]</wicket:message></th><td><span class="sha1" wicket:id="tagId">[tag id]</span></td></tr>
+		<tr><th><wicket:message key="gb.object">[object]</wicket:message></th><td><span class="sha1" wicket:id="taggedObject">[tagged object]</span> <span class="link" wicket:id="taggedObjectType"></span></td></tr>
+		<tr><th><wicket:message key="gb.tagger">[tagger]</wicket:message></th><td><span class="sha1" wicket:id="tagger">[tagger]</span></td></tr>
+		<tr><th></th><td><span class="sha1" wicket:id="tagDate">[tag date]</span></td></tr>
+	</table>
+	</div>
+	
+	<!--  full message -->
+	<pre class="commit_message" wicket:id="fullMessage">[commit message]</pre>
+	
+	<wicket:fragment wicket:id="fullPersonIdent">
+		<span wicket:id="personName"></span><span wicket:id="personAddress"></span>
+	</wicket:fragment>
+	
+	<wicket:fragment wicket:id="partialPersonIdent">
+		<span wicket:id="personName"></span>
+	</wicket:fragment>
+	
+</wicket:extend>
+</body>
+</html>
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/pages/TagPage.java b/src/main/java/com/gitblit/wicket/pages/TagPage.java
new file mode 100644
index 0000000..13c1011
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/TagPage.java
@@ -0,0 +1,107 @@
+/*
+ * 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.wicket.pages;
+
+import java.text.MessageFormat;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+
+import org.apache.wicket.PageParameters;
+import org.apache.wicket.markup.html.WebPage;
+import org.apache.wicket.markup.html.basic.Label;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.Repository;
+
+import com.gitblit.models.RefModel;
+import com.gitblit.utils.JGitUtils;
+import com.gitblit.wicket.CacheControl;
+import com.gitblit.wicket.CacheControl.LastModified;
+import com.gitblit.wicket.WicketUtils;
+import com.gitblit.wicket.panels.GravatarImage;
+import com.gitblit.wicket.panels.LinkPanel;
+import com.gitblit.wicket.panels.RefsPanel;
+
+@CacheControl(LastModified.BOOT)
+public class TagPage extends RepositoryPage {
+
+	public TagPage(PageParameters params) {
+		super(params);
+
+		Repository r = getRepository();
+
+		// Find tag in repository
+		List<RefModel> tags = JGitUtils.getTags(r, true, -1);
+		RefModel tagRef = null;
+		for (RefModel tag : tags) {
+			if (tag.getName().equals(objectId) || tag.getObjectId().getName().equals(objectId)) {
+				tagRef = tag;
+				break;
+			}
+		}
+
+		// Failed to find tag!
+		if (tagRef == null) {
+			error(MessageFormat.format(getString("gb.couldNotFindTag"), objectId), true);
+		}
+
+		// Display tag.
+		Class<? extends WebPage> linkClass;
+		PageParameters linkParameters = newCommitParameter(tagRef.getReferencedObjectId().getName());
+		String typeKey;
+		switch (tagRef.getReferencedObjectType()) {
+		case Constants.OBJ_BLOB:
+			typeKey = "gb.blob";
+			linkClass = BlobPage.class;
+			break;
+		case Constants.OBJ_TREE:
+			typeKey = "gb.tree";
+			linkClass = TreePage.class;
+			break;
+		case Constants.OBJ_COMMIT:
+		default:
+			typeKey = "gb.commit";
+			linkClass = CommitPage.class;
+			break;
+		}
+		add(new GravatarImage("taggerAvatar", tagRef.getAuthorIdent()));
+		
+		add(new RefsPanel("tagName", repositoryName, Arrays.asList(tagRef)));
+		add(new Label("tagId", tagRef.getObjectId().getName()));
+		add(new LinkPanel("taggedObject", "list", tagRef.getReferencedObjectId().getName(),
+				linkClass, linkParameters));
+		add(new Label("taggedObjectType", getString(typeKey)));
+
+		add(createPersonPanel("tagger", tagRef.getAuthorIdent(), com.gitblit.Constants.SearchType.AUTHOR));
+		Date when = new Date(0);
+		if (tagRef.getAuthorIdent() != null) {
+			when = tagRef.getAuthorIdent().getWhen();
+		}
+		add(WicketUtils.createTimestampLabel("tagDate", when, getTimeZone(), getTimeUtils()));
+
+		addFullText("fullMessage", tagRef.getFullMessage(), true);
+	}
+
+	@Override
+	protected String getPageName() {
+		return getString("gb.tag");
+	}
+	
+	@Override
+	protected Class<? extends BasePage> getRepoNavPageClass() {
+		return LogPage.class;
+	}
+}
diff --git a/src/com/gitblit/wicket/pages/TagsPage.html b/src/main/java/com/gitblit/wicket/pages/TagsPage.html
similarity index 100%
rename from src/com/gitblit/wicket/pages/TagsPage.html
rename to src/main/java/com/gitblit/wicket/pages/TagsPage.html
diff --git a/src/main/java/com/gitblit/wicket/pages/TagsPage.java b/src/main/java/com/gitblit/wicket/pages/TagsPage.java
new file mode 100644
index 0000000..2abb410
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/TagsPage.java
@@ -0,0 +1,43 @@
+/*
+ * 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.wicket.pages;
+
+import org.apache.wicket.PageParameters;
+
+import com.gitblit.wicket.CacheControl;
+import com.gitblit.wicket.CacheControl.LastModified;
+import com.gitblit.wicket.panels.TagsPanel;
+
+@CacheControl(LastModified.REPOSITORY)
+public class TagsPage extends RepositoryPage {
+
+	public TagsPage(PageParameters params) {
+		super(params);
+
+		add(new TagsPanel("tagsPanel", repositoryName, getRepository(), -1));
+
+	}
+
+	@Override
+	protected String getPageName() {
+		return getString("gb.tags");
+	}
+	
+	@Override
+	protected Class<? extends BasePage> getRepoNavPageClass() {
+		return LogPage.class;
+	}
+}
diff --git a/src/com/gitblit/wicket/pages/TicketPage.html b/src/main/java/com/gitblit/wicket/pages/TicketPage.html
similarity index 100%
rename from src/com/gitblit/wicket/pages/TicketPage.html
rename to src/main/java/com/gitblit/wicket/pages/TicketPage.html
diff --git a/src/main/java/com/gitblit/wicket/pages/TicketPage.java b/src/main/java/com/gitblit/wicket/pages/TicketPage.java
new file mode 100644
index 0000000..fd857be
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/TicketPage.java
@@ -0,0 +1,86 @@
+/*
+ * 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.wicket.pages;
+
+import org.apache.wicket.PageParameters;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.repeater.Item;
+import org.apache.wicket.markup.repeater.data.DataView;
+import org.apache.wicket.markup.repeater.data.ListDataProvider;
+import org.eclipse.jgit.lib.Repository;
+
+import com.gitblit.models.TicketModel;
+import com.gitblit.models.TicketModel.Comment;
+import com.gitblit.utils.StringUtils;
+import com.gitblit.utils.TicgitUtils;
+import com.gitblit.wicket.GitBlitWebSession;
+import com.gitblit.wicket.WicketUtils;
+
+public class TicketPage extends RepositoryPage {
+
+	public TicketPage(PageParameters params) {
+		super(params);
+
+		final String ticketFolder = WicketUtils.getPath(params);
+
+		Repository r = getRepository();
+		TicketModel t = TicgitUtils.getTicket(r, ticketFolder);
+
+		add(new Label("ticketTitle", t.title));
+		add(new Label("ticketId", t.id));
+		add(new Label("ticketHandler", t.handler.toLowerCase()));
+		add(WicketUtils.createTimestampLabel("ticketOpenDate", t.date, getTimeZone(), getTimeUtils()));
+		Label stateLabel = new Label("ticketState", t.state);
+		WicketUtils.setTicketCssClass(stateLabel, t.state);
+		add(stateLabel);
+		add(new Label("ticketTags", StringUtils.flattenStrings(t.tags)));
+
+		ListDataProvider<Comment> commentsDp = new ListDataProvider<Comment>(t.comments);
+		DataView<Comment> commentsView = new DataView<Comment>("comment", commentsDp) {
+			private static final long serialVersionUID = 1L;
+			int counter;
+
+			public void populateItem(final Item<Comment> item) {
+				final Comment entry = item.getModelObject();
+				item.add(WicketUtils.createDateLabel("commentDate", entry.date, GitBlitWebSession
+						.get().getTimezone(), getTimeUtils()));
+				item.add(new Label("commentAuthor", entry.author.toLowerCase()));
+				item.add(new Label("commentText", prepareComment(entry.text))
+						.setEscapeModelStrings(false));
+				WicketUtils.setAlternatingBackground(item, counter);
+				counter++;
+			}
+		};
+		add(commentsView);
+	}
+
+	@Override
+	protected String getPageName() {
+		return getString("gb.ticket");
+	}
+	
+	@Override
+	protected Class<? extends BasePage> getRepoNavPageClass() {
+		return TicketsPage.class;
+	}
+
+	private String prepareComment(String comment) {
+		String html = StringUtils.escapeForHtml(comment, false);
+		html = StringUtils.breakLinesForHtml(comment).trim();
+		return html.replaceAll("\\bcommit\\s*([A-Za-z0-9]*)\\b", "<a href=\"/commit/"
+				+ repositoryName + "/$1\">commit $1</a>");
+	}
+}
diff --git a/src/main/java/com/gitblit/wicket/pages/TicketsPage.html b/src/main/java/com/gitblit/wicket/pages/TicketsPage.html
new file mode 100644
index 0000000..e1f5f9a
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/TicketsPage.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"  
+      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"  
+      xml:lang="en"  
+      lang="en"> 
+
+<body>
+<wicket:extend>
+
+	<!-- tickets -->	
+	<table class="pretty">
+		<tbody>
+       		<tr wicket:id="ticket">
+         		<td style="padding:0; margin:0;"><div wicket:id="ticketState">[ticket state]</div></td>
+         		<td class="date"><span wicket:id="ticketDate">[ticket date]</span></td>
+         		<td class="author"><div wicket:id="ticketHandler">[ticket handler]</div></td>
+         		<td><div wicket:id="ticketTitle">[ticket title]</div></td>
+       		</tr>
+    	</tbody>
+	</table>	
+
+</wicket:extend>
+</body>
+</html>
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/pages/TicketsPage.java b/src/main/java/com/gitblit/wicket/pages/TicketsPage.java
new file mode 100644
index 0000000..3ec1a54
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/TicketsPage.java
@@ -0,0 +1,72 @@
+/*
+ * 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.wicket.pages;
+
+import java.util.List;
+
+import org.apache.wicket.PageParameters;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.repeater.Item;
+import org.apache.wicket.markup.repeater.data.DataView;
+import org.apache.wicket.markup.repeater.data.ListDataProvider;
+
+import com.gitblit.models.TicketModel;
+import com.gitblit.utils.StringUtils;
+import com.gitblit.utils.TicgitUtils;
+import com.gitblit.wicket.GitBlitWebSession;
+import com.gitblit.wicket.WicketUtils;
+import com.gitblit.wicket.panels.LinkPanel;
+
+public class TicketsPage extends RepositoryPage {
+
+	public TicketsPage(PageParameters params) {
+		super(params);
+
+		List<TicketModel> tickets = TicgitUtils.getTickets(getRepository());
+
+		ListDataProvider<TicketModel> ticketsDp = new ListDataProvider<TicketModel>(tickets);
+		DataView<TicketModel> ticketsView = new DataView<TicketModel>("ticket", ticketsDp) {
+			private static final long serialVersionUID = 1L;
+			int counter;
+
+			public void populateItem(final Item<TicketModel> item) {
+				final TicketModel entry = item.getModelObject();
+				Label stateLabel = new Label("ticketState", entry.state);
+				WicketUtils.setTicketCssClass(stateLabel, entry.state);
+				item.add(stateLabel);
+				item.add(WicketUtils.createDateLabel("ticketDate", entry.date, GitBlitWebSession
+						.get().getTimezone(), getTimeUtils()));
+				item.add(new Label("ticketHandler", StringUtils.trimString(
+						entry.handler.toLowerCase(), 30)));
+				item.add(new LinkPanel("ticketTitle", "list subject", StringUtils.trimString(
+						entry.title, 80), TicketPage.class, newPathParameter(entry.name)));
+
+				WicketUtils.setAlternatingBackground(item, counter);
+				counter++;
+			}
+		};
+		add(ticketsView);
+	}
+
+	protected PageParameters newPathParameter(String path) {
+		return WicketUtils.newPathParameter(repositoryName, objectId, path);
+	}
+
+	@Override
+	protected String getPageName() {
+		return getString("gb.tickets");
+	}
+}
diff --git a/src/com/gitblit/wicket/pages/TreePage.html b/src/main/java/com/gitblit/wicket/pages/TreePage.html
similarity index 100%
rename from src/com/gitblit/wicket/pages/TreePage.html
rename to src/main/java/com/gitblit/wicket/pages/TreePage.html
diff --git a/src/main/java/com/gitblit/wicket/pages/TreePage.java b/src/main/java/com/gitblit/wicket/pages/TreePage.java
new file mode 100644
index 0000000..25bcd67
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/TreePage.java
@@ -0,0 +1,189 @@
+/*
+ * 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.wicket.pages;
+
+import java.util.List;
+
+import org.apache.wicket.PageParameters;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.link.BookmarkablePageLink;
+import org.apache.wicket.markup.html.panel.Fragment;
+import org.apache.wicket.markup.repeater.Item;
+import org.apache.wicket.markup.repeater.data.DataView;
+import org.apache.wicket.markup.repeater.data.ListDataProvider;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.FileMode;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+
+import com.gitblit.models.PathModel;
+import com.gitblit.models.SubmoduleModel;
+import com.gitblit.utils.ByteFormat;
+import com.gitblit.utils.JGitUtils;
+import com.gitblit.wicket.CacheControl;
+import com.gitblit.wicket.CacheControl.LastModified;
+import com.gitblit.wicket.WicketUtils;
+import com.gitblit.wicket.panels.CommitHeaderPanel;
+import com.gitblit.wicket.panels.CompressedDownloadsPanel;
+import com.gitblit.wicket.panels.LinkPanel;
+import com.gitblit.wicket.panels.PathBreadcrumbsPanel;
+
+@CacheControl(LastModified.BOOT)
+public class TreePage extends RepositoryPage {
+
+	public TreePage(PageParameters params) {
+		super(params);
+
+		final String path = WicketUtils.getPath(params);
+
+		Repository r = getRepository();
+		RevCommit commit = getCommit();
+		List<PathModel> paths = JGitUtils.getFilesInPath(r, path, commit);
+
+		// tree page links
+		add(new BookmarkablePageLink<Void>("historyLink", HistoryPage.class,
+				WicketUtils.newPathParameter(repositoryName, objectId, path)));
+		add(new BookmarkablePageLink<Void>("headLink", TreePage.class,
+				WicketUtils.newPathParameter(repositoryName, Constants.HEAD, path)));
+		add(new CompressedDownloadsPanel("compressedLinks", getRequest()
+				.getRelativePathPrefixToContextRoot(), repositoryName, objectId, path));
+
+		add(new CommitHeaderPanel("commitHeader", repositoryName, commit));
+
+		// breadcrumbs
+		add(new PathBreadcrumbsPanel("breadcrumbs", repositoryName, path, objectId));
+		if (path != null && path.trim().length() > 0) {
+			// add .. parent path entry
+			String parentPath = null;
+			if (path.lastIndexOf('/') > -1) {
+				parentPath = path.substring(0, path.lastIndexOf('/'));
+			}
+			PathModel model = new PathModel("..", parentPath, 0, FileMode.TREE.getBits(), null, objectId);
+			model.isParentPath = true;
+			paths.add(0, model);
+		}
+
+		final ByteFormat byteFormat = new ByteFormat();
+
+		final String baseUrl = WicketUtils.getGitblitURL(getRequest());
+
+		// changed paths list
+		ListDataProvider<PathModel> pathsDp = new ListDataProvider<PathModel>(paths);
+		DataView<PathModel> pathsView = new DataView<PathModel>("changedPath", pathsDp) {
+			private static final long serialVersionUID = 1L;
+			int counter;
+
+			public void populateItem(final Item<PathModel> item) {
+				PathModel entry = item.getModelObject();
+				item.add(new Label("pathPermissions", JGitUtils.getPermissionsFromMode(entry.mode)));
+				if (entry.isParentPath) {
+					// parent .. path
+					item.add(WicketUtils.newBlankImage("pathIcon"));
+					item.add(new Label("pathSize", ""));
+					item.add(new LinkPanel("pathName", null, entry.name, TreePage.class,
+							WicketUtils
+									.newPathParameter(repositoryName, entry.commitId, entry.path)));
+					item.add(new Label("pathLinks", ""));
+				} else {
+					if (entry.isTree()) {
+						// folder/tree link
+						item.add(WicketUtils.newImage("pathIcon", "folder_16x16.png"));
+						item.add(new Label("pathSize", ""));
+						item.add(new LinkPanel("pathName", "list", entry.name, TreePage.class,
+								WicketUtils.newPathParameter(repositoryName, entry.commitId,
+										entry.path)));
+
+						// links
+						Fragment links = new Fragment("pathLinks", "treeLinks", this);
+						links.add(new BookmarkablePageLink<Void>("tree", TreePage.class,
+								WicketUtils.newPathParameter(repositoryName, entry.commitId,
+										entry.path)));
+						links.add(new BookmarkablePageLink<Void>("history", HistoryPage.class,
+								WicketUtils.newPathParameter(repositoryName, entry.commitId,
+										entry.path)));						
+						links.add(new CompressedDownloadsPanel("compressedLinks", baseUrl,
+								repositoryName, objectId, entry.path));
+
+						item.add(links);
+					} else if (entry.isSubmodule()) {
+						// submodule
+						String submoduleId = entry.objectId;						
+						String submodulePath;
+						boolean hasSubmodule = false;
+						SubmoduleModel submodule = getSubmodule(entry.path);
+						submodulePath = submodule.gitblitPath;
+						hasSubmodule = submodule.hasSubmodule;
+						
+						item.add(WicketUtils.newImage("pathIcon", "git-orange-16x16.png"));
+						item.add(new Label("pathSize", ""));
+						item.add(new LinkPanel("pathName", "list", entry.name + " @ " + 
+								getShortObjectId(submoduleId), TreePage.class,
+								WicketUtils.newPathParameter(submodulePath, submoduleId, "")).setEnabled(hasSubmodule));
+						
+						Fragment links = new Fragment("pathLinks", "submoduleLinks", this);
+						links.add(new BookmarkablePageLink<Void>("view", SummaryPage.class,
+								WicketUtils.newRepositoryParameter(submodulePath)).setEnabled(hasSubmodule));
+						links.add(new BookmarkablePageLink<Void>("tree", TreePage.class,
+								WicketUtils.newPathParameter(submodulePath, submoduleId,
+										"")).setEnabled(hasSubmodule));
+						links.add(new BookmarkablePageLink<Void>("history", HistoryPage.class,
+								WicketUtils.newPathParameter(repositoryName, entry.commitId,
+										entry.path)));
+						links.add(new CompressedDownloadsPanel("compressedLinks", baseUrl,
+								submodulePath, submoduleId, "").setEnabled(hasSubmodule));
+						item.add(links);						
+					} else {
+						// blob link
+						String displayPath = entry.name;
+						String path = entry.path;
+						if (entry.isSymlink()) {
+							path = JGitUtils.getStringContent(getRepository(), getCommit().getTree(), path);
+							displayPath = entry.name + " -> " + path;
+						}
+						item.add(WicketUtils.getFileImage("pathIcon", entry.name));
+						item.add(new Label("pathSize", byteFormat.format(entry.size)));
+						item.add(new LinkPanel("pathName", "list", displayPath, BlobPage.class,
+								WicketUtils.newPathParameter(repositoryName, entry.commitId,
+										path)));
+
+						// links
+						Fragment links = new Fragment("pathLinks", "blobLinks", this);
+						links.add(new BookmarkablePageLink<Void>("view", BlobPage.class,
+								WicketUtils.newPathParameter(repositoryName, entry.commitId,
+										path)));
+						links.add(new BookmarkablePageLink<Void>("raw", RawPage.class, WicketUtils
+								.newPathParameter(repositoryName, entry.commitId, path)));
+						links.add(new BookmarkablePageLink<Void>("blame", BlamePage.class,
+								WicketUtils.newPathParameter(repositoryName, entry.commitId,
+										path)));
+						links.add(new BookmarkablePageLink<Void>("history", HistoryPage.class,
+								WicketUtils.newPathParameter(repositoryName, entry.commitId,
+										path)));
+						item.add(links);
+					}
+				}
+				WicketUtils.setAlternatingBackground(item, counter);
+				counter++;
+			}
+		};
+		add(pathsView);
+	}
+
+	@Override
+	protected String getPageName() {
+		return getString("gb.tree");
+	}
+}
diff --git a/src/main/java/com/gitblit/wicket/pages/UserPage.html b/src/main/java/com/gitblit/wicket/pages/UserPage.html
new file mode 100644
index 0000000..7aaded7
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/UserPage.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"  
+      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"  
+      xml:lang="en"  
+      lang="en"> 
+
+<body>
+<wicket:extend>
+<div class="container">
+	<div class="row" style="padding-top:10px;">
+		<div class="span4">
+			<div wicket:id="gravatar"></div>
+			<div style="text-align: left;">
+				<h2><span wicket:id="userDisplayName"></span></h2>
+				<div><i class="icon-user"></i> <span wicket:id="userUsername"></span></div>
+				<div><i class="icon-envelope"></i><span wicket:id="userEmail"></span></div>
+			</div>
+		</div>
+		
+		<div class="span8">
+			<div class="pull-right">
+				<a class="btn-small" wicket:id="newRepository" style="padding-right:0px;">
+					<i class="icon icon-plus-sign"></i>
+					<wicket:message key="gb.newRepository"></wicket:message>
+				</a>
+			</div>
+			<div class="tabbable">
+				<!-- tab titles -->
+				<ul class="nav nav-tabs">
+					<li class="active"><a href="#repositories" data-toggle="tab"><wicket:message key="gb.repositories"></wicket:message></a></li>
+				</ul>
+	
+				<!-- tab content -->
+				<div class="tab-content">
+
+					<!-- repositories tab -->
+					<div class="tab-pane active" id="repositories">
+						<table width="100%">
+							<tbody>
+								<tr wicket:id="repositoryList"><td style="border-bottom:1px solid #eee;"><span wicket:id="repository"></span></td></tr>
+							</tbody>
+						</table>
+					</div>
+				</div>
+			</div>
+		</div>
+	</div>
+</div>
+</wicket:extend>
+</body>
+</html>
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/pages/UserPage.java b/src/main/java/com/gitblit/wicket/pages/UserPage.java
similarity index 100%
rename from src/com/gitblit/wicket/pages/UserPage.java
rename to src/main/java/com/gitblit/wicket/pages/UserPage.java
diff --git a/src/main/java/com/gitblit/wicket/pages/UsersPage.html b/src/main/java/com/gitblit/wicket/pages/UsersPage.html
new file mode 100644
index 0000000..6eec358
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/UsersPage.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"  
+      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"  
+      xml:lang="en"  
+      lang="en"> 
+<body>
+<wicket:extend>
+<div class="container">
+	<div wicket:id="teamsPanel">[teams panel]</div>
+
+	<div wicket:id="usersPanel">[users panel]</div>
+</div>
+</wicket:extend>
+</body>
+</html>
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/pages/UsersPage.java b/src/main/java/com/gitblit/wicket/pages/UsersPage.java
similarity index 100%
rename from src/com/gitblit/wicket/pages/UsersPage.java
rename to src/main/java/com/gitblit/wicket/pages/UsersPage.java
diff --git a/src/main/java/com/gitblit/wicket/pages/prettify/lang-apollo.js b/src/main/java/com/gitblit/wicket/pages/prettify/lang-apollo.js
new file mode 100644
index 0000000..99e4a97
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/prettify/lang-apollo.js
@@ -0,0 +1,2 @@
+PR.registerLangHandler(PR.createSimpleLexer([["com",/^#[^\n\r]*/,null,"#"],["pln",/^[\t\n\r \xa0]+/,null,"\t\n\r \u00a0"],["str",/^"(?:[^"\\]|\\[\S\s])*(?:"|$)/,null,'"']],[["kwd",/^(?:ADS|AD|AUG|BZF|BZMF|CAE|CAF|CA|CCS|COM|CS|DAS|DCA|DCOM|DCS|DDOUBL|DIM|DOUBLE|DTCB|DTCF|DV|DXCH|EDRUPT|EXTEND|INCR|INDEX|NDX|INHINT|LXCH|MASK|MSK|MP|MSU|NOOP|OVSK|QXCH|RAND|READ|RELINT|RESUME|RETURN|ROR|RXOR|SQUARE|SU|TCR|TCAA|OVSK|TCF|TC|TS|WAND|WOR|WRITE|XCH|XLQ|XXALQ|ZL|ZQ|ADD|ADZ|SUB|SUZ|MPY|MPR|MPZ|DVP|COM|ABS|CLA|CLZ|LDQ|STO|STQ|ALS|LLS|LRS|TRA|TSQ|TMI|TOV|AXT|TIX|DLY|INP|OUT)\s/,
+null],["typ",/^(?:-?GENADR|=MINUS|2BCADR|VN|BOF|MM|-?2CADR|-?[1-6]DNADR|ADRES|BBCON|[ES]?BANK=?|BLOCK|BNKSUM|E?CADR|COUNT\*?|2?DEC\*?|-?DNCHAN|-?DNPTR|EQUALS|ERASE|MEMORY|2?OCT|REMADR|SETLOC|SUBRO|ORG|BSS|BES|SYN|EQU|DEFINE|END)\s/,null],["lit",/^'(?:-*(?:\w|\\[!-~])(?:[\w-]*|\\[!-~])[!=?]?)?/],["pln",/^-*(?:[!-z]|\\[!-~])(?:[\w-]*|\\[!-~])[!=?]?/],["pun",/^[^\w\t\n\r "'-);\\\xa0]+/]]),["apollo","agc","aea"]);
diff --git a/src/main/java/com/gitblit/wicket/pages/prettify/lang-basic.js b/src/main/java/com/gitblit/wicket/pages/prettify/lang-basic.js
new file mode 100644
index 0000000..6b784d4
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/prettify/lang-basic.js
@@ -0,0 +1,3 @@
+var a=null;
+PR.registerLangHandler(PR.createSimpleLexer([["str",/^"(?:[^\n\r"\\]|\\.)*(?:"|$)/,a,'"'],["pln",/^\s+/,a," \r\n\t\u00a0"]],[["com",/^REM[^\n\r]*/,a],["kwd",/^\b(?:AND|CLOSE|CLR|CMD|CONT|DATA|DEF ?FN|DIM|END|FOR|GET|GOSUB|GOTO|IF|INPUT|LET|LIST|LOAD|NEW|NEXT|NOT|ON|OPEN|OR|POKE|PRINT|READ|RESTORE|RETURN|RUN|SAVE|STEP|STOP|SYS|THEN|TO|VERIFY|WAIT)\b/,a],["pln",/^[a-z][^\W_]?(?:\$|%)?/i,a],["lit",/^(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?/i,a,"0123456789"],["pun",
+/^.[^\s\w"$%.]*/,a]]),["basic","cbm"]);
diff --git a/src/main/java/com/gitblit/wicket/pages/prettify/lang-clj.js b/src/main/java/com/gitblit/wicket/pages/prettify/lang-clj.js
new file mode 100644
index 0000000..1bb539c
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/prettify/lang-clj.js
@@ -0,0 +1,18 @@
+/*
+ Copyright (C) 2011 Google Inc.
+
+ 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.
+*/
+var a=null;
+PR.registerLangHandler(PR.createSimpleLexer([["opn",/^[([{]+/,a,"([{"],["clo",/^[)\]}]+/,a,")]}"],["com",/^;[^\n\r]*/,a,";"],["pln",/^[\t\n\r \xa0]+/,a,"\t\n\r \u00a0"],["str",/^"(?:[^"\\]|\\[\S\s])*(?:"|$)/,a,'"']],[["kwd",/^(?:def|if|do|let|quote|var|fn|loop|recur|throw|try|monitor-enter|monitor-exit|defmacro|defn|defn-|macroexpand|macroexpand-1|for|doseq|dosync|dotimes|and|or|when|not|assert|doto|proxy|defstruct|first|rest|cons|defprotocol|deftype|defrecord|reify|defmulti|defmethod|meta|with-meta|ns|in-ns|create-ns|import|intern|refer|alias|namespace|resolve|ref|deref|refset|new|set!|memfn|to-array|into-array|aset|gen-class|reduce|map|filter|find|nil?|empty?|hash-map|hash-set|vec|vector|seq|flatten|reverse|assoc|dissoc|list|list?|disj|get|union|difference|intersection|extend|extend-type|extend-protocol|prn)\b/,a],
+["typ",/^:[\dA-Za-z-]+/]]),["clj"]);
diff --git a/src/main/java/com/gitblit/wicket/pages/prettify/lang-css.js b/src/main/java/com/gitblit/wicket/pages/prettify/lang-css.js
new file mode 100644
index 0000000..d7a4640
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/prettify/lang-css.js
@@ -0,0 +1,2 @@
+PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\f\r ]+/,null," \t\r\n\u000c"]],[["str",/^"(?:[^\n\f\r"\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*"/,null],["str",/^'(?:[^\n\f\r'\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*'/,null],["lang-css-str",/^url\(([^"')]+)\)/i],["kwd",/^(?:url|rgb|!important|@import|@page|@media|@charset|inherit)(?=[^\w-]|$)/i,null],["lang-css-kw",/^(-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*)\s*:/i],["com",/^\/\*[^*]*\*+(?:[^*/][^*]*\*+)*\//],
+["com",/^(?:<\!--|--\>)/],["lit",/^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i],["lit",/^#[\da-f]{3,6}\b/i],["pln",/^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i],["pun",/^[^\s\w"']+/]]),["css"]);PR.registerLangHandler(PR.createSimpleLexer([],[["kwd",/^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i]]),["css-kw"]);PR.registerLangHandler(PR.createSimpleLexer([],[["str",/^[^"')]+/]]),["css-str"]);
diff --git a/src/main/java/com/gitblit/wicket/pages/prettify/lang-dart.js b/src/main/java/com/gitblit/wicket/pages/prettify/lang-dart.js
new file mode 100644
index 0000000..eefccc9
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/prettify/lang-dart.js
@@ -0,0 +1,3 @@
+PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\r \xa0]+/,null,"\t\n\r \u00a0"]],[["com",/^#!.*/],["kwd",/^\b(?:import|library|part of|part|as|show|hide)\b/i],["com",/^\/\/.*/],["com",/^\/\*[^*]*\*+(?:[^*/][^*]*\*+)*\//],["kwd",/^\b(?:class|interface)\b/i],["kwd",/^\b(?:assert|break|case|catch|continue|default|do|else|finally|for|if|in|is|new|return|super|switch|this|throw|try|while)\b/i],["kwd",/^\b(?:abstract|const|extends|factory|final|get|implements|native|operator|set|static|typedef|var)\b/i],
+["typ",/^\b(?:bool|double|dynamic|int|num|object|string|void)\b/i],["kwd",/^\b(?:false|null|true)\b/i],["str",/^r?'''[\S\s]*?[^\\]'''/],["str",/^r?"""[\S\s]*?[^\\]"""/],["str",/^r?'('|[^\n\f\r]*?[^\\]')/],["str",/^r?"("|[^\n\f\r]*?[^\\]")/],["pln",/^[$_a-z]\w*/i],["pun",/^[!%&*+/:<-?^|~-]/],["lit",/^\b0x[\da-f]+/i],["lit",/^\b\d+(?:\.\d*)?(?:e[+-]?\d+)?/i],["lit",/^\b\.\d+(?:e[+-]?\d+)?/i],["pun",/^[(),.;[\]{}]/]]),
+["dart"]);
diff --git a/src/main/java/com/gitblit/wicket/pages/prettify/lang-erlang.js b/src/main/java/com/gitblit/wicket/pages/prettify/lang-erlang.js
new file mode 100644
index 0000000..27214a5
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/prettify/lang-erlang.js
@@ -0,0 +1,2 @@
+PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t-\r ]+/,null,"\t\n\u000b\u000c\r "],["str",/^"(?:[^\n\f\r"\\]|\\[\S\s])*(?:"|$)/,null,'"'],["lit",/^[a-z]\w*/],["lit",/^'(?:[^\n\f\r'\\]|\\[^&])+'?/,null,"'"],["lit",/^\?[^\t\n ({]+/,null,"?"],["lit",/^(?:0o[0-7]+|0x[\da-f]+|\d+(?:\.\d+)?(?:e[+-]?\d+)?)/i,null,"0123456789"]],[["com",/^%[^\n]*/],["kwd",/^(?:module|attributes|do|let|in|letrec|apply|call|primop|case|of|end|when|fun|try|catch|receive|after|char|integer|float,atom,string,var)\b/],
+["kwd",/^-[_a-z]+/],["typ",/^[A-Z_]\w*/],["pun",/^[,.;]/]]),["erlang","erl"]);
diff --git a/src/main/java/com/gitblit/wicket/pages/prettify/lang-go.js b/src/main/java/com/gitblit/wicket/pages/prettify/lang-go.js
new file mode 100644
index 0000000..1caca23
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/prettify/lang-go.js
@@ -0,0 +1 @@
+PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\r \xa0]+/,null,"\t\n\r \u00a0"],["pln",/^(?:"(?:[^"\\]|\\[\S\s])*(?:"|$)|'(?:[^'\\]|\\[\S\s])+(?:'|$)|`[^`]*(?:`|$))/,null,"\"'"]],[["com",/^(?:\/\/[^\n\r]*|\/\*[\S\s]*?\*\/)/],["pln",/^(?:[^"'/`]|\/(?![*/]))+/]]),["go"]);
diff --git a/src/main/java/com/gitblit/wicket/pages/prettify/lang-hs.js b/src/main/java/com/gitblit/wicket/pages/prettify/lang-hs.js
new file mode 100644
index 0000000..ff3729b
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/prettify/lang-hs.js
@@ -0,0 +1,2 @@
+PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t-\r ]+/,null,"\t\n\u000b\u000c\r "],["str",/^"(?:[^\n\f\r"\\]|\\[\S\s])*(?:"|$)/,null,'"'],["str",/^'(?:[^\n\f\r'\\]|\\[^&])'?/,null,"'"],["lit",/^(?:0o[0-7]+|0x[\da-f]+|\d+(?:\.\d+)?(?:e[+-]?\d+)?)/i,null,"0123456789"]],[["com",/^(?:--+[^\n\f\r]*|{-(?:[^-]|-+[^}-])*-})/],["kwd",/^(?:case|class|data|default|deriving|do|else|if|import|in|infix|infixl|infixr|instance|let|module|newtype|of|then|type|where|_)(?=[^\d'A-Za-z]|$)/,
+null],["pln",/^(?:[A-Z][\w']*\.)*[A-Za-z][\w']*/],["pun",/^[^\d\t-\r "'A-Za-z]+/]]),["hs"]);
diff --git a/src/main/java/com/gitblit/wicket/pages/prettify/lang-lisp.js b/src/main/java/com/gitblit/wicket/pages/prettify/lang-lisp.js
new file mode 100644
index 0000000..9c8cfa5
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/prettify/lang-lisp.js
@@ -0,0 +1,3 @@
+var a=null;
+PR.registerLangHandler(PR.createSimpleLexer([["opn",/^\(+/,a,"("],["clo",/^\)+/,a,")"],["com",/^;[^\n\r]*/,a,";"],["pln",/^[\t\n\r \xa0]+/,a,"\t\n\r \u00a0"],["str",/^"(?:[^"\\]|\\[\S\s])*(?:"|$)/,a,'"']],[["kwd",/^(?:block|c[ad]+r|catch|con[ds]|def(?:ine|un)|do|eq|eql|equal|equalp|eval-when|flet|format|go|if|labels|lambda|let|load-time-value|locally|macrolet|multiple-value-call|nil|progn|progv|quote|require|return-from|setq|symbol-macrolet|t|tagbody|the|throw|unwind)\b/,a],
+["lit",/^[+-]?(?:[#0]x[\da-f]+|\d+\/\d+|(?:\.\d+|\d+(?:\.\d*)?)(?:[de][+-]?\d+)?)/i],["lit",/^'(?:-*(?:\w|\\[!-~])(?:[\w-]*|\\[!-~])[!=?]?)?/],["pln",/^-*(?:[_a-z]|\\[!-~])(?:[\w-]*|\\[!-~])[!=?]?/i],["pun",/^[^\w\t\n\r "'-);\\\xa0]+/]]),["cl","el","lisp","lsp","scm","ss","rkt"]);
diff --git a/src/main/java/com/gitblit/wicket/pages/prettify/lang-llvm.js b/src/main/java/com/gitblit/wicket/pages/prettify/lang-llvm.js
new file mode 100644
index 0000000..16fade2
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/prettify/lang-llvm.js
@@ -0,0 +1 @@
+PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\r \xa0]+/,null,"\t\n\r \u00a0"],["str",/^!?"(?:[^"\\]|\\[\S\s])*(?:"|$)/,null,'"'],["com",/^;[^\n\r]*/,null,";"]],[["pln",/^[!%@](?:[$\-.A-Z_a-z][\w$\-.]*|\d+)/],["kwd",/^[^\W\d]\w*/,null],["lit",/^\d+\.\d+/],["lit",/^(?:\d+|0[Xx][\dA-Fa-f]+)/],["pun",/^[(-*,:<->[\]{}]|\.\.\.$/]]),["llvm","ll"]);
diff --git a/src/main/java/com/gitblit/wicket/pages/prettify/lang-lua.js b/src/main/java/com/gitblit/wicket/pages/prettify/lang-lua.js
new file mode 100644
index 0000000..7e44cca
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/prettify/lang-lua.js
@@ -0,0 +1,2 @@
+PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\r \xa0]+/,null,"\t\n\r \u00a0"],["str",/^(?:"(?:[^"\\]|\\[\S\s])*(?:"|$)|'(?:[^'\\]|\\[\S\s])*(?:'|$))/,null,"\"'"]],[["com",/^--(?:\[(=*)\[[\S\s]*?(?:]\1]|$)|[^\n\r]*)/],["str",/^\[(=*)\[[\S\s]*?(?:]\1]|$)/],["kwd",/^(?:and|break|do|else|elseif|end|false|for|function|if|in|local|nil|not|or|repeat|return|then|true|until|while)\b/,null],["lit",/^[+-]?(?:0x[\da-f]+|(?:\.\d+|\d+(?:\.\d*)?)(?:e[+-]?\d+)?)/i],
+["pln",/^[_a-z]\w*/i],["pun",/^[^\w\t\n\r \xa0][^\w\t\n\r "'+=\xa0-]*/]]),["lua"]);
diff --git a/src/main/java/com/gitblit/wicket/pages/prettify/lang-matlab.js b/src/main/java/com/gitblit/wicket/pages/prettify/lang-matlab.js
new file mode 100644
index 0000000..d0d3516
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/prettify/lang-matlab.js
@@ -0,0 +1,6 @@
+var a=null,b=window.PR,c=[[b.PR_PLAIN,/^[\t-\r \xa0]+/,a," \t\r\n\u000b\u000c\u00a0"],[b.PR_COMMENT,/^%{[^%]*%+(?:[^%}][^%]*%+)*}/,a],[b.PR_COMMENT,/^%[^\n\r]*/,a,"%"],["syscmd",/^![^\n\r]*/,a,"!"]],d=[["linecont",/^\.\.\.\s*[\n\r]/,a],["err",/^\?\?\? [^\n\r]*/,a],["wrn",/^Warning: [^\n\r]*/,a],["codeoutput",/^>>\s+/,a],["codeoutput",/^octave:\d+>\s+/,a],["lang-matlab-operators",/^((?:[A-Za-z]\w*(?:\.[A-Za-z]\w*)*|[).\]}])')/,a],["lang-matlab-identifiers",/^([A-Za-z]\w*(?:\.[A-Za-z]\w*)*)(?!')/,a],
+[b.PR_STRING,/^'(?:[^']|'')*'/,a],[b.PR_LITERAL,/^[+-]?\.?\d+(?:\.\d*)?(?:[Ee][+-]?\d+)?[ij]?/,a],[b.PR_TAG,/^[()[\]{}]/,a],[b.PR_PUNCTUATION,/^[!&*-/:->@\\^|~]/,a]],e=[["lang-matlab-identifiers",/^([A-Za-z]\w*(?:\.[A-Za-z]\w*)*)/,a],[b.PR_TAG,/^[()[\]{}]/,a],[b.PR_PUNCTUATION,/^[!&*-/:->@\\^|~]/,a],["transpose",/^'/,a]];
+b.registerLangHandler(b.createSimpleLexer([],[[b.PR_KEYWORD,/^\b(?:break|case|catch|classdef|continue|else|elseif|end|for|function|global|if|otherwise|parfor|persistent|return|spmd|switch|try|while)\b/,a],["const",/^\b(?:true|false|inf|Inf|nan|NaN|eps|pi|ans|nargin|nargout|varargin|varargout)\b/,a],[b.PR_TYPE,/^\b(?:cell|struct|char|double|single|logical|u?int(?:8|16|32|64)|sparse)\b/,a],["fun",/^\b(?:abs|accumarray|acos(?:d|h)?|acot(?:d|h)?|acsc(?:d|h)?|actxcontrol(?:list|select)?|actxGetRunningServer|actxserver|addlistener|addpath|addpref|addtodate|airy|align|alim|all|allchild|alpha|alphamap|amd|ancestor|and|angle|annotation|any|area|arrayfun|asec(?:d|h)?|asin(?:d|h)?|assert|assignin|atan[2dh]?|audiodevinfo|audioplayer|audiorecorder|aufinfo|auread|autumn|auwrite|avifile|aviinfo|aviread|axes|axis|balance|bar(?:3|3h|h)?|base2dec|beep|BeginInvoke|bench|bessel[h-ky]|beta|betainc|betaincinv|betaln|bicg|bicgstab|bicgstabl|bin2dec|bitand|bitcmp|bitget|bitmax|bitnot|bitor|bitset|bitshift|bitxor|blanks|blkdiag|bone|box|brighten|brush|bsxfun|builddocsearchdb|builtin|bvp4c|bvp5c|bvpget|bvpinit|bvpset|bvpxtend|calendar|calllib|callSoapService|camdolly|cameratoolbar|camlight|camlookat|camorbit|campan|campos|camproj|camroll|camtarget|camup|camva|camzoom|cart2pol|cart2sph|cast|cat|caxis|cd|cdf2rdf|cdfepoch|cdfinfo|cdflib(?:.(?:close|closeVar|computeEpoch|computeEpoch16|create|createAttr|createVar|delete|deleteAttr|deleteAttrEntry|deleteAttrgEntry|deleteVar|deleteVarRecords|epoch16Breakdown|epochBreakdown|getAttrEntry|getAttrgEntry|getAttrMaxEntry|getAttrMaxgEntry|getAttrName|getAttrNum|getAttrScope|getCacheSize|getChecksum|getCompression|getCompressionCacheSize|getConstantNames|getConstantValue|getCopyright|getFileBackward|getFormat|getLibraryCopyright|getLibraryVersion|getMajority|getName|getNumAttrEntries|getNumAttrgEntries|getNumAttributes|getNumgAttributes|getReadOnlyMode|getStageCacheSize|getValidate|getVarAllocRecords|getVarBlockingFactor|getVarCacheSize|getVarCompression|getVarData|getVarMaxAllocRecNum|getVarMaxWrittenRecNum|getVarName|getVarNum|getVarNumRecsWritten|getVarPadValue|getVarRecordData|getVarReservePercent|getVarsMaxWrittenRecNum|getVarSparseRecords|getVersion|hyperGetVarData|hyperPutVarData|inquire|inquireAttr|inquireAttrEntry|inquireAttrgEntry|inquireVar|open|putAttrEntry|putAttrgEntry|putVarData|putVarRecordData|renameAttr|renameVar|setCacheSize|setChecksum|setCompression|setCompressionCacheSize|setFileBackward|setFormat|setMajority|setReadOnlyMode|setStageCacheSize|setValidate|setVarAllocBlockRecords|setVarBlockingFactor|setVarCacheSize|setVarCompression|setVarInitialRecs|setVarPadValue|SetVarReservePercent|setVarsCacheSize|setVarSparseRecords))?|cdfread|cdfwrite|ceil|cell2mat|cell2struct|celldisp|cellfun|cellplot|cellstr|cgs|checkcode|checkin|checkout|chol|cholinc|cholupdate|circshift|cla|clabel|class|clc|clear|clearvars|clf|clipboard|clock|close|closereq|cmopts|cmpermute|cmunique|colamd|colon|colorbar|colordef|colormap|colormapeditor|colperm|Combine|comet|comet3|commandhistory|commandwindow|compan|compass|complex|computer|cond|condeig|condest|coneplot|conj|containers.Map|contour(?:[3cf]|slice)?|contrast|conv|conv2|convhull|convhulln|convn|cool|copper|copyfile|copyobj|corrcoef|cos(?:d|h)?|cot(?:d|h)?|cov|cplxpair|cputime|createClassFromWsdl|createSoapMessage|cross|csc(?:d|h)?|csvread|csvwrite|ctranspose|cumprod|cumsum|cumtrapz|curl|customverctrl|cylinder|daqread|daspect|datacursormode|datatipinfo|date|datenum|datestr|datetick|datevec|dbclear|dbcont|dbdown|dblquad|dbmex|dbquit|dbstack|dbstatus|dbstep|dbstop|dbtype|dbup|dde23|ddeget|ddesd|ddeset|deal|deblank|dec2base|dec2bin|dec2hex|decic|deconv|del2|delaunay|delaunay3|delaunayn|DelaunayTri|delete|demo|depdir|depfun|det|detrend|deval|diag|dialog|diary|diff|diffuse|dir|disp|display|dither|divergence|dlmread|dlmwrite|dmperm|doc|docsearch|dos|dot|dragrect|drawnow|dsearch|dsearchn|dynamicprops|echo|echodemo|edit|eig|eigs|ellipj|ellipke|ellipsoid|empty|enableNETfromNetworkDrive|enableservice|EndInvoke|enumeration|eomday|eq|erf|erfc|erfcinv|erfcx|erfinv|error|errorbar|errordlg|etime|etree|etreeplot|eval|evalc|evalin|event.(?:EventData|listener|PropertyEvent|proplistener)|exifread|exist|exit|exp|expint|expm|expm1|export2wsdlg|eye|ezcontour|ezcontourf|ezmesh|ezmeshc|ezplot|ezplot3|ezpolar|ezsurf|ezsurfc|factor|factorial|fclose|feather|feature|feof|ferror|feval|fft|fft2|fftn|fftshift|fftw|fgetl|fgets|fieldnames|figure|figurepalette|fileattrib|filebrowser|filemarker|fileparts|fileread|filesep|fill|fill3|filter|filter2|find|findall|findfigs|findobj|findstr|finish|fitsdisp|fitsinfo|fitsread|fitswrite|fix|flag|flipdim|fliplr|flipud|floor|flow|fminbnd|fminsearch|fopen|format|fplot|fprintf|frame2im|fread|freqspace|frewind|fscanf|fseek|ftell|FTP|full|fullfile|func2str|functions|funm|fwrite|fzero|gallery|gamma|gammainc|gammaincinv|gammaln|gca|gcbf|gcbo|gcd|gcf|gco|ge|genpath|genvarname|get|getappdata|getenv|getfield|getframe|getpixelposition|getpref|ginput|gmres|gplot|grabcode|gradient|gray|graymon|grid|griddata(?:3|n)?|griddedInterpolant|gsvd|gt|gtext|guidata|guide|guihandles|gunzip|gzip|h5create|h5disp|h5info|h5read|h5readatt|h5write|h5writeatt|hadamard|handle|hankel|hdf|hdf5|hdf5info|hdf5read|hdf5write|hdfinfo|hdfread|hdftool|help|helpbrowser|helpdesk|helpdlg|helpwin|hess|hex2dec|hex2num|hgexport|hggroup|hgload|hgsave|hgsetget|hgtransform|hidden|hilb|hist|histc|hold|home|horzcat|hostid|hot|hsv|hsv2rgb|hypot|ichol|idivide|ifft|ifft2|ifftn|ifftshift|ilu|im2frame|im2java|imag|image|imagesc|imapprox|imfinfo|imformats|import|importdata|imread|imwrite|ind2rgb|ind2sub|inferiorto|info|inline|inmem|inpolygon|input|inputdlg|inputname|inputParser|inspect|instrcallback|instrfind|instrfindall|int2str|integral(?:2|3)?|interp(?:1|1q|2|3|ft|n)|interpstreamspeed|intersect|intmax|intmin|inv|invhilb|ipermute|isa|isappdata|iscell|iscellstr|ischar|iscolumn|isdir|isempty|isequal|isequaln|isequalwithequalnans|isfield|isfinite|isfloat|isglobal|ishandle|ishghandle|ishold|isinf|isinteger|isjava|iskeyword|isletter|islogical|ismac|ismatrix|ismember|ismethod|isnan|isnumeric|isobject|isocaps|isocolors|isonormals|isosurface|ispc|ispref|isprime|isprop|isreal|isrow|isscalar|issorted|isspace|issparse|isstr|isstrprop|isstruct|isstudent|isunix|isvarname|isvector|javaaddpath|javaArray|javachk|javaclasspath|javacomponent|javaMethod|javaMethodEDT|javaObject|javaObjectEDT|javarmpath|jet|keyboard|kron|lasterr|lasterror|lastwarn|lcm|ldivide|ldl|le|legend|legendre|length|libfunctions|libfunctionsview|libisloaded|libpointer|libstruct|license|light|lightangle|lighting|lin2mu|line|lines|linkaxes|linkdata|linkprop|linsolve|linspace|listdlg|listfonts|load|loadlibrary|loadobj|log|log10|log1p|log2|loglog|logm|logspace|lookfor|lower|ls|lscov|lsqnonneg|lsqr|lt|lu|luinc|magic|makehgtform|mat2cell|mat2str|material|matfile|matlab.io.MatFile|matlab.mixin.(?:Copyable|Heterogeneous(?:.getDefaultScalarElement)?)|matlabrc|matlabroot|max|maxNumCompThreads|mean|median|membrane|memmapfile|memory|menu|mesh|meshc|meshgrid|meshz|meta.(?:class(?:.fromName)?|DynamicProperty|EnumeratedValue|event|MetaData|method|package(?:.(?:fromName|getAllPackages))?|property)|metaclass|methods|methodsview|mex(?:.getCompilerConfigurations)?|MException|mexext|mfilename|min|minres|minus|mislocked|mkdir|mkpp|mldivide|mlint|mlintrpt|mlock|mmfileinfo|mmreader|mod|mode|more|move|movefile|movegui|movie|movie2avi|mpower|mrdivide|msgbox|mtimes|mu2lin|multibandread|multibandwrite|munlock|namelengthmax|nargchk|narginchk|nargoutchk|native2unicode|nccreate|ncdisp|nchoosek|ncinfo|ncread|ncreadatt|ncwrite|ncwriteatt|ncwriteschema|ndgrid|ndims|ne|NET(?:.(?:addAssembly|Assembly|convertArray|createArray|createGeneric|disableAutoRelease|enableAutoRelease|GenericClass|invokeGenericMethod|NetException|setStaticProperty))?|netcdf.(?:abort|close|copyAtt|create|defDim|defGrp|defVar|defVarChunking|defVarDeflate|defVarFill|defVarFletcher32|delAtt|endDef|getAtt|getChunkCache|getConstant|getConstantNames|getVar|inq|inqAtt|inqAttID|inqAttName|inqDim|inqDimID|inqDimIDs|inqFormat|inqGrpName|inqGrpNameFull|inqGrpParent|inqGrps|inqLibVers|inqNcid|inqUnlimDims|inqVar|inqVarChunking|inqVarDeflate|inqVarFill|inqVarFletcher32|inqVarID|inqVarIDs|open|putAtt|putVar|reDef|renameAtt|renameDim|renameVar|setChunkCache|setDefaultFormat|setFill|sync)|newplot|nextpow2|nnz|noanimate|nonzeros|norm|normest|not|notebook|now|nthroot|null|num2cell|num2hex|num2str|numel|nzmax|ode(?:113|15i|15s|23|23s|23t|23tb|45)|odeget|odeset|odextend|onCleanup|ones|open|openfig|opengl|openvar|optimget|optimset|or|ordeig|orderfields|ordqz|ordschur|orient|orth|pack|padecoef|pagesetupdlg|pan|pareto|parseSoapResponse|pascal|patch|path|path2rc|pathsep|pathtool|pause|pbaspect|pcg|pchip|pcode|pcolor|pdepe|pdeval|peaks|perl|perms|permute|pie|pink|pinv|planerot|playshow|plot|plot3|plotbrowser|plotedit|plotmatrix|plottools|plotyy|plus|pol2cart|polar|poly|polyarea|polyder|polyeig|polyfit|polyint|polyval|polyvalm|pow2|power|ppval|prefdir|preferences|primes|print|printdlg|printopt|printpreview|prod|profile|profsave|propedit|propertyeditor|psi|publish|PutCharArray|PutFullMatrix|PutWorkspaceData|pwd|qhull|qmr|qr|qrdelete|qrinsert|qrupdate|quad|quad2d|quadgk|quadl|quadv|questdlg|quit|quiver|quiver3|qz|rand|randi|randn|randperm|RandStream(?:.(?:create|getDefaultStream|getGlobalStream|list|setDefaultStream|setGlobalStream))?|rank|rat|rats|rbbox|rcond|rdivide|readasync|real|reallog|realmax|realmin|realpow|realsqrt|record|rectangle|rectint|recycle|reducepatch|reducevolume|refresh|refreshdata|regexp|regexpi|regexprep|regexptranslate|rehash|rem|Remove|RemoveAll|repmat|reset|reshape|residue|restoredefaultpath|rethrow|rgb2hsv|rgb2ind|rgbplot|ribbon|rmappdata|rmdir|rmfield|rmpath|rmpref|rng|roots|rose|rosser|rot90|rotate|rotate3d|round|rref|rsf2csf|run|save|saveas|saveobj|savepath|scatter|scatter3|schur|sec|secd|sech|selectmoveresize|semilogx|semilogy|sendmail|serial|set|setappdata|setdiff|setenv|setfield|setpixelposition|setpref|setstr|setxor|shading|shg|shiftdim|showplottool|shrinkfaces|sign|sin(?:d|h)?|size|slice|smooth3|snapnow|sort|sortrows|sound|soundsc|spalloc|spaugment|spconvert|spdiags|specular|speye|spfun|sph2cart|sphere|spinmap|spline|spones|spparms|sprand|sprandn|sprandsym|sprank|spring|sprintf|spy|sqrt|sqrtm|squeeze|ss2tf|sscanf|stairs|startup|std|stem|stem3|stopasync|str2double|str2func|str2mat|str2num|strcat|strcmp|strcmpi|stream2|stream3|streamline|streamparticles|streamribbon|streamslice|streamtube|strfind|strjust|strmatch|strncmp|strncmpi|strread|strrep|strtok|strtrim|struct2cell|structfun|strvcat|sub2ind|subplot|subsasgn|subsindex|subspace|subsref|substruct|subvolume|sum|summer|superclasses|superiorto|support|surf|surf2patch|surface|surfc|surfl|surfnorm|svd|svds|swapbytes|symamd|symbfact|symmlq|symrcm|symvar|system|tan(?:d|h)?|tar|tempdir|tempname|tetramesh|texlabel|text|textread|textscan|textwrap|tfqmr|throw|tic|Tiff(?:.(?:getTagNames|getVersion))?|timer|timerfind|timerfindall|times|timeseries|title|toc|todatenum|toeplitz|toolboxdir|trace|transpose|trapz|treelayout|treeplot|tril|trimesh|triplequad|triplot|TriRep|TriScatteredInterp|trisurf|triu|tscollection|tsearch|tsearchn|tstool|type|typecast|uibuttongroup|uicontextmenu|uicontrol|uigetdir|uigetfile|uigetpref|uiimport|uimenu|uiopen|uipanel|uipushtool|uiputfile|uiresume|uisave|uisetcolor|uisetfont|uisetpref|uistack|uitable|uitoggletool|uitoolbar|uiwait|uminus|undocheckout|unicode2native|union|unique|unix|unloadlibrary|unmesh|unmkpp|untar|unwrap|unzip|uplus|upper|urlread|urlwrite|usejava|userpath|validateattributes|validatestring|vander|var|vectorize|ver|verctrl|verLessThan|version|vertcat|VideoReader(?:.isPlatformSupported)?|VideoWriter(?:.getProfiles)?|view|viewmtx|visdiff|volumebounds|voronoi|voronoin|wait|waitbar|waitfor|waitforbuttonpress|warndlg|warning|waterfall|wavfinfo|wavplay|wavread|wavrecord|wavwrite|web|weekday|what|whatsnew|which|whitebg|who|whos|wilkinson|winopen|winqueryreg|winter|wk1finfo|wk1read|wk1write|workspace|xlabel|xlim|xlsfinfo|xlsread|xlswrite|xmlread|xmlwrite|xor|xslt|ylabel|ylim|zeros|zip|zlabel|zlim|zoom)\b/,
+a],["fun_tbx",/^\b(?:addedvarplot|andrewsplot|anova[12n]|ansaribradley|aoctool|barttest|bbdesign|beta(?:cdf|fit|inv|like|pdf|rnd|stat)|bino(?:cdf|fit|inv|pdf|rnd|stat)|biplot|bootci|bootstrp|boxplot|candexch|candgen|canoncorr|capability|capaplot|caseread|casewrite|categorical|ccdesign|cdfplot|chi2(?:cdf|gof|inv|pdf|rnd|stat)|cholcov|Classification(?:BaggedEnsemble|Discriminant(?:.(?:fit|make|template))?|Ensemble|KNN(?:.(?:fit|template))?|PartitionedEnsemble|PartitionedModel|Tree(?:.(?:fit|template))?)|classify|classregtree|cluster|clusterdata|cmdscale|combnk|Compact(?:Classification(?:Discriminant|Ensemble|Tree)|Regression(?:Ensemble|Tree)|TreeBagger)|confusionmat|controlchart|controlrules|cophenet|copula(?:cdf|fit|param|pdf|rnd|stat)|cordexch|corr|corrcov|coxphfit|createns|crosstab|crossval|cvpartition|datasample|dataset|daugment|dcovary|dendrogram|dfittool|disttool|dummyvar|dwtest|ecdf|ecdfhist|ev(?:cdf|fit|inv|like|pdf|rnd|stat)|ExhaustiveSearcher|exp(?:cdf|fit|inv|like|pdf|rnd|stat)|factoran|fcdf|ff2n|finv|fitdist|fitensemble|fpdf|fracfact|fracfactgen|friedman|frnd|fstat|fsurfht|fullfact|gagerr|gam(?:cdf|fit|inv|like|pdf|rnd|stat)|GeneralizedLinearModel(?:.fit)?|geo(?:cdf|inv|mean|pdf|rnd|stat)|gev(?:cdf|fit|inv|like|pdf|rnd|stat)|gline|glmfit|glmval|glyphplot|gmdistribution(?:.fit)?|gname|gp(?:cdf|fit|inv|like|pdf|rnd|stat)|gplotmatrix|grp2idx|grpstats|gscatter|haltonset|harmmean|hist3|histfit|hmm(?:decode|estimate|generate|train|viterbi)|hougen|hyge(?:cdf|inv|pdf|rnd|stat)|icdf|inconsistent|interactionplot|invpred|iqr|iwishrnd|jackknife|jbtest|johnsrnd|KDTreeSearcher|kmeans|knnsearch|kruskalwallis|ksdensity|kstest|kstest2|kurtosis|lasso|lassoglm|lassoPlot|leverage|lhsdesign|lhsnorm|lillietest|LinearModel(?:.fit)?|linhyptest|linkage|logn(?:cdf|fit|inv|like|pdf|rnd|stat)|lsline|mad|mahal|maineffectsplot|manova1|manovacluster|mdscale|mhsample|mle|mlecov|mnpdf|mnrfit|mnrnd|mnrval|moment|multcompare|multivarichart|mvn(?:cdf|pdf|rnd)|mvregress|mvregresslike|mvt(?:cdf|pdf|rnd)|NaiveBayes(?:.fit)?|nan(?:cov|max|mean|median|min|std|sum|var)|nbin(?:cdf|fit|inv|pdf|rnd|stat)|ncf(?:cdf|inv|pdf|rnd|stat)|nct(?:cdf|inv|pdf|rnd|stat)|ncx2(?:cdf|inv|pdf|rnd|stat)|NeighborSearcher|nlinfit|nlintool|nlmefit|nlmefitsa|nlparci|nlpredci|nnmf|nominal|NonLinearModel(?:.fit)?|norm(?:cdf|fit|inv|like|pdf|rnd|stat)|normplot|normspec|ordinal|outlierMeasure|parallelcoords|paretotails|partialcorr|pcacov|pcares|pdf|pdist|pdist2|pearsrnd|perfcurve|perms|piecewisedistribution|plsregress|poiss(?:cdf|fit|inv|pdf|rnd|tat)|polyconf|polytool|prctile|princomp|ProbDist(?:Kernel|Parametric|UnivKernel|UnivParam)?|probplot|procrustes|qqplot|qrandset|qrandstream|quantile|randg|random|randsample|randtool|range|rangesearch|ranksum|rayl(?:cdf|fit|inv|pdf|rnd|stat)|rcoplot|refcurve|refline|regress|Regression(?:BaggedEnsemble|Ensemble|PartitionedEnsemble|PartitionedModel|Tree(?:.(?:fit|template))?)|regstats|relieff|ridge|robustdemo|robustfit|rotatefactors|rowexch|rsmdemo|rstool|runstest|sampsizepwr|scatterhist|sequentialfs|signrank|signtest|silhouette|skewness|slicesample|sobolset|squareform|statget|statset|stepwise|stepwisefit|surfht|tabulate|tblread|tblwrite|tcdf|tdfread|tiedrank|tinv|tpdf|TreeBagger|treedisp|treefit|treeprune|treetest|treeval|trimmean|trnd|tstat|ttest|ttest2|unid(?:cdf|inv|pdf|rnd|stat)|unif(?:cdf|inv|it|pdf|rnd|stat)|vartest(?:2|n)?|wbl(?:cdf|fit|inv|like|pdf|rnd|stat)|wblplot|wishrnd|x2fx|xptread|zscore|ztest)\b/,
+a],["fun_tbx",/^\b(?:adapthisteq|analyze75info|analyze75read|applycform|applylut|axes2pix|bestblk|blockproc|bwarea|bwareaopen|bwboundaries|bwconncomp|bwconvhull|bwdist|bwdistgeodesic|bweuler|bwhitmiss|bwlabel|bwlabeln|bwmorph|bwpack|bwperim|bwselect|bwtraceboundary|bwulterode|bwunpack|checkerboard|col2im|colfilt|conndef|convmtx2|corner|cornermetric|corr2|cp2tform|cpcorr|cpselect|cpstruct2pairs|dct2|dctmtx|deconvblind|deconvlucy|deconvreg|deconvwnr|decorrstretch|demosaic|dicom(?:anon|dict|info|lookup|read|uid|write)|edge|edgetaper|entropy|entropyfilt|fan2para|fanbeam|findbounds|fliptform|freqz2|fsamp2|fspecial|ftrans2|fwind1|fwind2|getheight|getimage|getimagemodel|getline|getneighbors|getnhood|getpts|getrangefromclass|getrect|getsequence|gray2ind|graycomatrix|graycoprops|graydist|grayslice|graythresh|hdrread|hdrwrite|histeq|hough|houghlines|houghpeaks|iccfind|iccread|iccroot|iccwrite|idct2|ifanbeam|im2bw|im2col|im2double|im2int16|im2java2d|im2single|im2uint16|im2uint8|imabsdiff|imadd|imadjust|ImageAdapter|imageinfo|imagemodel|imapplymatrix|imattributes|imbothat|imclearborder|imclose|imcolormaptool|imcomplement|imcontour|imcontrast|imcrop|imdilate|imdisplayrange|imdistline|imdivide|imellipse|imerode|imextendedmax|imextendedmin|imfill|imfilter|imfindcircles|imfreehand|imfuse|imgca|imgcf|imgetfile|imhandles|imhist|imhmax|imhmin|imimposemin|imlincomb|imline|immagbox|immovie|immultiply|imnoise|imopen|imoverview|imoverviewpanel|impixel|impixelinfo|impixelinfoval|impixelregion|impixelregionpanel|implay|impoint|impoly|impositionrect|improfile|imputfile|impyramid|imreconstruct|imrect|imregconfig|imregionalmax|imregionalmin|imregister|imresize|imroi|imrotate|imsave|imscrollpanel|imshow|imshowpair|imsubtract|imtool|imtophat|imtransform|imview|ind2gray|ind2rgb|interfileinfo|interfileread|intlut|ippl|iptaddcallback|iptcheckconn|iptcheckhandle|iptcheckinput|iptcheckmap|iptchecknargin|iptcheckstrs|iptdemos|iptgetapi|iptGetPointerBehavior|iptgetpref|ipticondir|iptnum2ordinal|iptPointerManager|iptprefs|iptremovecallback|iptSetPointerBehavior|iptsetpref|iptwindowalign|iradon|isbw|isflat|isgray|isicc|isind|isnitf|isrgb|isrset|lab2double|lab2uint16|lab2uint8|label2rgb|labelmatrix|makecform|makeConstrainToRectFcn|makehdr|makelut|makeresampler|maketform|mat2gray|mean2|medfilt2|montage|nitfinfo|nitfread|nlfilter|normxcorr2|ntsc2rgb|openrset|ordfilt2|otf2psf|padarray|para2fan|phantom|poly2mask|psf2otf|qtdecomp|qtgetblk|qtsetblk|radon|rangefilt|reflect|regionprops|registration.metric.(?:MattesMutualInformation|MeanSquares)|registration.optimizer.(?:OnePlusOneEvolutionary|RegularStepGradientDescent)|rgb2gray|rgb2ntsc|rgb2ycbcr|roicolor|roifill|roifilt2|roipoly|rsetwrite|std2|stdfilt|strel|stretchlim|subimage|tformarray|tformfwd|tforminv|tonemap|translate|truesize|uintlut|viscircles|warp|watershed|whitepoint|wiener2|xyz2double|xyz2uint16|ycbcr2rgb)\b/,
+a],["fun_tbx",/^\b(?:bintprog|color|fgoalattain|fminbnd|fmincon|fminimax|fminsearch|fminunc|fseminf|fsolve|fzero|fzmult|gangstr|ktrlink|linprog|lsqcurvefit|lsqlin|lsqnonlin|lsqnonneg|optimget|optimset|optimtool|quadprog)\b/,a],["ident",/^[A-Za-z]\w*(?:\.[A-Za-z]\w*)*/,a]]),["matlab-identifiers"]);b.registerLangHandler(b.createSimpleLexer([],e),["matlab-operators"]);b.registerLangHandler(b.createSimpleLexer(c,d),["matlab"]);
diff --git a/src/main/java/com/gitblit/wicket/pages/prettify/lang-ml.js b/src/main/java/com/gitblit/wicket/pages/prettify/lang-ml.js
new file mode 100644
index 0000000..8ed2b0c
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/prettify/lang-ml.js
@@ -0,0 +1,2 @@
+PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\r \xa0]+/,null,"\t\n\r \u00a0"],["com",/^#(?:if[\t\n\r \xa0]+(?:[$_a-z][\w']*|``[^\t\n\r`]*(?:``|$))|else|endif|light)/i,null,"#"],["str",/^(?:"(?:[^"\\]|\\[\S\s])*(?:"|$)|'(?:[^'\\]|\\[\S\s])(?:'|$))/,null,"\"'"]],[["com",/^(?:\/\/[^\n\r]*|\(\*[\S\s]*?\*\))/],["kwd",/^(?:abstract|and|as|assert|begin|class|default|delegate|do|done|downcast|downto|elif|else|end|exception|extern|false|finally|for|fun|function|if|in|inherit|inline|interface|internal|lazy|let|match|member|module|mutable|namespace|new|null|of|open|or|override|private|public|rec|return|static|struct|then|to|true|try|type|upcast|use|val|void|when|while|with|yield|asr|land|lor|lsl|lsr|lxor|mod|sig|atomic|break|checked|component|const|constraint|constructor|continue|eager|event|external|fixed|functor|global|include|method|mixin|object|parallel|process|protected|pure|sealed|trait|virtual|volatile)\b/],
+["lit",/^[+-]?(?:0x[\da-f]+|(?:\.\d+|\d+(?:\.\d*)?)(?:e[+-]?\d+)?)/i],["pln",/^(?:[_a-z][\w']*[!#?]?|``[^\t\n\r`]*(?:``|$))/i],["pun",/^[^\w\t\n\r "'\xa0]+/]]),["fs","ml"]);
diff --git a/src/main/java/com/gitblit/wicket/pages/prettify/lang-mumps.js b/src/main/java/com/gitblit/wicket/pages/prettify/lang-mumps.js
new file mode 100644
index 0000000..8a6b3fd
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/prettify/lang-mumps.js
@@ -0,0 +1,2 @@
+PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\r \xa0]+/,null,"\t\n\r \u00a0"],["str",/^"(?:[^"]|\\.)*"/,null,'"']],[["com",/^;[^\n\r]*/,null,";"],["dec",/^\$(?:d|device|ec|ecode|es|estack|et|etrap|h|horolog|i|io|j|job|k|key|p|principal|q|quit|st|stack|s|storage|sy|system|t|test|tl|tlevel|tr|trestart|x|y|z[a-z]*|a|ascii|c|char|d|data|e|extract|f|find|fn|fnumber|g|get|j|justify|l|length|na|name|o|order|p|piece|ql|qlength|qs|qsubscript|q|query|r|random|re|reverse|s|select|st|stack|t|text|tr|translate|nan)\b/i,
+null],["kwd",/^(?:[^$]b|break|c|close|d|do|e|else|f|for|g|goto|h|halt|h|hang|i|if|j|job|k|kill|l|lock|m|merge|n|new|o|open|q|quit|r|read|s|set|tc|tcommit|tre|trestart|tro|trollback|ts|tstart|u|use|v|view|w|write|x|xecute)\b/i,null],["lit",/^[+-]?(?:\.\d+|\d+(?:\.\d*)?)(?:e[+-]?\d+)?/i],["pln",/^[a-z][^\W_]*/i],["pun",/^[^\w\t\n\r"$%;^\xa0]|_/]]),["mumps"]);
diff --git a/src/main/java/com/gitblit/wicket/pages/prettify/lang-n.js b/src/main/java/com/gitblit/wicket/pages/prettify/lang-n.js
new file mode 100644
index 0000000..27812a5
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/prettify/lang-n.js
@@ -0,0 +1,4 @@
+var a=null;
+PR.registerLangHandler(PR.createSimpleLexer([["str",/^(?:'(?:[^\n\r'\\]|\\.)*'|"(?:[^\n\r"\\]|\\.)*(?:"|$))/,a,'"'],["com",/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\n\r]*)/,a,"#"],["pln",/^\s+/,a," \r\n\t\u00a0"]],[["str",/^@"(?:[^"]|"")*(?:"|$)/,a],["str",/^<#[^#>]*(?:#>|$)/,a],["str",/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,a],["com",/^\/\/[^\n\r]*/,a],["com",/^\/\*[\S\s]*?(?:\*\/|$)/,
+a],["kwd",/^(?:abstract|and|as|base|catch|class|def|delegate|enum|event|extern|false|finally|fun|implements|interface|internal|is|macro|match|matches|module|mutable|namespace|new|null|out|override|params|partial|private|protected|public|ref|sealed|static|struct|syntax|this|throw|true|try|type|typeof|using|variant|virtual|volatile|when|where|with|assert|assert2|async|break|checked|continue|do|else|ensures|for|foreach|if|late|lock|new|nolate|otherwise|regexp|repeat|requires|return|surroundwith|unchecked|unless|using|while|yield)\b/,
+a],["typ",/^(?:array|bool|byte|char|decimal|double|float|int|list|long|object|sbyte|short|string|ulong|uint|ufloat|ulong|ushort|void)\b/,a],["lit",/^@[$_a-z][\w$@]*/i,a],["typ",/^@[A-Z]+[a-z][\w$@]*/,a],["pln",/^'?[$_a-z][\w$@]*/i,a],["lit",/^(?:0x[\da-f]+|(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d\+)(?:e[+-]?\d+)?)[a-z]*/i,a,"0123456789"],["pun",/^.[^\s\w"-$'./@`]*/,a]]),["n","nemerle"]);
diff --git a/src/main/java/com/gitblit/wicket/pages/prettify/lang-pascal.js b/src/main/java/com/gitblit/wicket/pages/prettify/lang-pascal.js
new file mode 100644
index 0000000..8435fad
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/prettify/lang-pascal.js
@@ -0,0 +1,3 @@
+var a=null;
+PR.registerLangHandler(PR.createSimpleLexer([["str",/^'(?:[^\n\r'\\]|\\.)*(?:'|$)/,a,"'"],["pln",/^\s+/,a," \r\n\t\u00a0"]],[["com",/^\(\*[\S\s]*?(?:\*\)|$)|^{[\S\s]*?(?:}|$)/,a],["kwd",/^(?:absolute|and|array|asm|assembler|begin|case|const|constructor|destructor|div|do|downto|else|end|external|for|forward|function|goto|if|implementation|in|inline|interface|interrupt|label|mod|not|object|of|or|packed|procedure|program|record|repeat|set|shl|shr|then|to|type|unit|until|uses|var|virtual|while|with|xor)\b/i,a],
+["lit",/^(?:true|false|self|nil)/i,a],["pln",/^[a-z][^\W_]*/i,a],["lit",/^(?:\$[\da-f]+|(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?)/i,a,"0123456789"],["pun",/^.[^\s\w$'./@]*/,a]]),["pascal"]);
diff --git a/src/main/java/com/gitblit/wicket/pages/prettify/lang-proto.js b/src/main/java/com/gitblit/wicket/pages/prettify/lang-proto.js
new file mode 100644
index 0000000..f006ad8
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/prettify/lang-proto.js
@@ -0,0 +1 @@
+PR.registerLangHandler(PR.sourceDecorator({keywords:"bytes,default,double,enum,extend,extensions,false,group,import,max,message,option,optional,package,repeated,required,returns,rpc,service,syntax,to,true",types:/^(bool|(double|s?fixed|[su]?int)(32|64)|float|string)\b/,cStyleComments:!0}),["proto"]);
diff --git a/src/main/java/com/gitblit/wicket/pages/prettify/lang-r.js b/src/main/java/com/gitblit/wicket/pages/prettify/lang-r.js
new file mode 100644
index 0000000..99af8f8
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/prettify/lang-r.js
@@ -0,0 +1,2 @@
+PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\r \xa0]+/,null,"\t\n\r \u00a0"],["str",/^"(?:[^"\\]|\\[\S\s])*(?:"|$)/,null,'"'],["str",/^'(?:[^'\\]|\\[\S\s])*(?:'|$)/,null,"'"]],[["com",/^#.*/],["kwd",/^(?:if|else|for|while|repeat|in|next|break|return|switch|function)(?![\w.])/],["lit",/^0[Xx][\dA-Fa-f]+([Pp]\d+)?[Li]?/],["lit",/^[+-]?(\d+(\.\d+)?|\.\d+)([Ee][+-]?\d+)?[Li]?/],["lit",/^(?:NULL|NA(?:_(?:integer|real|complex|character)_)?|Inf|TRUE|FALSE|NaN|\.\.(?:\.|\d+))(?![\w.])/],
+["pun",/^(?:<<?-|->>?|-|==|<=|>=|<|>|&&?|!=|\|\|?|[!*+/^]|%.*?%|[$=@~]|:{1,3}|[(),;?[\]{}])/],["pln",/^(?:[A-Za-z]+[\w.]*|\.[^\W\d][\w.]*)(?![\w.])/],["str",/^`.+`/]]),["r","s","R","S","Splus"]);
diff --git a/src/main/java/com/gitblit/wicket/pages/prettify/lang-rd.js b/src/main/java/com/gitblit/wicket/pages/prettify/lang-rd.js
new file mode 100644
index 0000000..7a7e43f
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/prettify/lang-rd.js
@@ -0,0 +1 @@
+PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\r \xa0]+/,null,"\t\n\r \u00a0"],["com",/^%[^\n\r]*/,null,"%"]],[["lit",/^\\(?:cr|l?dots|R|tab)\b/],["kwd",/^\\[@-Za-z]+/],["kwd",/^#(?:ifn?def|endif)/],["pln",/^\\[{}]/],["pun",/^[()[\]{}]+/]]),["Rd","rd"]);
diff --git a/src/main/java/com/gitblit/wicket/pages/prettify/lang-scala.js b/src/main/java/com/gitblit/wicket/pages/prettify/lang-scala.js
new file mode 100644
index 0000000..3f97dba
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/prettify/lang-scala.js
@@ -0,0 +1,2 @@
+PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\r \xa0]+/,null,"\t\n\r \u00a0"],["str",/^"(?:""(?:""?(?!")|[^"\\]|\\.)*"{0,3}|(?:[^\n\r"\\]|\\.)*"?)/,null,'"'],["lit",/^`(?:[^\n\r\\`]|\\.)*`?/,null,"`"],["pun",/^[!#%&(--:-@[-^{-~]+/,null,"!#%&()*+,-:;<=>?@[\\]^{|}~"]],[["str",/^'(?:[^\n\r'\\]|\\(?:'|[^\n\r']+))'/],["lit",/^'[$A-Z_a-z][\w$]*(?![\w$'])/],["kwd",/^(?:abstract|case|catch|class|def|do|else|extends|final|finally|for|forSome|if|implicit|import|lazy|match|new|object|override|package|private|protected|requires|return|sealed|super|throw|trait|try|type|val|var|while|with|yield)\b/],
+["lit",/^(?:true|false|null|this)\b/],["lit",/^(?:0(?:[0-7]+|x[\da-f]+)l?|(?:0|[1-9]\d*)(?:(?:\.\d+)?(?:e[+-]?\d+)?f?|l?)|\\.\d+(?:e[+-]?\d+)?f?)/i],["typ",/^[$_]*[A-Z][\d$A-Z_]*[a-z][\w$]*/],["pln",/^[$A-Z_a-z][\w$]*/],["com",/^\/(?:\/.*|\*(?:\/|\**[^*/])*(?:\*+\/?)?)/],["pun",/^(?:\.+|\/)/]]),["scala"]);
diff --git a/src/main/java/com/gitblit/wicket/pages/prettify/lang-sql.js b/src/main/java/com/gitblit/wicket/pages/prettify/lang-sql.js
new file mode 100644
index 0000000..8ec4280
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/prettify/lang-sql.js
@@ -0,0 +1,2 @@
+PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\r \xa0]+/,null,"\t\n\r \u00a0"],["str",/^(?:"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*')/,null,"\"'"]],[["com",/^(?:--[^\n\r]*|\/\*[\S\s]*?(?:\*\/|$))/],["kwd",/^(?:add|all|alter|and|any|apply|as|asc|authorization|backup|begin|between|break|browse|bulk|by|cascade|case|check|checkpoint|close|clustered|coalesce|collate|column|commit|compute|connect|constraint|contains|containstable|continue|convert|create|cross|current|current_date|current_time|current_timestamp|current_user|cursor|database|dbcc|deallocate|declare|default|delete|deny|desc|disk|distinct|distributed|double|drop|dummy|dump|else|end|errlvl|escape|except|exec|execute|exists|exit|fetch|file|fillfactor|following|for|foreign|freetext|freetexttable|from|full|function|goto|grant|group|having|holdlock|identity|identitycol|identity_insert|if|in|index|inner|insert|intersect|into|is|join|key|kill|left|like|lineno|load|match|matched|merge|natural|national|nocheck|nonclustered|nocycle|not|null|nullif|of|off|offsets|on|open|opendatasource|openquery|openrowset|openxml|option|or|order|outer|over|partition|percent|pivot|plan|preceding|precision|primary|print|proc|procedure|public|raiserror|read|readtext|reconfigure|references|replication|restore|restrict|return|revoke|right|rollback|rowcount|rowguidcol|rows?|rule|save|schema|select|session_user|set|setuser|shutdown|some|start|statistics|system_user|table|textsize|then|to|top|tran|transaction|trigger|truncate|tsequal|unbounded|union|unique|unpivot|update|updatetext|use|user|using|values|varying|view|waitfor|when|where|while|with|within|writetext|xml)(?=[^\w-]|$)/i,
+null],["lit",/^[+-]?(?:0x[\da-f]+|(?:\.\d+|\d+(?:\.\d*)?)(?:e[+-]?\d+)?)/i],["pln",/^[_a-z][\w-]*/i],["pun",/^[^\w\t\n\r "'\xa0][^\w\t\n\r "'+\xa0-]*/]]),["sql"]);
diff --git a/src/main/java/com/gitblit/wicket/pages/prettify/lang-tcl.js b/src/main/java/com/gitblit/wicket/pages/prettify/lang-tcl.js
new file mode 100644
index 0000000..490f562
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/prettify/lang-tcl.js
@@ -0,0 +1,3 @@
+var a=null;
+PR.registerLangHandler(PR.createSimpleLexer([["opn",/^{+/,a,"{"],["clo",/^}+/,a,"}"],["com",/^#[^\n\r]*/,a,"#"],["pln",/^[\t\n\r \xa0]+/,a,"\t\n\r \u00a0"],["str",/^"(?:[^"\\]|\\[\S\s])*(?:"|$)/,a,'"']],[["kwd",/^(?:after|append|apply|array|break|case|catch|continue|error|eval|exec|exit|expr|for|foreach|if|incr|info|proc|return|set|switch|trace|uplevel|upvar|while)\b/,a],["lit",/^[+-]?(?:[#0]x[\da-f]+|\d+\/\d+|(?:\.\d+|\d+(?:\.\d*)?)(?:[de][+-]?\d+)?)/i],["lit",
+/^'(?:-*(?:\w|\\[!-~])(?:[\w-]*|\\[!-~])[!=?]?)?/],["pln",/^-*(?:[_a-z]|\\[!-~])(?:[\w-]*|\\[!-~])[!=?]?/i],["pun",/^[^\w\t\n\r "'-);\\\xa0]+/]]),["tcl"]);
diff --git a/src/main/java/com/gitblit/wicket/pages/prettify/lang-tex.js b/src/main/java/com/gitblit/wicket/pages/prettify/lang-tex.js
new file mode 100644
index 0000000..dcfdadd
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/prettify/lang-tex.js
@@ -0,0 +1 @@
+PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\r \xa0]+/,null,"\t\n\r \u00a0"],["com",/^%[^\n\r]*/,null,"%"]],[["kwd",/^\\[@-Za-z]+/],["kwd",/^\\./],["typ",/^[$&]/],["lit",/[+-]?(?:\.\d+|\d+(?:\.\d*)?)(cm|em|ex|in|pc|pt|bp|mm)/i],["pun",/^[()=[\]{}]+/]]),["latex","tex"]);
diff --git a/src/main/java/com/gitblit/wicket/pages/prettify/lang-vb.js b/src/main/java/com/gitblit/wicket/pages/prettify/lang-vb.js
new file mode 100644
index 0000000..ddde464
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/prettify/lang-vb.js
@@ -0,0 +1,2 @@
+PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\r \xa0\u2028\u2029]+/,null,"\t\n\r \u00a0\u2028\u2029"],["str",/^(?:["\u201c\u201d](?:[^"\u201c\u201d]|["\u201c\u201d]{2})(?:["\u201c\u201d]c|$)|["\u201c\u201d](?:[^"\u201c\u201d]|["\u201c\u201d]{2})*(?:["\u201c\u201d]|$))/i,null,'"\u201c\u201d'],["com",/^['\u2018\u2019](?:_(?:\r\n?|[^\r]?)|[^\n\r_\u2028\u2029])*/,null,"'\u2018\u2019"]],[["kwd",/^(?:addhandler|addressof|alias|and|andalso|ansi|as|assembly|auto|boolean|byref|byte|byval|call|case|catch|cbool|cbyte|cchar|cdate|cdbl|cdec|char|cint|class|clng|cobj|const|cshort|csng|cstr|ctype|date|decimal|declare|default|delegate|dim|directcast|do|double|each|else|elseif|end|endif|enum|erase|error|event|exit|finally|for|friend|function|get|gettype|gosub|goto|handles|if|implements|imports|in|inherits|integer|interface|is|let|lib|like|long|loop|me|mod|module|mustinherit|mustoverride|mybase|myclass|namespace|new|next|not|notinheritable|notoverridable|object|on|option|optional|or|orelse|overloads|overridable|overrides|paramarray|preserve|private|property|protected|public|raiseevent|readonly|redim|removehandler|resume|return|select|set|shadows|shared|short|single|static|step|stop|string|structure|sub|synclock|then|throw|to|try|typeof|unicode|until|variant|wend|when|while|with|withevents|writeonly|xor|endif|gosub|let|variant|wend)\b/i,
+null],["com",/^rem\b.*/i],["lit",/^(?:true\b|false\b|nothing\b|\d+(?:e[+-]?\d+[dfr]?|[dfilrs])?|(?:&h[\da-f]+|&o[0-7]+)[ils]?|\d*\.\d+(?:e[+-]?\d+)?[dfr]?|#\s+(?:\d+[/-]\d+[/-]\d+(?:\s+\d+:\d+(?::\d+)?(\s*(?:am|pm))?)?|\d+:\d+(?::\d+)?(\s*(?:am|pm))?)\s+#)/i],["pln",/^(?:(?:[a-z]|_\w)\w*(?:\[[!#%&@]+])?|\[(?:[a-z]|_\w)\w*])/i],["pun",/^[^\w\t\n\r "'[\]\xa0\u2018\u2019\u201c\u201d\u2028\u2029]+/],["pun",/^(?:\[|])/]]),["vb","vbs"]);
diff --git a/src/main/java/com/gitblit/wicket/pages/prettify/lang-vhdl.js b/src/main/java/com/gitblit/wicket/pages/prettify/lang-vhdl.js
new file mode 100644
index 0000000..51f3017
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/prettify/lang-vhdl.js
@@ -0,0 +1,3 @@
+PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\r \xa0]+/,null,"\t\n\r \u00a0"]],[["str",/^(?:[box]?"(?:[^"]|"")*"|'.')/i],["com",/^--[^\n\r]*/],["kwd",/^(?:abs|access|after|alias|all|and|architecture|array|assert|attribute|begin|block|body|buffer|bus|case|component|configuration|constant|disconnect|downto|else|elsif|end|entity|exit|file|for|function|generate|generic|group|guarded|if|impure|in|inertial|inout|is|label|library|linkage|literal|loop|map|mod|nand|new|next|nor|not|null|of|on|open|or|others|out|package|port|postponed|procedure|process|pure|range|record|register|reject|rem|report|return|rol|ror|select|severity|shared|signal|sla|sll|sra|srl|subtype|then|to|transport|type|unaffected|units|until|use|variable|wait|when|while|with|xnor|xor)(?=[^\w-]|$)/i,
+null],["typ",/^(?:bit|bit_vector|character|boolean|integer|real|time|string|severity_level|positive|natural|signed|unsigned|line|text|std_u?logic(?:_vector)?)(?=[^\w-]|$)/i,null],["typ",/^'(?:active|ascending|base|delayed|driving|driving_value|event|high|image|instance_name|last_active|last_event|last_value|left|leftof|length|low|path_name|pos|pred|quiet|range|reverse_range|right|rightof|simple_name|stable|succ|transaction|val|value)(?=[^\w-]|$)/i,null],["lit",/^\d+(?:_\d+)*(?:#[\w.\\]+#(?:[+-]?\d+(?:_\d+)*)?|(?:\.\d+(?:_\d+)*)?(?:e[+-]?\d+(?:_\d+)*)?)/i],
+["pln",/^(?:[a-z]\w*|\\[^\\]*\\)/i],["pun",/^[^\w\t\n\r "'\xa0][^\w\t\n\r "'\xa0-]*/]]),["vhdl","vhd"]);
diff --git a/src/main/java/com/gitblit/wicket/pages/prettify/lang-wiki.js b/src/main/java/com/gitblit/wicket/pages/prettify/lang-wiki.js
new file mode 100644
index 0000000..96c1e34
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/prettify/lang-wiki.js
@@ -0,0 +1,2 @@
+PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\d\t a-gi-z\xa0]+/,null,"\t \u00a0abcdefgijklmnopqrstuvwxyz0123456789"],["pun",/^[*=[\]^~]+/,null,"=*~^[]"]],[["lang-wiki.meta",/(?:^^|\r\n?|\n)(#[a-z]+)\b/],["lit",/^[A-Z][a-z][\da-z]+[A-Z][a-z][^\W_]+\b/],["lang-",/^{{{([\S\s]+?)}}}/],["lang-",/^`([^\n\r`]+)`/],["str",/^https?:\/\/[^\s#/?]*(?:\/[^\s#?]*)?(?:\?[^\s#]*)?(?:#\S*)?/i],["pln",/^(?:\r\n|[\S\s])[^\n\r#*=A-[^`h{~]*/]]),["wiki"]);
+PR.registerLangHandler(PR.createSimpleLexer([["kwd",/^#[a-z]+/i,null,"#"]],[]),["wiki.meta"]);
diff --git a/src/main/java/com/gitblit/wicket/pages/prettify/lang-xq.js b/src/main/java/com/gitblit/wicket/pages/prettify/lang-xq.js
new file mode 100644
index 0000000..e323ae3
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/prettify/lang-xq.js
@@ -0,0 +1,3 @@
+PR.registerLangHandler(PR.createSimpleLexer([["var pln",/^\$[\w-]+/,null,"$"]],[["pln",/^[\s=][<>][\s=]/],["lit",/^@[\w-]+/],["tag",/^<\/?[a-z](?:[\w-.:]*\w)?|\/?>$/i],["com",/^\(:[\S\s]*?:\)/],["pln",/^[(),/;[\]{}]$/],["str",/^(?:"(?:[^"\\{]|\\[\S\s])*(?:"|$)|'(?:[^'\\{]|\\[\S\s])*(?:'|$))/,null,"\"'"],["kwd",/^(?:xquery|where|version|variable|union|typeswitch|treat|to|then|text|stable|sortby|some|self|schema|satisfies|returns|return|ref|processing-instruction|preceding-sibling|preceding|precedes|parent|only|of|node|namespace|module|let|item|intersect|instance|in|import|if|function|for|follows|following-sibling|following|external|except|every|else|element|descending|descendant-or-self|descendant|define|default|declare|comment|child|cast|case|before|attribute|assert|ascending|as|ancestor-or-self|ancestor|after|eq|order|by|or|and|schema-element|document-node|node|at)\b/],
+["typ",/^(?:xs:yearMonthDuration|xs:unsignedLong|xs:time|xs:string|xs:short|xs:QName|xs:Name|xs:long|xs:integer|xs:int|xs:gYearMonth|xs:gYear|xs:gMonthDay|xs:gDay|xs:float|xs:duration|xs:double|xs:decimal|xs:dayTimeDuration|xs:dateTime|xs:date|xs:byte|xs:boolean|xs:anyURI|xf:yearMonthDuration)\b/,null],["fun pln",/^(?:xp:dereference|xinc:node-expand|xinc:link-references|xinc:link-expand|xhtml:restructure|xhtml:clean|xhtml:add-lists|xdmp:zip-manifest|xdmp:zip-get|xdmp:zip-create|xdmp:xquery-version|xdmp:word-convert|xdmp:with-namespaces|xdmp:version|xdmp:value|xdmp:user-roles|xdmp:user-last-login|xdmp:user|xdmp:url-encode|xdmp:url-decode|xdmp:uri-is-file|xdmp:uri-format|xdmp:uri-content-type|xdmp:unquote|xdmp:unpath|xdmp:triggers-database|xdmp:trace|xdmp:to-json|xdmp:tidy|xdmp:subbinary|xdmp:strftime|xdmp:spawn-in|xdmp:spawn|xdmp:sleep|xdmp:shutdown|xdmp:set-session-field|xdmp:set-response-encoding|xdmp:set-response-content-type|xdmp:set-response-code|xdmp:set-request-time-limit|xdmp:set|xdmp:servers|xdmp:server-status|xdmp:server-name|xdmp:server|xdmp:security-database|xdmp:security-assert|xdmp:schema-database|xdmp:save|xdmp:role-roles|xdmp:role|xdmp:rethrow|xdmp:restart|xdmp:request-timestamp|xdmp:request-status|xdmp:request-cancel|xdmp:request|xdmp:redirect-response|xdmp:random|xdmp:quote|xdmp:query-trace|xdmp:query-meters|xdmp:product-edition|xdmp:privilege-roles|xdmp:privilege|xdmp:pretty-print|xdmp:powerpoint-convert|xdmp:platform|xdmp:permission|xdmp:pdf-convert|xdmp:path|xdmp:octal-to-integer|xdmp:node-uri|xdmp:node-replace|xdmp:node-kind|xdmp:node-insert-child|xdmp:node-insert-before|xdmp:node-insert-after|xdmp:node-delete|xdmp:node-database|xdmp:mul64|xdmp:modules-root|xdmp:modules-database|xdmp:merging|xdmp:merge-cancel|xdmp:merge|xdmp:md5|xdmp:logout|xdmp:login|xdmp:log-level|xdmp:log|xdmp:lock-release|xdmp:lock-acquire|xdmp:load|xdmp:invoke-in|xdmp:invoke|xdmp:integer-to-octal|xdmp:integer-to-hex|xdmp:http-put|xdmp:http-post|xdmp:http-options|xdmp:http-head|xdmp:http-get|xdmp:http-delete|xdmp:hosts|xdmp:host-status|xdmp:host-name|xdmp:host|xdmp:hex-to-integer|xdmp:hash64|xdmp:hash32|xdmp:has-privilege|xdmp:groups|xdmp:group-serves|xdmp:group-servers|xdmp:group-name|xdmp:group-hosts|xdmp:group|xdmp:get-session-field-names|xdmp:get-session-field|xdmp:get-response-encoding|xdmp:get-response-code|xdmp:get-request-username|xdmp:get-request-user|xdmp:get-request-url|xdmp:get-request-protocol|xdmp:get-request-path|xdmp:get-request-method|xdmp:get-request-header-names|xdmp:get-request-header|xdmp:get-request-field-names|xdmp:get-request-field-filename|xdmp:get-request-field-content-type|xdmp:get-request-field|xdmp:get-request-client-certificate|xdmp:get-request-client-address|xdmp:get-request-body|xdmp:get-current-user|xdmp:get-current-roles|xdmp:get|xdmp:function-name|xdmp:function-module|xdmp:function|xdmp:from-json|xdmp:forests|xdmp:forest-status|xdmp:forest-restore|xdmp:forest-restart|xdmp:forest-name|xdmp:forest-delete|xdmp:forest-databases|xdmp:forest-counts|xdmp:forest-clear|xdmp:forest-backup|xdmp:forest|xdmp:filesystem-file|xdmp:filesystem-directory|xdmp:exists|xdmp:excel-convert|xdmp:eval-in|xdmp:eval|xdmp:estimate|xdmp:email|xdmp:element-content-type|xdmp:elapsed-time|xdmp:document-set-quality|xdmp:document-set-property|xdmp:document-set-properties|xdmp:document-set-permissions|xdmp:document-set-collections|xdmp:document-remove-properties|xdmp:document-remove-permissions|xdmp:document-remove-collections|xdmp:document-properties|xdmp:document-locks|xdmp:document-load|xdmp:document-insert|xdmp:document-get-quality|xdmp:document-get-properties|xdmp:document-get-permissions|xdmp:document-get-collections|xdmp:document-get|xdmp:document-forest|xdmp:document-delete|xdmp:document-add-properties|xdmp:document-add-permissions|xdmp:document-add-collections|xdmp:directory-properties|xdmp:directory-locks|xdmp:directory-delete|xdmp:directory-create|xdmp:directory|xdmp:diacritic-less|xdmp:describe|xdmp:default-permissions|xdmp:default-collections|xdmp:databases|xdmp:database-restore-validate|xdmp:database-restore-status|xdmp:database-restore-cancel|xdmp:database-restore|xdmp:database-name|xdmp:database-forests|xdmp:database-backup-validate|xdmp:database-backup-status|xdmp:database-backup-purge|xdmp:database-backup-cancel|xdmp:database-backup|xdmp:database|xdmp:collection-properties|xdmp:collection-locks|xdmp:collection-delete|xdmp:collation-canonical-uri|xdmp:castable-as|xdmp:can-grant-roles|xdmp:base64-encode|xdmp:base64-decode|xdmp:architecture|xdmp:apply|xdmp:amp-roles|xdmp:amp|xdmp:add64|xdmp:add-response-header|xdmp:access|trgr:trigger-set-recursive|trgr:trigger-set-permissions|trgr:trigger-set-name|trgr:trigger-set-module|trgr:trigger-set-event|trgr:trigger-set-description|trgr:trigger-remove-permissions|trgr:trigger-module|trgr:trigger-get-permissions|trgr:trigger-enable|trgr:trigger-disable|trgr:trigger-database-online-event|trgr:trigger-data-event|trgr:trigger-add-permissions|trgr:remove-trigger|trgr:property-content|trgr:pre-commit|trgr:post-commit|trgr:get-trigger-by-id|trgr:get-trigger|trgr:document-scope|trgr:document-content|trgr:directory-scope|trgr:create-trigger|trgr:collection-scope|trgr:any-property-content|thsr:set-entry|thsr:remove-term|thsr:remove-synonym|thsr:remove-entry|thsr:query-lookup|thsr:lookup|thsr:load|thsr:insert|thsr:expand|thsr:add-synonym|spell:suggest-detailed|spell:suggest|spell:remove-word|spell:make-dictionary|spell:load|spell:levenshtein-distance|spell:is-correct|spell:insert|spell:double-metaphone|spell:add-word|sec:users-collection|sec:user-set-roles|sec:user-set-password|sec:user-set-name|sec:user-set-description|sec:user-set-default-permissions|sec:user-set-default-collections|sec:user-remove-roles|sec:user-privileges|sec:user-get-roles|sec:user-get-description|sec:user-get-default-permissions|sec:user-get-default-collections|sec:user-doc-permissions|sec:user-doc-collections|sec:user-add-roles|sec:unprotect-collection|sec:uid-for-name|sec:set-realm|sec:security-version|sec:security-namespace|sec:security-installed|sec:security-collection|sec:roles-collection|sec:role-set-roles|sec:role-set-name|sec:role-set-description|sec:role-set-default-permissions|sec:role-set-default-collections|sec:role-remove-roles|sec:role-privileges|sec:role-get-roles|sec:role-get-description|sec:role-get-default-permissions|sec:role-get-default-collections|sec:role-doc-permissions|sec:role-doc-collections|sec:role-add-roles|sec:remove-user|sec:remove-role-from-users|sec:remove-role-from-role|sec:remove-role-from-privileges|sec:remove-role-from-amps|sec:remove-role|sec:remove-privilege|sec:remove-amp|sec:protect-collection|sec:privileges-collection|sec:privilege-set-roles|sec:privilege-set-name|sec:privilege-remove-roles|sec:privilege-get-roles|sec:privilege-add-roles|sec:priv-doc-permissions|sec:priv-doc-collections|sec:get-user-names|sec:get-unique-elem-id|sec:get-role-names|sec:get-role-ids|sec:get-privilege|sec:get-distinct-permissions|sec:get-collection|sec:get-amp|sec:create-user-with-role|sec:create-user|sec:create-role|sec:create-privilege|sec:create-amp|sec:collections-collection|sec:collection-set-permissions|sec:collection-remove-permissions|sec:collection-get-permissions|sec:collection-add-permissions|sec:check-admin|sec:amps-collection|sec:amp-set-roles|sec:amp-remove-roles|sec:amp-get-roles|sec:amp-doc-permissions|sec:amp-doc-collections|sec:amp-add-roles|search:unparse|search:suggest|search:snippet|search:search|search:resolve-nodes|search:resolve|search:remove-constraint|search:parse|search:get-default-options|search:estimate|search:check-options|prof:value|prof:reset|prof:report|prof:invoke|prof:eval|prof:enable|prof:disable|prof:allowed|ppt:clean|pki:template-set-request|pki:template-set-name|pki:template-set-key-type|pki:template-set-key-options|pki:template-set-description|pki:template-in-use|pki:template-get-version|pki:template-get-request|pki:template-get-name|pki:template-get-key-type|pki:template-get-key-options|pki:template-get-id|pki:template-get-description|pki:need-certificate|pki:is-temporary|pki:insert-trusted-certificates|pki:insert-template|pki:insert-signed-certificates|pki:insert-certificate-revocation-list|pki:get-trusted-certificate-ids|pki:get-template-ids|pki:get-template-certificate-authority|pki:get-template-by-name|pki:get-template|pki:get-pending-certificate-requests-xml|pki:get-pending-certificate-requests-pem|pki:get-pending-certificate-request|pki:get-certificates-for-template-xml|pki:get-certificates-for-template|pki:get-certificates|pki:get-certificate-xml|pki:get-certificate-pem|pki:get-certificate|pki:generate-temporary-certificate-if-necessary|pki:generate-temporary-certificate|pki:generate-template-certificate-authority|pki:generate-certificate-request|pki:delete-template|pki:delete-certificate|pki:create-template|pdf:make-toc|pdf:insert-toc-headers|pdf:get-toc|pdf:clean|p:status-transition|p:state-transition|p:remove|p:pipelines|p:insert|p:get-by-id|p:get|p:execute|p:create|p:condition|p:collection|p:action|ooxml:runs-merge|ooxml:package-uris|ooxml:package-parts-insert|ooxml:package-parts|msword:clean|mcgm:polygon|mcgm:point|mcgm:geospatial-query-from-elements|mcgm:geospatial-query|mcgm:circle|math:tanh|math:tan|math:sqrt|math:sinh|math:sin|math:pow|math:modf|math:log10|math:log|math:ldexp|math:frexp|math:fmod|math:floor|math:fabs|math:exp|math:cosh|math:cos|math:ceil|math:atan2|math:atan|math:asin|math:acos|map:put|map:map|map:keys|map:get|map:delete|map:count|map:clear|lnk:to|lnk:remove|lnk:insert|lnk:get|lnk:from|lnk:create|kml:polygon|kml:point|kml:interior-polygon|kml:geospatial-query-from-elements|kml:geospatial-query|kml:circle|kml:box|gml:polygon|gml:point|gml:interior-polygon|gml:geospatial-query-from-elements|gml:geospatial-query|gml:circle|gml:box|georss:point|georss:geospatial-query|georss:circle|geo:polygon|geo:point|geo:interior-polygon|geo:geospatial-query-from-elements|geo:geospatial-query|geo:circle|geo:box|fn:zero-or-one|fn:years-from-duration|fn:year-from-dateTime|fn:year-from-date|fn:upper-case|fn:unordered|fn:true|fn:translate|fn:trace|fn:tokenize|fn:timezone-from-time|fn:timezone-from-dateTime|fn:timezone-from-date|fn:sum|fn:subtract-dateTimes-yielding-yearMonthDuration|fn:subtract-dateTimes-yielding-dayTimeDuration|fn:substring-before|fn:substring-after|fn:substring|fn:subsequence|fn:string-to-codepoints|fn:string-pad|fn:string-length|fn:string-join|fn:string|fn:static-base-uri|fn:starts-with|fn:seconds-from-time|fn:seconds-from-duration|fn:seconds-from-dateTime|fn:round-half-to-even|fn:round|fn:root|fn:reverse|fn:resolve-uri|fn:resolve-QName|fn:replace|fn:remove|fn:QName|fn:prefix-from-QName|fn:position|fn:one-or-more|fn:number|fn:not|fn:normalize-unicode|fn:normalize-space|fn:node-name|fn:node-kind|fn:nilled|fn:namespace-uri-from-QName|fn:namespace-uri-for-prefix|fn:namespace-uri|fn:name|fn:months-from-duration|fn:month-from-dateTime|fn:month-from-date|fn:minutes-from-time|fn:minutes-from-duration|fn:minutes-from-dateTime|fn:min|fn:max|fn:matches|fn:lower-case|fn:local-name-from-QName|fn:local-name|fn:last|fn:lang|fn:iri-to-uri|fn:insert-before|fn:index-of|fn:in-scope-prefixes|fn:implicit-timezone|fn:idref|fn:id|fn:hours-from-time|fn:hours-from-duration|fn:hours-from-dateTime|fn:floor|fn:false|fn:expanded-QName|fn:exists|fn:exactly-one|fn:escape-uri|fn:escape-html-uri|fn:error|fn:ends-with|fn:encode-for-uri|fn:empty|fn:document-uri|fn:doc-available|fn:doc|fn:distinct-values|fn:distinct-nodes|fn:default-collation|fn:deep-equal|fn:days-from-duration|fn:day-from-dateTime|fn:day-from-date|fn:data|fn:current-time|fn:current-dateTime|fn:current-date|fn:count|fn:contains|fn:concat|fn:compare|fn:collection|fn:codepoints-to-string|fn:codepoint-equal|fn:ceiling|fn:boolean|fn:base-uri|fn:avg|fn:adjust-time-to-timezone|fn:adjust-dateTime-to-timezone|fn:adjust-date-to-timezone|fn:abs|feed:unsubscribe|feed:subscription|feed:subscribe|feed:request|feed:item|feed:description|excel:clean|entity:enrich|dom:set-pipelines|dom:set-permissions|dom:set-name|dom:set-evaluation-context|dom:set-domain-scope|dom:set-description|dom:remove-pipeline|dom:remove-permissions|dom:remove|dom:get|dom:evaluation-context|dom:domains|dom:domain-scope|dom:create|dom:configuration-set-restart-user|dom:configuration-set-permissions|dom:configuration-set-evaluation-context|dom:configuration-set-default-domain|dom:configuration-get|dom:configuration-create|dom:collection|dom:add-pipeline|dom:add-permissions|dls:retention-rules|dls:retention-rule-remove|dls:retention-rule-insert|dls:retention-rule|dls:purge|dls:node-expand|dls:link-references|dls:link-expand|dls:documents-query|dls:document-versions-query|dls:document-version-uri|dls:document-version-query|dls:document-version-delete|dls:document-version-as-of|dls:document-version|dls:document-update|dls:document-unmanage|dls:document-set-quality|dls:document-set-property|dls:document-set-properties|dls:document-set-permissions|dls:document-set-collections|dls:document-retention-rules|dls:document-remove-properties|dls:document-remove-permissions|dls:document-remove-collections|dls:document-purge|dls:document-manage|dls:document-is-managed|dls:document-insert-and-manage|dls:document-include-query|dls:document-history|dls:document-get-permissions|dls:document-extract-part|dls:document-delete|dls:document-checkout-status|dls:document-checkout|dls:document-checkin|dls:document-add-properties|dls:document-add-permissions|dls:document-add-collections|dls:break-checkout|dls:author-query|dls:as-of-query|dbk:convert|dbg:wait|dbg:value|dbg:stopped|dbg:stop|dbg:step|dbg:status|dbg:stack|dbg:out|dbg:next|dbg:line|dbg:invoke|dbg:function|dbg:finish|dbg:expr|dbg:eval|dbg:disconnect|dbg:detach|dbg:continue|dbg:connect|dbg:clear|dbg:breakpoints|dbg:break|dbg:attached|dbg:attach|cvt:save-converted-documents|cvt:part-uri|cvt:destination-uri|cvt:basepath|cvt:basename|cts:words|cts:word-query-weight|cts:word-query-text|cts:word-query-options|cts:word-query|cts:word-match|cts:walk|cts:uris|cts:uri-match|cts:train|cts:tokenize|cts:thresholds|cts:stem|cts:similar-query-weight|cts:similar-query-nodes|cts:similar-query|cts:shortest-distance|cts:search|cts:score|cts:reverse-query-weight|cts:reverse-query-nodes|cts:reverse-query|cts:remainder|cts:registered-query-weight|cts:registered-query-options|cts:registered-query-ids|cts:registered-query|cts:register|cts:query|cts:quality|cts:properties-query-query|cts:properties-query|cts:polygon-vertices|cts:polygon|cts:point-longitude|cts:point-latitude|cts:point|cts:or-query-queries|cts:or-query|cts:not-query-weight|cts:not-query-query|cts:not-query|cts:near-query-weight|cts:near-query-queries|cts:near-query-options|cts:near-query-distance|cts:near-query|cts:highlight|cts:geospatial-co-occurrences|cts:frequency|cts:fitness|cts:field-words|cts:field-word-query-weight|cts:field-word-query-text|cts:field-word-query-options|cts:field-word-query-field-name|cts:field-word-query|cts:field-word-match|cts:entity-highlight|cts:element-words|cts:element-word-query-weight|cts:element-word-query-text|cts:element-word-query-options|cts:element-word-query-element-name|cts:element-word-query|cts:element-word-match|cts:element-values|cts:element-value-ranges|cts:element-value-query-weight|cts:element-value-query-text|cts:element-value-query-options|cts:element-value-query-element-name|cts:element-value-query|cts:element-value-match|cts:element-value-geospatial-co-occurrences|cts:element-value-co-occurrences|cts:element-range-query-weight|cts:element-range-query-value|cts:element-range-query-options|cts:element-range-query-operator|cts:element-range-query-element-name|cts:element-range-query|cts:element-query-query|cts:element-query-element-name|cts:element-query|cts:element-pair-geospatial-values|cts:element-pair-geospatial-value-match|cts:element-pair-geospatial-query-weight|cts:element-pair-geospatial-query-region|cts:element-pair-geospatial-query-options|cts:element-pair-geospatial-query-longitude-name|cts:element-pair-geospatial-query-latitude-name|cts:element-pair-geospatial-query-element-name|cts:element-pair-geospatial-query|cts:element-pair-geospatial-boxes|cts:element-geospatial-values|cts:element-geospatial-value-match|cts:element-geospatial-query-weight|cts:element-geospatial-query-region|cts:element-geospatial-query-options|cts:element-geospatial-query-element-name|cts:element-geospatial-query|cts:element-geospatial-boxes|cts:element-child-geospatial-values|cts:element-child-geospatial-value-match|cts:element-child-geospatial-query-weight|cts:element-child-geospatial-query-region|cts:element-child-geospatial-query-options|cts:element-child-geospatial-query-element-name|cts:element-child-geospatial-query-child-name|cts:element-child-geospatial-query|cts:element-child-geospatial-boxes|cts:element-attribute-words|cts:element-attribute-word-query-weight|cts:element-attribute-word-query-text|cts:element-attribute-word-query-options|cts:element-attribute-word-query-element-name|cts:element-attribute-word-query-attribute-name|cts:element-attribute-word-query|cts:element-attribute-word-match|cts:element-attribute-values|cts:element-attribute-value-ranges|cts:element-attribute-value-query-weight|cts:element-attribute-value-query-text|cts:element-attribute-value-query-options|cts:element-attribute-value-query-element-name|cts:element-attribute-value-query-attribute-name|cts:element-attribute-value-query|cts:element-attribute-value-match|cts:element-attribute-value-geospatial-co-occurrences|cts:element-attribute-value-co-occurrences|cts:element-attribute-range-query-weight|cts:element-attribute-range-query-value|cts:element-attribute-range-query-options|cts:element-attribute-range-query-operator|cts:element-attribute-range-query-element-name|cts:element-attribute-range-query-attribute-name|cts:element-attribute-range-query|cts:element-attribute-pair-geospatial-values|cts:element-attribute-pair-geospatial-value-match|cts:element-attribute-pair-geospatial-query-weight|cts:element-attribute-pair-geospatial-query-region|cts:element-attribute-pair-geospatial-query-options|cts:element-attribute-pair-geospatial-query-longitude-name|cts:element-attribute-pair-geospatial-query-latitude-name|cts:element-attribute-pair-geospatial-query-element-name|cts:element-attribute-pair-geospatial-query|cts:element-attribute-pair-geospatial-boxes|cts:document-query-uris|cts:document-query|cts:distance|cts:directory-query-uris|cts:directory-query-depth|cts:directory-query|cts:destination|cts:deregister|cts:contains|cts:confidence|cts:collections|cts:collection-query-uris|cts:collection-query|cts:collection-match|cts:classify|cts:circle-radius|cts:circle-center|cts:circle|cts:box-west|cts:box-south|cts:box-north|cts:box-east|cts:box|cts:bearing|cts:arc-intersection|cts:and-query-queries|cts:and-query-options|cts:and-query|cts:and-not-query-positive-query|cts:and-not-query-negative-query|cts:and-not-query|css:get|css:convert|cpf:success|cpf:failure|cpf:document-set-state|cpf:document-set-processing-status|cpf:document-set-last-updated|cpf:document-set-error|cpf:document-get-state|cpf:document-get-processing-status|cpf:document-get-last-updated|cpf:document-get-error|cpf:check-transition|alert:spawn-matching-actions|alert:rule-user-id-query|alert:rule-set-user-id|alert:rule-set-query|alert:rule-set-options|alert:rule-set-name|alert:rule-set-description|alert:rule-set-action|alert:rule-remove|alert:rule-name-query|alert:rule-insert|alert:rule-id-query|alert:rule-get-user-id|alert:rule-get-query|alert:rule-get-options|alert:rule-get-name|alert:rule-get-id|alert:rule-get-description|alert:rule-get-action|alert:rule-action-query|alert:remove-triggers|alert:make-rule|alert:make-log-action|alert:make-config|alert:make-action|alert:invoke-matching-actions|alert:get-my-rules|alert:get-all-rules|alert:get-actions|alert:find-matching-rules|alert:create-triggers|alert:config-set-uri|alert:config-set-trigger-ids|alert:config-set-options|alert:config-set-name|alert:config-set-description|alert:config-set-cpf-domain-names|alert:config-set-cpf-domain-ids|alert:config-insert|alert:config-get-uri|alert:config-get-trigger-ids|alert:config-get-options|alert:config-get-name|alert:config-get-id|alert:config-get-description|alert:config-get-cpf-domain-names|alert:config-get-cpf-domain-ids|alert:config-get|alert:config-delete|alert:action-set-options|alert:action-set-name|alert:action-set-module-root|alert:action-set-module-db|alert:action-set-module|alert:action-set-description|alert:action-remove|alert:action-insert|alert:action-get-options|alert:action-get-name|alert:action-get-module-root|alert:action-get-module-db|alert:action-get-module|alert:action-get-description|zero-or-one|years-from-duration|year-from-dateTime|year-from-date|upper-case|unordered|true|translate|trace|tokenize|timezone-from-time|timezone-from-dateTime|timezone-from-date|sum|subtract-dateTimes-yielding-yearMonthDuration|subtract-dateTimes-yielding-dayTimeDuration|substring-before|substring-after|substring|subsequence|string-to-codepoints|string-pad|string-length|string-join|string|static-base-uri|starts-with|seconds-from-time|seconds-from-duration|seconds-from-dateTime|round-half-to-even|round|root|reverse|resolve-uri|resolve-QName|replace|remove|QName|prefix-from-QName|position|one-or-more|number|not|normalize-unicode|normalize-space|node-name|node-kind|nilled|namespace-uri-from-QName|namespace-uri-for-prefix|namespace-uri|name|months-from-duration|month-from-dateTime|month-from-date|minutes-from-time|minutes-from-duration|minutes-from-dateTime|min|max|matches|lower-case|local-name-from-QName|local-name|last|lang|iri-to-uri|insert-before|index-of|in-scope-prefixes|implicit-timezone|idref|id|hours-from-time|hours-from-duration|hours-from-dateTime|floor|false|expanded-QName|exists|exactly-one|escape-uri|escape-html-uri|error|ends-with|encode-for-uri|empty|document-uri|doc-available|doc|distinct-values|distinct-nodes|default-collation|deep-equal|days-from-duration|day-from-dateTime|day-from-date|data|current-time|current-dateTime|current-date|count|contains|concat|compare|collection|codepoints-to-string|codepoint-equal|ceiling|boolean|base-uri|avg|adjust-time-to-timezone|adjust-dateTime-to-timezone|adjust-date-to-timezone|abs)\b/],
+["pln",/^[\w:-]+/],["pln",/^[\t\n\r \xa0]+/]]),["xq","xquery"]);
diff --git a/src/main/java/com/gitblit/wicket/pages/prettify/lang-yaml.js b/src/main/java/com/gitblit/wicket/pages/prettify/lang-yaml.js
new file mode 100644
index 0000000..c38729b
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/prettify/lang-yaml.js
@@ -0,0 +1,2 @@
+var a=null;
+PR.registerLangHandler(PR.createSimpleLexer([["pun",/^[:>?|]+/,a,":|>?"],["dec",/^%(?:YAML|TAG)[^\n\r#]+/,a,"%"],["typ",/^&\S+/,a,"&"],["typ",/^!\S*/,a,"!"],["str",/^"(?:[^"\\]|\\.)*(?:"|$)/,a,'"'],["str",/^'(?:[^']|'')*(?:'|$)/,a,"'"],["com",/^#[^\n\r]*/,a,"#"],["pln",/^\s+/,a," \t\r\n"]],[["dec",/^(?:---|\.\.\.)(?:[\n\r]|$)/],["pun",/^-/],["kwd",/^\w+:[\n\r ]/],["pln",/^\w+/]]),["yaml","yml"]);
diff --git a/src/main/java/com/gitblit/wicket/pages/prettify/prettify.css b/src/main/java/com/gitblit/wicket/pages/prettify/prettify.css
new file mode 100644
index 0000000..d44b3a2
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/prettify/prettify.css
@@ -0,0 +1 @@
+.pln{color:#000}@media screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun,.opn,.clo{color:#660}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec,.var{color:#606}.fun{color:red}}@media print,projection{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun,.opn,.clo{color:#440}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}pre.prettyprint{padding:2px;border:1px solid #888}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee}
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/pages/prettify/prettify.js b/src/main/java/com/gitblit/wicket/pages/prettify/prettify.js
new file mode 100644
index 0000000..7b99049
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/prettify/prettify.js
@@ -0,0 +1,30 @@
+!function(){var q=null;window.PR_SHOULD_USE_CONTINUATION=!0;
+(function(){function S(a){function d(e){var b=e.charCodeAt(0);if(b!==92)return b;var a=e.charAt(1);return(b=r[a])?b:"0"<=a&&a<="7"?parseInt(e.substring(1),8):a==="u"||a==="x"?parseInt(e.substring(2),16):e.charCodeAt(1)}function g(e){if(e<32)return(e<16?"\\x0":"\\x")+e.toString(16);e=String.fromCharCode(e);return e==="\\"||e==="-"||e==="]"||e==="^"?"\\"+e:e}function b(e){var b=e.substring(1,e.length-1).match(/\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\[0-3][0-7]{0,2}|\\[0-7]{1,2}|\\[\S\s]|[^\\]/g),e=[],a=
+b[0]==="^",c=["["];a&&c.push("^");for(var a=a?1:0,f=b.length;a<f;++a){var h=b[a];if(/\\[bdsw]/i.test(h))c.push(h);else{var h=d(h),l;a+2<f&&"-"===b[a+1]?(l=d(b[a+2]),a+=2):l=h;e.push([h,l]);l<65||h>122||(l<65||h>90||e.push([Math.max(65,h)|32,Math.min(l,90)|32]),l<97||h>122||e.push([Math.max(97,h)&-33,Math.min(l,122)&-33]))}}e.sort(function(e,a){return e[0]-a[0]||a[1]-e[1]});b=[];f=[];for(a=0;a<e.length;++a)h=e[a],h[0]<=f[1]+1?f[1]=Math.max(f[1],h[1]):b.push(f=h);for(a=0;a<b.length;++a)h=b[a],c.push(g(h[0])),
+h[1]>h[0]&&(h[1]+1>h[0]&&c.push("-"),c.push(g(h[1])));c.push("]");return c.join("")}function s(e){for(var a=e.source.match(/\[(?:[^\\\]]|\\[\S\s])*]|\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\\d+|\\[^\dux]|\(\?[!:=]|[()^]|[^()[\\^]+/g),c=a.length,d=[],f=0,h=0;f<c;++f){var l=a[f];l==="("?++h:"\\"===l.charAt(0)&&(l=+l.substring(1))&&(l<=h?d[l]=-1:a[f]=g(l))}for(f=1;f<d.length;++f)-1===d[f]&&(d[f]=++x);for(h=f=0;f<c;++f)l=a[f],l==="("?(++h,d[h]||(a[f]="(?:")):"\\"===l.charAt(0)&&(l=+l.substring(1))&&l<=h&&
+(a[f]="\\"+d[l]);for(f=0;f<c;++f)"^"===a[f]&&"^"!==a[f+1]&&(a[f]="");if(e.ignoreCase&&m)for(f=0;f<c;++f)l=a[f],e=l.charAt(0),l.length>=2&&e==="["?a[f]=b(l):e!=="\\"&&(a[f]=l.replace(/[A-Za-z]/g,function(a){a=a.charCodeAt(0);return"["+String.fromCharCode(a&-33,a|32)+"]"}));return a.join("")}for(var x=0,m=!1,j=!1,k=0,c=a.length;k<c;++k){var i=a[k];if(i.ignoreCase)j=!0;else if(/[a-z]/i.test(i.source.replace(/\\u[\da-f]{4}|\\x[\da-f]{2}|\\[^UXux]/gi,""))){m=!0;j=!1;break}}for(var r={b:8,t:9,n:10,v:11,
+f:12,r:13},n=[],k=0,c=a.length;k<c;++k){i=a[k];if(i.global||i.multiline)throw Error(""+i);n.push("(?:"+s(i)+")")}return RegExp(n.join("|"),j?"gi":"g")}function T(a,d){function g(a){var c=a.nodeType;if(c==1){if(!b.test(a.className)){for(c=a.firstChild;c;c=c.nextSibling)g(c);c=a.nodeName.toLowerCase();if("br"===c||"li"===c)s[j]="\n",m[j<<1]=x++,m[j++<<1|1]=a}}else if(c==3||c==4)c=a.nodeValue,c.length&&(c=d?c.replace(/\r\n?/g,"\n"):c.replace(/[\t\n\r ]+/g," "),s[j]=c,m[j<<1]=x,x+=c.length,m[j++<<1|1]=
+a)}var b=/(?:^|\s)nocode(?:\s|$)/,s=[],x=0,m=[],j=0;g(a);return{a:s.join("").replace(/\n$/,""),d:m}}function H(a,d,g,b){d&&(a={a:d,e:a},g(a),b.push.apply(b,a.g))}function U(a){for(var d=void 0,g=a.firstChild;g;g=g.nextSibling)var b=g.nodeType,d=b===1?d?a:g:b===3?V.test(g.nodeValue)?a:d:d;return d===a?void 0:d}function C(a,d){function g(a){for(var j=a.e,k=[j,"pln"],c=0,i=a.a.match(s)||[],r={},n=0,e=i.length;n<e;++n){var z=i[n],w=r[z],t=void 0,f;if(typeof w==="string")f=!1;else{var h=b[z.charAt(0)];
+if(h)t=z.match(h[1]),w=h[0];else{for(f=0;f<x;++f)if(h=d[f],t=z.match(h[1])){w=h[0];break}t||(w="pln")}if((f=w.length>=5&&"lang-"===w.substring(0,5))&&!(t&&typeof t[1]==="string"))f=!1,w="src";f||(r[z]=w)}h=c;c+=z.length;if(f){f=t[1];var l=z.indexOf(f),B=l+f.length;t[2]&&(B=z.length-t[2].length,l=B-f.length);w=w.substring(5);H(j+h,z.substring(0,l),g,k);H(j+h+l,f,I(w,f),k);H(j+h+B,z.substring(B),g,k)}else k.push(j+h,w)}a.g=k}var b={},s;(function(){for(var g=a.concat(d),j=[],k={},c=0,i=g.length;c<i;++c){var r=
+g[c],n=r[3];if(n)for(var e=n.length;--e>=0;)b[n.charAt(e)]=r;r=r[1];n=""+r;k.hasOwnProperty(n)||(j.push(r),k[n]=q)}j.push(/[\S\s]/);s=S(j)})();var x=d.length;return g}function v(a){var d=[],g=[];a.tripleQuotedStrings?d.push(["str",/^(?:'''(?:[^'\\]|\\[\S\s]|''?(?=[^']))*(?:'''|$)|"""(?:[^"\\]|\\[\S\s]|""?(?=[^"]))*(?:"""|$)|'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$))/,q,"'\""]):a.multiLineStrings?d.push(["str",/^(?:'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$)|`(?:[^\\`]|\\[\S\s])*(?:`|$))/,
+q,"'\"`"]):d.push(["str",/^(?:'(?:[^\n\r'\\]|\\.)*(?:'|$)|"(?:[^\n\r"\\]|\\.)*(?:"|$))/,q,"\"'"]);a.verbatimStrings&&g.push(["str",/^@"(?:[^"]|"")*(?:"|$)/,q]);var b=a.hashComments;b&&(a.cStyleComments?(b>1?d.push(["com",/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,q,"#"]):d.push(["com",/^#(?:(?:define|e(?:l|nd)if|else|error|ifn?def|include|line|pragma|undef|warning)\b|[^\n\r]*)/,q,"#"]),g.push(["str",/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h(?:h|pp|\+\+)?|[a-z]\w*)>/,q])):d.push(["com",
+/^#[^\n\r]*/,q,"#"]));a.cStyleComments&&(g.push(["com",/^\/\/[^\n\r]*/,q]),g.push(["com",/^\/\*[\S\s]*?(?:\*\/|$)/,q]));if(b=a.regexLiterals){var s=(b=b>1?"":"\n\r")?".":"[\\S\\s]";g.push(["lang-regex",RegExp("^(?:^^\\.?|[+-]|[!=]=?=?|\\#|%=?|&&?=?|\\(|\\*=?|[+\\-]=|->|\\/=?|::?|<<?=?|>>?>?=?|,|;|\\?|@|\\[|~|{|\\^\\^?=?|\\|\\|?=?|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\\s*("+("/(?=[^/*"+b+"])(?:[^/\\x5B\\x5C"+b+"]|\\x5C"+s+"|\\x5B(?:[^\\x5C\\x5D"+b+"]|\\x5C"+
+s+")*(?:\\x5D|$))+/")+")")])}(b=a.types)&&g.push(["typ",b]);b=(""+a.keywords).replace(/^ | $/g,"");b.length&&g.push(["kwd",RegExp("^(?:"+b.replace(/[\s,]+/g,"|")+")\\b"),q]);d.push(["pln",/^\s+/,q," \r\n\t\u00a0"]);b="^.[^\\s\\w.$@'\"`/\\\\]*";a.regexLiterals&&(b+="(?!s*/)");g.push(["lit",/^@[$_a-z][\w$@]*/i,q],["typ",/^(?:[@_]?[A-Z]+[a-z][\w$@]*|\w+_t\b)/,q],["pln",/^[$_a-z][\w$@]*/i,q],["lit",/^(?:0x[\da-f]+|(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d\+)(?:e[+-]?\d+)?)[a-z]*/i,q,"0123456789"],["pln",/^\\[\S\s]?/,
+q],["pun",RegExp(b),q]);return C(d,g)}function J(a,d,g){function b(a){var c=a.nodeType;if(c==1&&!x.test(a.className))if("br"===a.nodeName)s(a),a.parentNode&&a.parentNode.removeChild(a);else for(a=a.firstChild;a;a=a.nextSibling)b(a);else if((c==3||c==4)&&g){var d=a.nodeValue,i=d.match(m);if(i)c=d.substring(0,i.index),a.nodeValue=c,(d=d.substring(i.index+i[0].length))&&a.parentNode.insertBefore(j.createTextNode(d),a.nextSibling),s(a),c||a.parentNode.removeChild(a)}}function s(a){function b(a,c){var d=
+c?a.cloneNode(!1):a,e=a.parentNode;if(e){var e=b(e,1),g=a.nextSibling;e.appendChild(d);for(var i=g;i;i=g)g=i.nextSibling,e.appendChild(i)}return d}for(;!a.nextSibling;)if(a=a.parentNode,!a)return;for(var a=b(a.nextSibling,0),d;(d=a.parentNode)&&d.nodeType===1;)a=d;c.push(a)}for(var x=/(?:^|\s)nocode(?:\s|$)/,m=/\r\n?|\n/,j=a.ownerDocument,k=j.createElement("li");a.firstChild;)k.appendChild(a.firstChild);for(var c=[k],i=0;i<c.length;++i)b(c[i]);d===(d|0)&&c[0].setAttribute("value",d);var r=j.createElement("ol");
+r.className="linenums";for(var d=Math.max(0,d-1|0)||0,i=0,n=c.length;i<n;++i)k=c[i],k.className="L"+(i+d)%10,k.firstChild||k.appendChild(j.createTextNode("\u00a0")),r.appendChild(k);a.appendChild(r)}function p(a,d){for(var g=d.length;--g>=0;){var b=d[g];F.hasOwnProperty(b)?D.console&&console.warn("cannot override language handler %s",b):F[b]=a}}function I(a,d){if(!a||!F.hasOwnProperty(a))a=/^\s*</.test(d)?"default-markup":"default-code";return F[a]}function K(a){var d=a.h;try{var g=T(a.c,a.i),b=g.a;
+a.a=b;a.d=g.d;a.e=0;I(d,b)(a);var s=/\bMSIE\s(\d+)/.exec(navigator.userAgent),s=s&&+s[1]<=8,d=/\n/g,x=a.a,m=x.length,g=0,j=a.d,k=j.length,b=0,c=a.g,i=c.length,r=0;c[i]=m;var n,e;for(e=n=0;e<i;)c[e]!==c[e+2]?(c[n++]=c[e++],c[n++]=c[e++]):e+=2;i=n;for(e=n=0;e<i;){for(var p=c[e],w=c[e+1],t=e+2;t+2<=i&&c[t+1]===w;)t+=2;c[n++]=p;c[n++]=w;e=t}c.length=n;var f=a.c,h;if(f)h=f.style.display,f.style.display="none";try{for(;b<k;){var l=j[b+2]||m,B=c[r+2]||m,t=Math.min(l,B),A=j[b+1],G;if(A.nodeType!==1&&(G=x.substring(g,
+t))){s&&(G=G.replace(d,"\r"));A.nodeValue=G;var L=A.ownerDocument,o=L.createElement("span");o.className=c[r+1];var v=A.parentNode;v.replaceChild(o,A);o.appendChild(A);g<l&&(j[b+1]=A=L.createTextNode(x.substring(t,l)),v.insertBefore(A,o.nextSibling))}g=t;g>=l&&(b+=2);g>=B&&(r+=2)}}finally{if(f)f.style.display=h}}catch(u){D.console&&console.log(u&&u.stack||u)}}var D=window,y=["break,continue,do,else,for,if,return,while"],E=[[y,"auto,case,char,const,default,double,enum,extern,float,goto,inline,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"],
+"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"],M=[E,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,delegate,dynamic_cast,explicit,export,friend,generic,late_check,mutable,namespace,nullptr,property,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"],N=[E,"abstract,assert,boolean,byte,extends,final,finally,implements,import,instanceof,interface,null,native,package,strictfp,super,synchronized,throws,transient"],
+O=[N,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,internal,into,is,let,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var,virtual,where"],E=[E,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"],P=[y,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"],
+Q=[y,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"],W=[y,"as,assert,const,copy,drop,enum,extern,fail,false,fn,impl,let,log,loop,match,mod,move,mut,priv,pub,pure,ref,self,static,struct,true,trait,type,unsafe,use"],y=[y,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"],R=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)\b/,
+V=/\S/,X=v({keywords:[M,O,E,"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END",P,Q,y],hashComments:!0,cStyleComments:!0,multiLineStrings:!0,regexLiterals:!0}),F={};p(X,["default-code"]);p(C([],[["pln",/^[^<?]+/],["dec",/^<!\w[^>]*(?:>|$)/],["com",/^<\!--[\S\s]*?(?:--\>|$)/],["lang-",/^<\?([\S\s]+?)(?:\?>|$)/],["lang-",/^<%([\S\s]+?)(?:%>|$)/],["pun",/^(?:<[%?]|[%?]>)/],["lang-",
+/^<xmp\b[^>]*>([\S\s]+?)<\/xmp\b[^>]*>/i],["lang-js",/^<script\b[^>]*>([\S\s]*?)(<\/script\b[^>]*>)/i],["lang-css",/^<style\b[^>]*>([\S\s]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]),["default-markup","htm","html","mxml","xhtml","xml","xsl"]);p(C([["pln",/^\s+/,q," \t\r\n"],["atv",/^(?:"[^"]*"?|'[^']*'?)/,q,"\"'"]],[["tag",/^^<\/?[a-z](?:[\w-.:]*\w)?|\/?>$/i],["atn",/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^\s"'>]*(?:[^\s"'/>]|\/(?=\s)))/],["pun",/^[/<->]+/],
+["lang-js",/^on\w+\s*=\s*"([^"]+)"/i],["lang-js",/^on\w+\s*=\s*'([^']+)'/i],["lang-js",/^on\w+\s*=\s*([^\s"'>]+)/i],["lang-css",/^style\s*=\s*"([^"]+)"/i],["lang-css",/^style\s*=\s*'([^']+)'/i],["lang-css",/^style\s*=\s*([^\s"'>]+)/i]]),["in.tag"]);p(C([],[["atv",/^[\S\s]+/]]),["uq.val"]);p(v({keywords:M,hashComments:!0,cStyleComments:!0,types:R}),["c","cc","cpp","cxx","cyc","m"]);p(v({keywords:"null,true,false"}),["json"]);p(v({keywords:O,hashComments:!0,cStyleComments:!0,verbatimStrings:!0,types:R}),
+["cs"]);p(v({keywords:N,cStyleComments:!0}),["java"]);p(v({keywords:y,hashComments:!0,multiLineStrings:!0}),["bash","bsh","csh","sh"]);p(v({keywords:P,hashComments:!0,multiLineStrings:!0,tripleQuotedStrings:!0}),["cv","py","python"]);p(v({keywords:"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END",hashComments:!0,multiLineStrings:!0,regexLiterals:2}),["perl","pl","pm"]);p(v({keywords:Q,
+hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["rb","ruby"]);p(v({keywords:E,cStyleComments:!0,regexLiterals:!0}),["javascript","js"]);p(v({keywords:"all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,throw,true,try,unless,until,when,while,yes",hashComments:3,cStyleComments:!0,multilineStrings:!0,tripleQuotedStrings:!0,regexLiterals:!0}),["coffee"]);p(v({keywords:W,cStyleComments:!0,multilineStrings:!0}),["rc","rs","rust"]);
+p(C([],[["str",/^[\S\s]+/]]),["regex"]);var Y=D.PR={createSimpleLexer:C,registerLangHandler:p,sourceDecorator:v,PR_ATTRIB_NAME:"atn",PR_ATTRIB_VALUE:"atv",PR_COMMENT:"com",PR_DECLARATION:"dec",PR_KEYWORD:"kwd",PR_LITERAL:"lit",PR_NOCODE:"nocode",PR_PLAIN:"pln",PR_PUNCTUATION:"pun",PR_SOURCE:"src",PR_STRING:"str",PR_TAG:"tag",PR_TYPE:"typ",prettyPrintOne:D.prettyPrintOne=function(a,d,g){var b=document.createElement("div");b.innerHTML="<pre>"+a+"</pre>";b=b.firstChild;g&&J(b,g,!0);K({h:d,j:g,c:b,i:1});
+return b.innerHTML},prettyPrint:D.prettyPrint=function(a,d){function g(){for(var b=D.PR_SHOULD_USE_CONTINUATION?c.now()+250:Infinity;i<p.length&&c.now()<b;i++){for(var d=p[i],j=h,k=d;k=k.previousSibling;){var m=k.nodeType,o=(m===7||m===8)&&k.nodeValue;if(o?!/^\??prettify\b/.test(o):m!==3||/\S/.test(k.nodeValue))break;if(o){j={};o.replace(/\b(\w+)=([\w%+\-.:]+)/g,function(a,b,c){j[b]=c});break}}k=d.className;if((j!==h||e.test(k))&&!v.test(k)){m=!1;for(o=d.parentNode;o;o=o.parentNode)if(f.test(o.tagName)&&
+o.className&&e.test(o.className)){m=!0;break}if(!m){d.className+=" prettyprinted";m=j.lang;if(!m){var m=k.match(n),y;if(!m&&(y=U(d))&&t.test(y.tagName))m=y.className.match(n);m&&(m=m[1])}if(w.test(d.tagName))o=1;else var o=d.currentStyle,u=s.defaultView,o=(o=o?o.whiteSpace:u&&u.getComputedStyle?u.getComputedStyle(d,q).getPropertyValue("white-space"):0)&&"pre"===o.substring(0,3);u=j.linenums;if(!(u=u==="true"||+u))u=(u=k.match(/\blinenums\b(?::(\d+))?/))?u[1]&&u[1].length?+u[1]:!0:!1;u&&J(d,u,o);r=
+{h:m,c:d,j:u,i:o};K(r)}}}i<p.length?setTimeout(g,250):"function"===typeof a&&a()}for(var b=d||document.body,s=b.ownerDocument||document,b=[b.getElementsByTagName("pre"),b.getElementsByTagName("code"),b.getElementsByTagName("xmp")],p=[],m=0;m<b.length;++m)for(var j=0,k=b[m].length;j<k;++j)p.push(b[m][j]);var b=q,c=Date;c.now||(c={now:function(){return+new Date}});var i=0,r,n=/\blang(?:uage)?-([\w.]+)(?!\S)/,e=/\bprettyprint\b/,v=/\bprettyprinted\b/,w=/pre|xmp/i,t=/^code$/i,f=/^(?:pre|code|xmp)$/i,
+h={};g()}};typeof define==="function"&&define.amd&&define("google-code-prettify",[],function(){return Y})})();}()
diff --git a/src/main/java/com/gitblit/wicket/pages/prettify/run_prettify.js b/src/main/java/com/gitblit/wicket/pages/prettify/run_prettify.js
new file mode 100644
index 0000000..9eb3e16
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/prettify/run_prettify.js
@@ -0,0 +1,34 @@
+!function(){var r=null;
+(function(){function X(e){function j(){try{J.doScroll("left")}catch(e){P(j,50);return}w("poll")}function w(j){if(!(j.type=="readystatechange"&&x.readyState!="complete")&&((j.type=="load"?n:x)[z](i+j.type,w,!1),!m&&(m=!0)))e.call(n,j.type||j)}var Y=x.addEventListener,m=!1,C=!0,t=Y?"addEventListener":"attachEvent",z=Y?"removeEventListener":"detachEvent",i=Y?"":"on";if(x.readyState=="complete")e.call(n,"lazy");else{if(x.createEventObject&&J.doScroll){try{C=!n.frameElement}catch(A){}C&&j()}x[t](i+"DOMContentLoaded",
+w,!1);x[t](i+"readystatechange",w,!1);n[t](i+"load",w,!1)}}function Q(){S&&X(function(){var e=K.length;$(e?function(){for(var j=0;j<e;++j)(function(e){P(function(){n.exports[K[e]].apply(n,arguments)},0)})(j)}:void 0)})}for(var n=window,P=n.setTimeout,x=document,J=x.documentElement,L=x.head||x.getElementsByTagName("head")[0]||J,z="",A=x.scripts,m=A.length;--m>=0;){var M=A[m],T=M.src.match(/^[^#?]*\/run_prettify\.js(\?[^#]*)?(?:#.*)?$/);if(T){z=T[1]||"";M.parentNode.removeChild(M);break}}var S=!0,D=
+[],N=[],K=[];z.replace(/[&?]([^&=]+)=([^&]+)/g,function(e,j,w){w=decodeURIComponent(w);j=decodeURIComponent(j);j=="autorun"?S=!/^[0fn]/i.test(w):j=="lang"?D.push(w):j=="skin"?N.push(w):j=="callback"&&K.push(w)});m=0;for(z=D.length;m<z;++m)(function(){var e=x.createElement("script");e.onload=e.onerror=e.onreadystatechange=function(){if(e&&(!e.readyState||/loaded|complete/.test(e.readyState)))e.onerror=e.onload=e.onreadystatechange=r,--R,R||P(Q,0),e.parentNode&&e.parentNode.removeChild(e),e=r};e.type=
+"text/javascript";e.src="https://google-code-prettify.googlecode.com/svn/loader/lang-"+encodeURIComponent(D[m])+".js";L.insertBefore(e,L.firstChild)})(D[m]);for(var R=D.length,A=[],m=0,z=N.length;m<z;++m)A.push("https://google-code-prettify.googlecode.com/svn/loader/skins/"+encodeURIComponent(N[m])+".css");A.push("https://google-code-prettify.googlecode.com/svn/loader/prettify.css");(function(e){function j(m){if(m!==w){var n=x.createElement("link");n.rel="stylesheet";n.type="text/css";if(m+1<w)n.error=
+n.onerror=function(){j(m+1)};n.href=e[m];L.appendChild(n)}}var w=e.length;j(0)})(A);var $=function(){window.PR_SHOULD_USE_CONTINUATION=!0;var e;(function(){function j(a){function d(f){var b=f.charCodeAt(0);if(b!==92)return b;var a=f.charAt(1);return(b=i[a])?b:"0"<=a&&a<="7"?parseInt(f.substring(1),8):a==="u"||a==="x"?parseInt(f.substring(2),16):f.charCodeAt(1)}function h(f){if(f<32)return(f<16?"\\x0":"\\x")+f.toString(16);f=String.fromCharCode(f);return f==="\\"||f==="-"||f==="]"||f==="^"?"\\"+f:
+f}function b(f){var b=f.substring(1,f.length-1).match(/\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\[0-3][0-7]{0,2}|\\[0-7]{1,2}|\\[\S\s]|[^\\]/g),f=[],a=b[0]==="^",c=["["];a&&c.push("^");for(var a=a?1:0,g=b.length;a<g;++a){var k=b[a];if(/\\[bdsw]/i.test(k))c.push(k);else{var k=d(k),o;a+2<g&&"-"===b[a+1]?(o=d(b[a+2]),a+=2):o=k;f.push([k,o]);o<65||k>122||(o<65||k>90||f.push([Math.max(65,k)|32,Math.min(o,90)|32]),o<97||k>122||f.push([Math.max(97,k)&-33,Math.min(o,122)&-33]))}}f.sort(function(f,a){return f[0]-
+a[0]||a[1]-f[1]});b=[];g=[];for(a=0;a<f.length;++a)k=f[a],k[0]<=g[1]+1?g[1]=Math.max(g[1],k[1]):b.push(g=k);for(a=0;a<b.length;++a)k=b[a],c.push(h(k[0])),k[1]>k[0]&&(k[1]+1>k[0]&&c.push("-"),c.push(h(k[1])));c.push("]");return c.join("")}function e(f){for(var a=f.source.match(/\[(?:[^\\\]]|\\[\S\s])*]|\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\\d+|\\[^\dux]|\(\?[!:=]|[()^]|[^()[\\^]+/g),c=a.length,d=[],g=0,k=0;g<c;++g){var o=a[g];o==="("?++k:"\\"===o.charAt(0)&&(o=+o.substring(1))&&(o<=k?d[o]=-1:a[g]=h(o))}for(g=
+1;g<d.length;++g)-1===d[g]&&(d[g]=++j);for(k=g=0;g<c;++g)o=a[g],o==="("?(++k,d[k]||(a[g]="(?:")):"\\"===o.charAt(0)&&(o=+o.substring(1))&&o<=k&&(a[g]="\\"+d[o]);for(g=0;g<c;++g)"^"===a[g]&&"^"!==a[g+1]&&(a[g]="");if(f.ignoreCase&&F)for(g=0;g<c;++g)o=a[g],f=o.charAt(0),o.length>=2&&f==="["?a[g]=b(o):f!=="\\"&&(a[g]=o.replace(/[A-Za-z]/g,function(a){a=a.charCodeAt(0);return"["+String.fromCharCode(a&-33,a|32)+"]"}));return a.join("")}for(var j=0,F=!1,l=!1,I=0,c=a.length;I<c;++I){var p=a[I];if(p.ignoreCase)l=
+!0;else if(/[a-z]/i.test(p.source.replace(/\\u[\da-f]{4}|\\x[\da-f]{2}|\\[^UXux]/gi,""))){F=!0;l=!1;break}}for(var i={b:8,t:9,n:10,v:11,f:12,r:13},q=[],I=0,c=a.length;I<c;++I){p=a[I];if(p.global||p.multiline)throw Error(""+p);q.push("(?:"+e(p)+")")}return RegExp(q.join("|"),l?"gi":"g")}function m(a,d){function h(a){var c=a.nodeType;if(c==1){if(!b.test(a.className)){for(c=a.firstChild;c;c=c.nextSibling)h(c);c=a.nodeName.toLowerCase();if("br"===c||"li"===c)e[l]="\n",F[l<<1]=j++,F[l++<<1|1]=a}}else if(c==
+3||c==4)c=a.nodeValue,c.length&&(c=d?c.replace(/\r\n?/g,"\n"):c.replace(/[\t\n\r ]+/g," "),e[l]=c,F[l<<1]=j,j+=c.length,F[l++<<1|1]=a)}var b=/(?:^|\s)nocode(?:\s|$)/,e=[],j=0,F=[],l=0;h(a);return{a:e.join("").replace(/\n$/,""),d:F}}function n(a,d,h,b){d&&(a={a:d,e:a},h(a),b.push.apply(b,a.g))}function x(a){for(var d=void 0,h=a.firstChild;h;h=h.nextSibling)var b=h.nodeType,d=b===1?d?a:h:b===3?S.test(h.nodeValue)?a:d:d;return d===a?void 0:d}function C(a,d){function h(a){for(var l=a.e,j=[l,"pln"],c=
+0,p=a.a.match(e)||[],m={},q=0,f=p.length;q<f;++q){var B=p[q],y=m[B],u=void 0,g;if(typeof y==="string")g=!1;else{var k=b[B.charAt(0)];if(k)u=B.match(k[1]),y=k[0];else{for(g=0;g<i;++g)if(k=d[g],u=B.match(k[1])){y=k[0];break}u||(y="pln")}if((g=y.length>=5&&"lang-"===y.substring(0,5))&&!(u&&typeof u[1]==="string"))g=!1,y="src";g||(m[B]=y)}k=c;c+=B.length;if(g){g=u[1];var o=B.indexOf(g),H=o+g.length;u[2]&&(H=B.length-u[2].length,o=H-g.length);y=y.substring(5);n(l+k,B.substring(0,o),h,j);n(l+k+o,g,A(y,
+g),j);n(l+k+H,B.substring(H),h,j)}else j.push(l+k,y)}a.g=j}var b={},e;(function(){for(var h=a.concat(d),l=[],i={},c=0,p=h.length;c<p;++c){var m=h[c],q=m[3];if(q)for(var f=q.length;--f>=0;)b[q.charAt(f)]=m;m=m[1];q=""+m;i.hasOwnProperty(q)||(l.push(m),i[q]=r)}l.push(/[\S\s]/);e=j(l)})();var i=d.length;return h}function t(a){var d=[],h=[];a.tripleQuotedStrings?d.push(["str",/^(?:'''(?:[^'\\]|\\[\S\s]|''?(?=[^']))*(?:'''|$)|"""(?:[^"\\]|\\[\S\s]|""?(?=[^"]))*(?:"""|$)|'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$))/,
+r,"'\""]):a.multiLineStrings?d.push(["str",/^(?:'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$)|`(?:[^\\`]|\\[\S\s])*(?:`|$))/,r,"'\"`"]):d.push(["str",/^(?:'(?:[^\n\r'\\]|\\.)*(?:'|$)|"(?:[^\n\r"\\]|\\.)*(?:"|$))/,r,"\"'"]);a.verbatimStrings&&h.push(["str",/^@"(?:[^"]|"")*(?:"|$)/,r]);var b=a.hashComments;b&&(a.cStyleComments?(b>1?d.push(["com",/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,r,"#"]):d.push(["com",/^#(?:(?:define|e(?:l|nd)if|else|error|ifn?def|include|line|pragma|undef|warning)\b|[^\n\r]*)/,
+r,"#"]),h.push(["str",/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h(?:h|pp|\+\+)?|[a-z]\w*)>/,r])):d.push(["com",/^#[^\n\r]*/,r,"#"]));a.cStyleComments&&(h.push(["com",/^\/\/[^\n\r]*/,r]),h.push(["com",/^\/\*[\S\s]*?(?:\*\/|$)/,r]));if(b=a.regexLiterals){var e=(b=b>1?"":"\n\r")?".":"[\\S\\s]";h.push(["lang-regex",RegExp("^(?:^^\\.?|[+-]|[!=]=?=?|\\#|%=?|&&?=?|\\(|\\*=?|[+\\-]=|->|\\/=?|::?|<<?=?|>>?>?=?|,|;|\\?|@|\\[|~|{|\\^\\^?=?|\\|\\|?=?|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\\s*("+
+("/(?=[^/*"+b+"])(?:[^/\\x5B\\x5C"+b+"]|\\x5C"+e+"|\\x5B(?:[^\\x5C\\x5D"+b+"]|\\x5C"+e+")*(?:\\x5D|$))+/")+")")])}(b=a.types)&&h.push(["typ",b]);b=(""+a.keywords).replace(/^ | $/g,"");b.length&&h.push(["kwd",RegExp("^(?:"+b.replace(/[\s,]+/g,"|")+")\\b"),r]);d.push(["pln",/^\s+/,r," \r\n\t\u00a0"]);b="^.[^\\s\\w.$@'\"`/\\\\]*";a.regexLiterals&&(b+="(?!s*/)");h.push(["lit",/^@[$_a-z][\w$@]*/i,r],["typ",/^(?:[@_]?[A-Z]+[a-z][\w$@]*|\w+_t\b)/,r],["pln",/^[$_a-z][\w$@]*/i,r],["lit",/^(?:0x[\da-f]+|(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d\+)(?:e[+-]?\d+)?)[a-z]*/i,
+r,"0123456789"],["pln",/^\\[\S\s]?/,r],["pun",RegExp(b),r]);return C(d,h)}function z(a,d,h){function b(a){var c=a.nodeType;if(c==1&&!j.test(a.className))if("br"===a.nodeName)e(a),a.parentNode&&a.parentNode.removeChild(a);else for(a=a.firstChild;a;a=a.nextSibling)b(a);else if((c==3||c==4)&&h){var d=a.nodeValue,i=d.match(m);if(i)c=d.substring(0,i.index),a.nodeValue=c,(d=d.substring(i.index+i[0].length))&&a.parentNode.insertBefore(l.createTextNode(d),a.nextSibling),e(a),c||a.parentNode.removeChild(a)}}
+function e(a){function b(a,c){var d=c?a.cloneNode(!1):a,f=a.parentNode;if(f){var f=b(f,1),h=a.nextSibling;f.appendChild(d);for(var e=h;e;e=h)h=e.nextSibling,f.appendChild(e)}return d}for(;!a.nextSibling;)if(a=a.parentNode,!a)return;for(var a=b(a.nextSibling,0),d;(d=a.parentNode)&&d.nodeType===1;)a=d;c.push(a)}for(var j=/(?:^|\s)nocode(?:\s|$)/,m=/\r\n?|\n/,l=a.ownerDocument,i=l.createElement("li");a.firstChild;)i.appendChild(a.firstChild);for(var c=[i],p=0;p<c.length;++p)b(c[p]);d===(d|0)&&c[0].setAttribute("value",
+d);var n=l.createElement("ol");n.className="linenums";for(var d=Math.max(0,d-1|0)||0,p=0,q=c.length;p<q;++p)i=c[p],i.className="L"+(p+d)%10,i.firstChild||i.appendChild(l.createTextNode("\u00a0")),n.appendChild(i);a.appendChild(n)}function i(a,d){for(var h=d.length;--h>=0;){var b=d[h];U.hasOwnProperty(b)?V.console&&console.warn("cannot override language handler %s",b):U[b]=a}}function A(a,d){if(!a||!U.hasOwnProperty(a))a=/^\s*</.test(d)?"default-markup":"default-code";return U[a]}function D(a){var d=
+a.h;try{var h=m(a.c,a.i),b=h.a;a.a=b;a.d=h.d;a.e=0;A(d,b)(a);var e=/\bMSIE\s(\d+)/.exec(navigator.userAgent),e=e&&+e[1]<=8,d=/\n/g,i=a.a,j=i.length,h=0,l=a.d,n=l.length,b=0,c=a.g,p=c.length,t=0;c[p]=j;var q,f;for(f=q=0;f<p;)c[f]!==c[f+2]?(c[q++]=c[f++],c[q++]=c[f++]):f+=2;p=q;for(f=q=0;f<p;){for(var x=c[f],y=c[f+1],u=f+2;u+2<=p&&c[u+1]===y;)u+=2;c[q++]=x;c[q++]=y;f=u}c.length=q;var g=a.c,k;if(g)k=g.style.display,g.style.display="none";try{for(;b<n;){var o=l[b+2]||j,H=c[t+2]||j,u=Math.min(o,H),E=l[b+
+1],W;if(E.nodeType!==1&&(W=i.substring(h,u))){e&&(W=W.replace(d,"\r"));E.nodeValue=W;var Z=E.ownerDocument,s=Z.createElement("span");s.className=c[t+1];var z=E.parentNode;z.replaceChild(s,E);s.appendChild(E);h<o&&(l[b+1]=E=Z.createTextNode(i.substring(u,o)),z.insertBefore(E,s.nextSibling))}h=u;h>=o&&(b+=2);h>=H&&(t+=2)}}finally{if(g)g.style.display=k}}catch(v){V.console&&console.log(v&&v.stack||v)}}var V=window,G=["break,continue,do,else,for,if,return,while"],O=[[G,"auto,case,char,const,default,double,enum,extern,float,goto,inline,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"],
+"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"],J=[O,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,delegate,dynamic_cast,explicit,export,friend,generic,late_check,mutable,namespace,nullptr,property,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"],K=[O,"abstract,assert,boolean,byte,extends,final,finally,implements,import,instanceof,interface,null,native,package,strictfp,super,synchronized,throws,transient"],
+L=[K,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,internal,into,is,let,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var,virtual,where"],O=[O,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"],M=[G,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"],
+N=[G,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"],R=[G,"as,assert,const,copy,drop,enum,extern,fail,false,fn,impl,let,log,loop,match,mod,move,mut,priv,pub,pure,ref,self,static,struct,true,trait,type,unsafe,use"],G=[G,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"],Q=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)\b/,
+S=/\S/,T=t({keywords:[J,L,O,"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END",M,N,G],hashComments:!0,cStyleComments:!0,multiLineStrings:!0,regexLiterals:!0}),U={};i(T,["default-code"]);i(C([],[["pln",/^[^<?]+/],["dec",/^<!\w[^>]*(?:>|$)/],["com",/^<\!--[\S\s]*?(?:--\>|$)/],["lang-",/^<\?([\S\s]+?)(?:\?>|$)/],["lang-",/^<%([\S\s]+?)(?:%>|$)/],["pun",/^(?:<[%?]|[%?]>)/],["lang-",
+/^<xmp\b[^>]*>([\S\s]+?)<\/xmp\b[^>]*>/i],["lang-js",/^<script\b[^>]*>([\S\s]*?)(<\/script\b[^>]*>)/i],["lang-css",/^<style\b[^>]*>([\S\s]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]),["default-markup","htm","html","mxml","xhtml","xml","xsl"]);i(C([["pln",/^\s+/,r," \t\r\n"],["atv",/^(?:"[^"]*"?|'[^']*'?)/,r,"\"'"]],[["tag",/^^<\/?[a-z](?:[\w-.:]*\w)?|\/?>$/i],["atn",/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^\s"'>]*(?:[^\s"'/>]|\/(?=\s)))/],["pun",/^[/<->]+/],
+["lang-js",/^on\w+\s*=\s*"([^"]+)"/i],["lang-js",/^on\w+\s*=\s*'([^']+)'/i],["lang-js",/^on\w+\s*=\s*([^\s"'>]+)/i],["lang-css",/^style\s*=\s*"([^"]+)"/i],["lang-css",/^style\s*=\s*'([^']+)'/i],["lang-css",/^style\s*=\s*([^\s"'>]+)/i]]),["in.tag"]);i(C([],[["atv",/^[\S\s]+/]]),["uq.val"]);i(t({keywords:J,hashComments:!0,cStyleComments:!0,types:Q}),["c","cc","cpp","cxx","cyc","m"]);i(t({keywords:"null,true,false"}),["json"]);i(t({keywords:L,hashComments:!0,cStyleComments:!0,verbatimStrings:!0,types:Q}),
+["cs"]);i(t({keywords:K,cStyleComments:!0}),["java"]);i(t({keywords:G,hashComments:!0,multiLineStrings:!0}),["bash","bsh","csh","sh"]);i(t({keywords:M,hashComments:!0,multiLineStrings:!0,tripleQuotedStrings:!0}),["cv","py","python"]);i(t({keywords:"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END",hashComments:!0,multiLineStrings:!0,regexLiterals:2}),["perl","pl","pm"]);i(t({keywords:N,
+hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["rb","ruby"]);i(t({keywords:O,cStyleComments:!0,regexLiterals:!0}),["javascript","js"]);i(t({keywords:"all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,throw,true,try,unless,until,when,while,yes",hashComments:3,cStyleComments:!0,multilineStrings:!0,tripleQuotedStrings:!0,regexLiterals:!0}),["coffee"]);i(t({keywords:R,cStyleComments:!0,multilineStrings:!0}),["rc","rs","rust"]);
+i(C([],[["str",/^[\S\s]+/]]),["regex"]);var X=V.PR={createSimpleLexer:C,registerLangHandler:i,sourceDecorator:t,PR_ATTRIB_NAME:"atn",PR_ATTRIB_VALUE:"atv",PR_COMMENT:"com",PR_DECLARATION:"dec",PR_KEYWORD:"kwd",PR_LITERAL:"lit",PR_NOCODE:"nocode",PR_PLAIN:"pln",PR_PUNCTUATION:"pun",PR_SOURCE:"src",PR_STRING:"str",PR_TAG:"tag",PR_TYPE:"typ",prettyPrintOne:function(a,d,e){var b=document.createElement("div");b.innerHTML="<pre>"+a+"</pre>";b=b.firstChild;e&&z(b,e,!0);D({h:d,j:e,c:b,i:1});return b.innerHTML},
+prettyPrint:e=e=function(a,d){function e(){for(var b=V.PR_SHOULD_USE_CONTINUATION?c.now()+250:Infinity;p<j.length&&c.now()<b;p++){for(var d=j[p],m=k,l=d;l=l.previousSibling;){var n=l.nodeType,s=(n===7||n===8)&&l.nodeValue;if(s?!/^\??prettify\b/.test(s):n!==3||/\S/.test(l.nodeValue))break;if(s){m={};s.replace(/\b(\w+)=([\w%+\-.:]+)/g,function(a,b,c){m[b]=c});break}}l=d.className;if((m!==k||f.test(l))&&!w.test(l)){n=!1;for(s=d.parentNode;s;s=s.parentNode)if(g.test(s.tagName)&&s.className&&f.test(s.className)){n=
+!0;break}if(!n){d.className+=" prettyprinted";n=m.lang;if(!n){var n=l.match(q),A;if(!n&&(A=x(d))&&u.test(A.tagName))n=A.className.match(q);n&&(n=n[1])}if(y.test(d.tagName))s=1;else var s=d.currentStyle,v=i.defaultView,s=(s=s?s.whiteSpace:v&&v.getComputedStyle?v.getComputedStyle(d,r).getPropertyValue("white-space"):0)&&"pre"===s.substring(0,3);v=m.linenums;if(!(v=v==="true"||+v))v=(v=l.match(/\blinenums\b(?::(\d+))?/))?v[1]&&v[1].length?+v[1]:!0:!1;v&&z(d,v,s);t={h:n,c:d,j:v,i:s};D(t)}}}p<j.length?
+P(e,250):"function"===typeof a&&a()}for(var b=d||document.body,i=b.ownerDocument||document,b=[b.getElementsByTagName("pre"),b.getElementsByTagName("code"),b.getElementsByTagName("xmp")],j=[],m=0;m<b.length;++m)for(var l=0,n=b[m].length;l<n;++l)j.push(b[m][l]);var b=r,c=Date;c.now||(c={now:function(){return+new Date}});var p=0,t,q=/\blang(?:uage)?-([\w.]+)(?!\S)/,f=/\bprettyprint\b/,w=/\bprettyprinted\b/,y=/pre|xmp/i,u=/^code$/i,g=/^(?:pre|code|xmp)$/i,k={};e()}};typeof define==="function"&&define.amd&&
+define("google-code-prettify",[],function(){return X})})();return e}();R||P(Q,0)})();}()
diff --git a/src/main/java/com/gitblit/wicket/panels/ActivityPanel.html b/src/main/java/com/gitblit/wicket/panels/ActivityPanel.html
new file mode 100644
index 0000000..9dff0a7
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/panels/ActivityPanel.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"  
+      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"  
+      xml:lang="en"  
+      lang="en"> 
+
+<body>
+<wicket:panel>
+
+	<div wicket:id="activity" style="padding-bottom:10px;">
+		<div class="header"><i class="icon-refresh" style="vertical-align: middle;"></i> <span style="font-weight:bold;" wicket:id="title">[title]</span></div>
+		<table class="activity">
+			<tr wicket:id="commit">
+				<td class="hidden-phone date" style="width:60px; vertical-align: middle;text-align: right;padding-right:10px;" ><span wicket:id="time">[time of day]</span></td>
+				<td style="width:10em;text-align:left;vertical-align: middle;">
+					<span wicket:id="repository" class="activitySwatch">[repository link]</span>
+				</td>
+				<td class="hidden-phone hidden-tablet" style="width:30px;vertical-align: middle;"><span wicket:id="avatar" style="vertical-align: middle;"></span></td>
+				<td style="vertical-align: middle;padding-left:15px;">
+					<img class="hidden-phone hidden-tablet" wicket:id="commitIcon" style="vertical-align: top;"></img>
+					<span wicket:id="message">[shortlog commit link]</span><br/>
+					<span wicket:id="author">[author link]</span> <span class="hidden-phone"><wicket:message key="gb.authored"></wicket:message> <span wicket:id="commitid">[commit id]</span></span> on <span wicket:id="branch"></span>
+				</td>
+				<td class="hidden-phone" style="text-align:right;vertical-align: middle;">
+					<div wicket:id="commitRefs">[commit refs]</div>
+				</td>
+				<td class="hidden-phone rightAlign" style="width:7em;vertical-align: middle;">
+        			<span class="link">
+						<a wicket:id="diff" target="_blank"><wicket:message key="gb.diff"></wicket:message></a> | <a wicket:id="tree" target="_blank"><wicket:message key="gb.tree"></wicket:message></a>
+					</span>
+				</td>		
+			</tr>		
+		</table>	
+	</div>
+</wicket:panel>
+</body>
+</html>
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/panels/ActivityPanel.java b/src/main/java/com/gitblit/wicket/panels/ActivityPanel.java
new file mode 100644
index 0000000..b509f65
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/panels/ActivityPanel.java
@@ -0,0 +1,149 @@
+/*
+ * 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.wicket.panels;
+
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.wicket.markup.html.link.BookmarkablePageLink;
+import org.apache.wicket.markup.repeater.Item;
+import org.apache.wicket.markup.repeater.data.DataView;
+import org.apache.wicket.markup.repeater.data.ListDataProvider;
+import org.eclipse.jgit.lib.Repository;
+
+import com.gitblit.Constants;
+import com.gitblit.GitBlit;
+import com.gitblit.Keys;
+import com.gitblit.models.Activity;
+import com.gitblit.models.RepositoryCommit;
+import com.gitblit.utils.StringUtils;
+import com.gitblit.wicket.WicketUtils;
+import com.gitblit.wicket.pages.CommitDiffPage;
+import com.gitblit.wicket.pages.CommitPage;
+import com.gitblit.wicket.pages.GitSearchPage;
+import com.gitblit.wicket.pages.LogPage;
+import com.gitblit.wicket.pages.SummaryPage;
+import com.gitblit.wicket.pages.TreePage;
+
+/**
+ * Renders activity in day-blocks in reverse-chronological order.
+ * 
+ * @author James Moger
+ * 
+ */
+public class ActivityPanel extends BasePanel {
+
+	private static final long serialVersionUID = 1L;
+
+	public ActivityPanel(String wicketId, List<Activity> recentActivity) {
+		super(wicketId);
+
+		Collections.sort(recentActivity);
+		
+		final int shortHashLen = GitBlit.getInteger(Keys.web.shortCommitIdLength, 6);
+		DataView<Activity> activityView = new DataView<Activity>("activity",
+				new ListDataProvider<Activity>(recentActivity)) {
+			private static final long serialVersionUID = 1L;
+
+			public void populateItem(final Item<Activity> activityItem) {
+				final Activity entry = activityItem.getModelObject();
+				activityItem.add(WicketUtils.createDatestampLabel("title", entry.startDate, getTimeZone(), getTimeUtils()));
+
+				// display the commits in chronological order
+				DataView<RepositoryCommit> commits = new DataView<RepositoryCommit>("commit",
+						new ListDataProvider<RepositoryCommit>(entry.getCommits())) {
+					private static final long serialVersionUID = 1L;
+
+					public void populateItem(final Item<RepositoryCommit> commitItem) {
+						final RepositoryCommit commit = commitItem.getModelObject();
+
+						// commit time of day
+						commitItem.add(WicketUtils.createTimeLabel("time", commit.getCommitterIdent()
+								.getWhen(), getTimeZone(), getTimeUtils()));
+
+						// avatar
+						commitItem.add(new GravatarImage("avatar", commit.getAuthorIdent(), 40));
+
+						// merge icon
+						if (commit.getParentCount() > 1) {
+							commitItem.add(WicketUtils.newImage("commitIcon",
+									"commit_merge_16x16.png"));
+						} else {
+							commitItem.add(WicketUtils.newBlankImage("commitIcon").setVisible(false));
+						}
+
+						// author search link
+						String author = commit.getAuthorIdent().getName();
+						LinkPanel authorLink = new LinkPanel("author", "list", author,
+								GitSearchPage.class, WicketUtils.newSearchParameter(commit.repository,
+										commit.getName(), author, Constants.SearchType.AUTHOR), true);
+						setPersonSearchTooltip(authorLink, author, Constants.SearchType.AUTHOR);
+						commitItem.add(authorLink);
+
+						// repository
+						String repoName = StringUtils.stripDotGit(commit.repository);
+						LinkPanel repositoryLink = new LinkPanel("repository", null,
+								repoName, SummaryPage.class,
+								WicketUtils.newRepositoryParameter(commit.repository), true);
+						WicketUtils.setCssBackground(repositoryLink, repoName);
+						commitItem.add(repositoryLink);
+
+						// repository branch
+						LinkPanel branchLink = new LinkPanel("branch", "list", Repository.shortenRefName(commit.branch),
+								LogPage.class, WicketUtils.newObjectParameter(commit.repository,
+										commit.branch), true);
+						WicketUtils.setCssStyle(branchLink, "color: #008000;");
+						commitItem.add(branchLink);
+
+						LinkPanel commitid = new LinkPanel("commitid", "list subject",
+								commit.getName().substring(0,  shortHashLen), CommitPage.class,
+								WicketUtils.newObjectParameter(commit.repository, commit.getName()), true);
+						commitItem.add(commitid);
+
+						// message/commit link
+						String shortMessage = commit.getShortMessage();
+						String trimmedMessage = shortMessage;
+						if (commit.getRefs() != null && commit.getRefs().size() > 0) {
+							trimmedMessage = StringUtils.trimString(shortMessage, Constants.LEN_SHORTLOG_REFS);
+						} else {
+							trimmedMessage = StringUtils.trimString(shortMessage, Constants.LEN_SHORTLOG);
+						}
+						LinkPanel shortlog = new LinkPanel("message", "list subject",
+								trimmedMessage, CommitPage.class, WicketUtils.newObjectParameter(
+										commit.repository, commit.getName()), true);
+						if (!shortMessage.equals(trimmedMessage)) {
+							WicketUtils.setHtmlTooltip(shortlog, shortMessage);
+						}
+						commitItem.add(shortlog);
+
+						// refs
+						commitItem.add(new RefsPanel("commitRefs", commit.repository, commit
+								.getRefs()));
+
+						// diff, tree links
+						commitItem.add(new BookmarkablePageLink<Void>("diff", CommitDiffPage.class,
+								WicketUtils.newObjectParameter(commit.repository, commit.getName()))
+								.setEnabled(commit.getParentCount() > 0));
+						commitItem.add(new BookmarkablePageLink<Void>("tree", TreePage.class,
+								WicketUtils.newObjectParameter(commit.repository, commit.getName())));						
+					}
+				};
+				activityItem.add(commits);
+			}
+		};
+		add(activityView);
+	}
+}
diff --git a/src/main/java/com/gitblit/wicket/panels/BasePanel.java b/src/main/java/com/gitblit/wicket/panels/BasePanel.java
new file mode 100644
index 0000000..e4aeeb0
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/panels/BasePanel.java
@@ -0,0 +1,105 @@
+/*
+ * 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.wicket.panels;
+
+import java.util.ResourceBundle;
+import java.util.TimeZone;
+
+import org.apache.wicket.AttributeModifier;
+import org.apache.wicket.Component;
+import org.apache.wicket.markup.html.panel.Panel;
+import org.apache.wicket.model.Model;
+
+import com.gitblit.Constants;
+import com.gitblit.GitBlit;
+import com.gitblit.Keys;
+import com.gitblit.utils.TimeUtils;
+import com.gitblit.wicket.GitBlitWebSession;
+import com.gitblit.wicket.WicketUtils;
+
+public abstract class BasePanel extends Panel {
+
+	private static final long serialVersionUID = 1L;
+	
+	private transient TimeUtils timeUtils;
+
+	public BasePanel(String wicketId) {
+		super(wicketId);
+	}
+
+	protected TimeZone getTimeZone() {
+		return GitBlit.getBoolean(Keys.web.useClientTimezone, false) ? GitBlitWebSession.get()
+				.getTimezone() : GitBlit.getTimezone();
+	}
+	
+	protected TimeUtils getTimeUtils() {
+		if (timeUtils == null) {
+			ResourceBundle bundle;		
+			try {
+				bundle = ResourceBundle.getBundle("com.gitblit.wicket.GitBlitWebApp", GitBlitWebSession.get().getLocale());
+			} catch (Throwable t) {
+				bundle = ResourceBundle.getBundle("com.gitblit.wicket.GitBlitWebApp");
+			}
+			timeUtils = new TimeUtils(bundle, getTimeZone());
+		}
+		return timeUtils;
+	}
+	
+	protected void setPersonSearchTooltip(Component component, String value, Constants.SearchType searchType) {
+		if (searchType.equals(Constants.SearchType.AUTHOR)) {
+			WicketUtils.setHtmlTooltip(component, getString("gb.searchForAuthor") + " " + value);
+		} else if (searchType.equals(Constants.SearchType.COMMITTER)) {
+			WicketUtils.setHtmlTooltip(component, getString("gb.searchForCommitter") + " " + value);
+		}
+	}
+
+	public static class JavascriptEventConfirmation extends AttributeModifier {
+
+		private static final long serialVersionUID = 1L;
+
+		public JavascriptEventConfirmation(String event, String msg) {
+			super(event, true, new Model<String>(msg));
+		}
+
+		protected String newValue(final String currentValue, final String replacementValue) {
+			String prefix = "var conf = confirm('" + replacementValue + "'); "
+					+ "if (!conf) return false; ";
+			String result = prefix;
+			if (currentValue != null) {
+				result = prefix + currentValue;
+			}
+			return result;
+		}
+	}
+
+	public static class JavascriptTextPrompt extends AttributeModifier {
+
+		private static final long serialVersionUID = 1L;
+
+		private String initialValue = "";
+		
+		public JavascriptTextPrompt(String event, String msg, String value) {
+			super(event, true, new Model<String>(msg));
+			initialValue = value;
+		}
+
+		protected String newValue(final String currentValue, final String message) {
+			String result = "var userText = prompt('" + message + "','"
+					+ (initialValue == null ? "" : initialValue) + "'); " + "return userText; ";
+			return result;
+		}
+	}
+}
diff --git a/src/main/java/com/gitblit/wicket/panels/BranchesPanel.html b/src/main/java/com/gitblit/wicket/panels/BranchesPanel.html
new file mode 100644
index 0000000..e95b283
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/panels/BranchesPanel.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"  
+      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"  
+      xml:lang="en"  
+      lang="en"> 
+
+<body>
+<wicket:panel>
+
+	<!-- header -->
+	<div class="header"><i class="icon-random"></i> <b><span wicket:id="branches">[branches header]</span></b></div>	
+	
+	<table class="pretty">
+		<tbody>
+       		<tr wicket:id="branch">
+         		<td class="date"><span wicket:id="branchDate">[branch date]</span></td>
+         		<td><span wicket:id="branchName">[branch name]</span></td>
+         		<td class="hidden-phone hidden-tablet author"><span wicket:id="branchAuthor">[branch author]</span></td>
+         		<td class="hidden-phone"><span wicket:id="branchLog">[branch log]</span></td>
+         		<td class="hidden-phone rightAlign">
+         			<span wicket:id="branchLinks"></span>
+				</td>
+       		</tr>
+    	</tbody>
+	</table>	
+
+	<div wicket:id="allBranches">[all branches]</div>	
+
+	<!-- branch page links -->
+	<wicket:fragment wicket:id="branchPageLinks">
+		<span class="link">
+			<a wicket:id="log"><wicket:message key="gb.log"></wicket:message></a> | <a wicket:id="tree"><wicket:message key="gb.tree"></wicket:message></a> | <a wicket:id="metrics"><wicket:message key="gb.metrics"></wicket:message></a> | <a wicket:id="syndication"><wicket:message key="gb.feed"></wicket:message></a>
+		</span>
+	</wicket:fragment>
+
+	<!-- branch page admin links -->
+	<wicket:fragment wicket:id="branchPageAdminLinks">
+		<span class="link">
+			<a wicket:id="log"><wicket:message key="gb.log"></wicket:message></a> | <a wicket:id="tree"><wicket:message key="gb.tree"></wicket:message></a> | <a wicket:id="metrics"><wicket:message key="gb.metrics"></wicket:message></a> | <a wicket:id="syndication"><wicket:message key="gb.feed"></wicket:message></a> | <a wicket:id="deleteBranch"><wicket:message key="gb.delete"></wicket:message></a>
+		</span>
+	</wicket:fragment>
+
+	<!-- branch panel links -->
+	<wicket:fragment wicket:id="branchPanelLinks">
+		<span class="link">
+			<a wicket:id="log"><wicket:message key="gb.log"></wicket:message></a> | <a wicket:id="tree"><wicket:message key="gb.tree"></wicket:message></a>
+		</span>
+	</wicket:fragment>
+			
+</wicket:panel>
+</body>
+</html>
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/panels/BranchesPanel.java b/src/main/java/com/gitblit/wicket/panels/BranchesPanel.java
similarity index 100%
rename from src/com/gitblit/wicket/panels/BranchesPanel.java
rename to src/main/java/com/gitblit/wicket/panels/BranchesPanel.java
diff --git a/src/com/gitblit/wicket/panels/BulletListPanel.html b/src/main/java/com/gitblit/wicket/panels/BulletListPanel.html
similarity index 100%
rename from src/com/gitblit/wicket/panels/BulletListPanel.html
rename to src/main/java/com/gitblit/wicket/panels/BulletListPanel.html
diff --git a/src/com/gitblit/wicket/panels/BulletListPanel.java b/src/main/java/com/gitblit/wicket/panels/BulletListPanel.java
similarity index 100%
rename from src/com/gitblit/wicket/panels/BulletListPanel.java
rename to src/main/java/com/gitblit/wicket/panels/BulletListPanel.java
diff --git a/src/com/gitblit/wicket/panels/CommitHeaderPanel.html b/src/main/java/com/gitblit/wicket/panels/CommitHeaderPanel.html
similarity index 100%
rename from src/com/gitblit/wicket/panels/CommitHeaderPanel.html
rename to src/main/java/com/gitblit/wicket/panels/CommitHeaderPanel.html
diff --git a/src/main/java/com/gitblit/wicket/panels/CommitHeaderPanel.java b/src/main/java/com/gitblit/wicket/panels/CommitHeaderPanel.java
new file mode 100644
index 0000000..eb75750
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/panels/CommitHeaderPanel.java
@@ -0,0 +1,49 @@
+/*
+ * 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.wicket.panels;
+
+import org.apache.wicket.markup.html.basic.Label;
+import org.eclipse.jgit.revwalk.RevCommit;
+
+import com.gitblit.Constants;
+import com.gitblit.utils.StringUtils;
+import com.gitblit.wicket.WicketUtils;
+import com.gitblit.wicket.pages.CommitPage;
+
+public class CommitHeaderPanel extends BasePanel {
+
+	private static final long serialVersionUID = 1L;
+
+	public CommitHeaderPanel(String id, String title) {
+		super(id);
+		add(new Label("shortmessage", title));
+		add(new Label("commitid"));
+		add(new Label("author"));
+		add(new Label("date"));
+		add(new Label("authorAvatar"));
+	}
+
+	public CommitHeaderPanel(String id, String repositoryName, RevCommit c) {
+		super(id);
+		add(new LinkPanel("shortmessage", "title", StringUtils.trimString(c.getShortMessage(),
+				Constants.LEN_SHORTLOG), CommitPage.class,
+				WicketUtils.newObjectParameter(repositoryName, c.getName())));
+		add(new Label("commitid", c.getName()));
+		add(new Label("author", c.getAuthorIdent().getName()));
+		add(WicketUtils.createDateLabel("date", c.getAuthorIdent().getWhen(), getTimeZone(), getTimeUtils()));
+		add(new GravatarImage("authorAvatar", c.getAuthorIdent()));
+	}
+}
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/panels/CommitLegendPanel.html b/src/main/java/com/gitblit/wicket/panels/CommitLegendPanel.html
similarity index 100%
rename from src/com/gitblit/wicket/panels/CommitLegendPanel.html
rename to src/main/java/com/gitblit/wicket/panels/CommitLegendPanel.html
diff --git a/src/com/gitblit/wicket/panels/CommitLegendPanel.java b/src/main/java/com/gitblit/wicket/panels/CommitLegendPanel.java
similarity index 100%
rename from src/com/gitblit/wicket/panels/CommitLegendPanel.java
rename to src/main/java/com/gitblit/wicket/panels/CommitLegendPanel.java
diff --git a/src/com/gitblit/wicket/panels/CompressedDownloadsPanel.html b/src/main/java/com/gitblit/wicket/panels/CompressedDownloadsPanel.html
similarity index 100%
rename from src/com/gitblit/wicket/panels/CompressedDownloadsPanel.html
rename to src/main/java/com/gitblit/wicket/panels/CompressedDownloadsPanel.html
diff --git a/src/main/java/com/gitblit/wicket/panels/CompressedDownloadsPanel.java b/src/main/java/com/gitblit/wicket/panels/CompressedDownloadsPanel.java
new file mode 100644
index 0000000..122ae55
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/panels/CompressedDownloadsPanel.java
@@ -0,0 +1,78 @@
+/*
+ * 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.wicket.panels;
+
+import java.util.List;
+
+import org.apache.wicket.Component;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.panel.Panel;
+import org.apache.wicket.markup.repeater.Item;
+import org.apache.wicket.markup.repeater.data.DataView;
+import org.apache.wicket.markup.repeater.data.ListDataProvider;
+
+import com.gitblit.DownloadZipServlet;
+import com.gitblit.DownloadZipServlet.Format;
+import com.gitblit.GitBlit;
+import com.gitblit.Keys;
+
+public class CompressedDownloadsPanel extends Panel {
+
+	private static final long serialVersionUID = 1L;
+
+	public CompressedDownloadsPanel(String id, final String baseUrl, final String repositoryName, final String objectId, final String path) {
+		super(id);
+		
+		List<String> types = GitBlit.getStrings(Keys.web.compressedDownloads);
+		if (types.isEmpty()) {
+			types.add(Format.zip.name());
+			types.add(Format.gz.name());
+		}
+		
+		ListDataProvider<String> refsDp = new ListDataProvider<String>(types);
+		DataView<String> refsView = new DataView<String>("compressedLinks", refsDp) {
+			private static final long serialVersionUID = 1L;
+			int counter;
+
+			@Override
+			protected void onBeforeRender() {
+				super.onBeforeRender();
+				counter = 0;
+			}
+			
+			@Override
+			public void populateItem(final Item<String> item) {
+				String compressionType = item.getModelObject();
+				Format format = Format.fromName(compressionType);
+				
+				String href = DownloadZipServlet.asLink(baseUrl, repositoryName,
+						objectId, path, format);
+				LinkPanel c = new LinkPanel("compressedLink", null, format.name(), href);
+				c.setNoFollow();
+				item.add(c);
+				Label lb = new Label("linkSep", "|");
+				lb.setVisible(counter > 0);
+				lb.setRenderBodyOnly(true);
+				item.add(lb.setEscapeModelStrings(false));
+				item.setRenderBodyOnly(true);
+				counter++;
+			}
+		};
+		add(refsView);
+		
+		setVisible(GitBlit.getBoolean(Keys.web.allowZipDownloads, true));
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/panels/DigestsPanel.html b/src/main/java/com/gitblit/wicket/panels/DigestsPanel.html
new file mode 100644
index 0000000..e21cf24
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/panels/DigestsPanel.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"  
+      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"  
+      xml:lang="en"  
+      lang="en"> 
+
+<body>
+<wicket:panel>
+<div wicket:id="change" class="reflog">
+	<table style="padding: 3px 0px;">
+	<tr>
+		<td class="icon hidden-phone"><i wicket:id="changeIcon"></i></td>
+		<td class="header">
+			<div>
+				<span class="when" wicket:id="whenChanged"></span>
+			</div>
+			<div style="font-weight:bold;"><span wicket:id="whoChanged">[who changed]</span> <span wicket:id="whatChanged"></span> <span wicket:id="refChanged"></span> <span wicket:id="repoPreposition"></span> <span wicket:id="repoChanged"></span> <span wicket:id="byAuthors"></span></div>
+		</td>
+	</tr>
+	<tr>
+		<td class="hidden-phone"></td>
+		<td class="commits">
+			<div>
+				<table>
+					<tr class="commit" wicket:id="commit">
+						<td class="hidden-phone hidden-tablet"><span wicket:id="commitAuthor"></span></td>
+						<td><span wicket:id="hashLink">[hash link]</span></td>
+						<td><img wicket:id="commitIcon" /></td>
+						<td>   							
+							<span wicket:id="commitShortMessage">[commit short message]</span>
+						</td>
+					</tr>
+				</table>
+				<span class="link" wicket:id="compareLink"></span>
+			</div>
+		</td>
+	</tr>	
+	</table>
+</div>
+</wicket:panel>
+</body>
+</html>
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/panels/DigestsPanel.java b/src/main/java/com/gitblit/wicket/panels/DigestsPanel.java
new file mode 100644
index 0000000..0f380a4
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/panels/DigestsPanel.java
@@ -0,0 +1,273 @@
+/*
+ * Copyright 2013 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit.wicket.panels;
+
+import java.text.DateFormat;
+import java.text.MessageFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+import java.util.TimeZone;
+
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.repeater.Item;
+import org.apache.wicket.markup.repeater.data.DataView;
+import org.apache.wicket.markup.repeater.data.ListDataProvider;
+
+import com.gitblit.Constants;
+import com.gitblit.GitBlit;
+import com.gitblit.Keys;
+import com.gitblit.models.DailyLogEntry;
+import com.gitblit.models.RepositoryCommit;
+import com.gitblit.utils.StringUtils;
+import com.gitblit.utils.TimeUtils;
+import com.gitblit.wicket.WicketUtils;
+import com.gitblit.wicket.pages.CommitPage;
+import com.gitblit.wicket.pages.ComparePage;
+import com.gitblit.wicket.pages.SummaryPage;
+import com.gitblit.wicket.pages.TagPage;
+import com.gitblit.wicket.pages.TreePage;
+import com.gitblit.wicket.pages.UserPage;
+
+public class DigestsPanel extends BasePanel {
+
+	private static final long serialVersionUID = 1L;
+
+	private final boolean hasChanges;
+	
+	private boolean hasMore;
+
+	public DigestsPanel(String wicketId, List<DailyLogEntry> digests) {
+		super(wicketId);
+		hasChanges = digests.size() > 0;
+
+		final int hashLen = GitBlit.getInteger(Keys.web.shortCommitIdLength, 6);
+
+		String dateFormat = GitBlit.getString(Keys.web.datestampLongFormat, "EEEE, MMMM d, yyyy");
+		final TimeZone timezone = getTimeZone();
+		final DateFormat df = new SimpleDateFormat(dateFormat);
+		df.setTimeZone(timezone);
+		final Calendar cal = Calendar.getInstance(timezone);
+		
+		ListDataProvider<DailyLogEntry> dp = new ListDataProvider<DailyLogEntry>(digests);
+		DataView<DailyLogEntry> pushView = new DataView<DailyLogEntry>("change", dp) {
+			private static final long serialVersionUID = 1L;
+
+			public void populateItem(final Item<DailyLogEntry> logItem) {
+				final DailyLogEntry change = logItem.getModelObject();
+				String fullRefName = change.getChangedRefs().get(0);
+				String shortRefName = fullRefName;
+				boolean isTag = false;
+				if (shortRefName.startsWith(Constants.R_HEADS)) {
+					shortRefName = shortRefName.substring(Constants.R_HEADS.length());
+				} else if (shortRefName.startsWith(Constants.R_TAGS)) {
+					shortRefName = shortRefName.substring(Constants.R_TAGS.length());
+					isTag = true;
+				}
+				
+				String fuzzydate;
+				TimeUtils tu = getTimeUtils();
+				Date pushDate = change.date;
+				if (TimeUtils.isToday(pushDate, timezone)) {
+					fuzzydate = tu.today();
+				} else if (TimeUtils.isYesterday(pushDate, timezone)) {
+					fuzzydate = tu.yesterday();
+				} else {
+					// calculate a fuzzy time ago date
+                	cal.setTime(pushDate);
+                	cal.set(Calendar.HOUR_OF_DAY, 0);
+                	cal.set(Calendar.MINUTE, 0);
+                	cal.set(Calendar.SECOND, 0);
+                	cal.set(Calendar.MILLISECOND, 0);
+                	pushDate = cal.getTime();
+					fuzzydate = getTimeUtils().timeAgo(pushDate);
+				}
+				logItem.add(new Label("whenChanged", fuzzydate + ", " + df.format(pushDate)));
+
+				Label changeIcon = new Label("changeIcon");
+				// use the repository hash color to differentiate the icon.
+                String color = StringUtils.getColor(StringUtils.stripDotGit(change.repository));
+                WicketUtils.setCssStyle(changeIcon, "color: " + color);
+
+				if (isTag) {
+					WicketUtils.setCssClass(changeIcon, "iconic-tag");
+				} else {
+					WicketUtils.setCssClass(changeIcon, "iconic-loop");
+				}
+				logItem.add(changeIcon);
+
+                if (!isTag) {
+                	logItem.add(new Label("whoChanged").setVisible(false));
+                } else {
+                	if (change.user.username.equals(change.user.emailAddress) && change.user.emailAddress.indexOf('@') > -1) {
+                		// username is an email address can not link - 1.2.1 push log bug
+                		logItem.add(new Label("whoChanged", change.user.getDisplayName()));
+                	} else {
+                		// link to user account page
+                		logItem.add(new LinkPanel("whoChanged", null, change.user.getDisplayName(),
+                				UserPage.class, WicketUtils.newUsernameParameter(change.user.username)));
+                	}
+                }
+				
+				String preposition = "gb.of";
+				boolean isDelete = false;
+				String what;
+				String by = null;
+				switch(change.getChangeType(fullRefName)) {
+				case CREATE:
+					if (isTag) {
+						// new tag
+						what = getString("gb.createdNewTag");
+						preposition = "gb.in";
+					} else {
+						// new branch
+						what = getString("gb.createdNewBranch");
+						preposition = "gb.in";
+					}
+					break;
+				case DELETE:
+					isDelete = true;
+					if (isTag) {
+						what = getString("gb.deletedTag");
+					} else {
+						what = getString("gb.deletedBranch");
+					}
+					preposition = "gb.from";
+					break;
+				default:
+					what = MessageFormat.format(change.getCommitCount() > 1 ? getString("gb.commitsTo") : getString("gb.oneCommitTo"), change.getCommitCount());
+					
+					if (change.getAuthorCount() == 1) {
+						by = MessageFormat.format(getString("gb.byOneAuthor"), change.getAuthorIdent().getName());
+					} else {
+						by = MessageFormat.format(getString("gb.byNAuthors"), change.getAuthorCount());	
+					}
+					break;
+				}
+				logItem.add(new Label("whatChanged", what));
+				logItem.add(new Label("byAuthors", by).setVisible(!StringUtils.isEmpty(by)));
+				
+				if (isDelete) {
+					// can't link to deleted ref
+					logItem.add(new Label("refChanged", shortRefName));
+				} else if (isTag) {
+					// link to tag
+					logItem.add(new LinkPanel("refChanged", null, shortRefName,
+							TagPage.class, WicketUtils.newObjectParameter(change.repository, fullRefName)));
+				} else {
+					// link to tree
+					logItem.add(new LinkPanel("refChanged", null, shortRefName,
+						TreePage.class, WicketUtils.newObjectParameter(change.repository, fullRefName)));
+				}
+				
+				// to/from/etc
+				logItem.add(new Label("repoPreposition", getString(preposition)));
+				String repoName = StringUtils.stripDotGit(change.repository);
+				logItem.add(new LinkPanel("repoChanged", null, repoName,
+						SummaryPage.class, WicketUtils.newRepositoryParameter(change.repository)));
+				
+				int maxCommitCount = 5;
+				List<RepositoryCommit> commits = change.getCommits();
+				if (commits.size() > maxCommitCount) {
+					commits = new ArrayList<RepositoryCommit>(commits.subList(0,  maxCommitCount));					
+				}
+				
+				// compare link
+				String compareLinkText = null;
+				if ((change.getCommitCount() <= maxCommitCount) && (change.getCommitCount() > 1)) {
+					compareLinkText = MessageFormat.format(getString("gb.viewComparison"), commits.size());
+				} else if (change.getCommitCount() > maxCommitCount) {
+					int diff = change.getCommitCount() - maxCommitCount;
+					compareLinkText = MessageFormat.format(diff > 1 ? getString("gb.nMoreCommits") : getString("gb.oneMoreCommit"), diff);
+				}
+				if (StringUtils.isEmpty(compareLinkText)) {
+					logItem.add(new Label("compareLink").setVisible(false));
+				} else {
+					String endRangeId = change.getNewId(fullRefName);
+					String startRangeId = change.getOldId(fullRefName);
+					logItem.add(new LinkPanel("compareLink", null, compareLinkText, ComparePage.class, WicketUtils.newRangeParameter(change.repository, startRangeId, endRangeId)));
+				}
+				
+				final boolean showSwatch = GitBlit.getBoolean(Keys.web.repositoryListSwatches, true);
+				
+				ListDataProvider<RepositoryCommit> cdp = new ListDataProvider<RepositoryCommit>(commits);
+				DataView<RepositoryCommit> commitsView = new DataView<RepositoryCommit>("commit", cdp) {
+					private static final long serialVersionUID = 1L;
+
+					public void populateItem(final Item<RepositoryCommit> commitItem) {
+						final RepositoryCommit commit = commitItem.getModelObject();
+
+						// author gravatar
+						commitItem.add(new GravatarImage("commitAuthor", commit.getAuthorIdent().getName(),
+								commit.getAuthorIdent().getEmailAddress(), null, 16, false, false));
+						
+						// merge icon
+						if (commit.getParentCount() > 1) {
+							commitItem.add(WicketUtils.newImage("commitIcon", "commit_merge_16x16.png"));
+						} else {
+							commitItem.add(WicketUtils.newBlankImage("commitIcon"));
+						}
+
+						// short message
+						String shortMessage = commit.getShortMessage();
+						String trimmedMessage = shortMessage;
+						if (commit.getRefs() != null && commit.getRefs().size() > 0) {
+							trimmedMessage = StringUtils.trimString(shortMessage, Constants.LEN_SHORTLOG_REFS);
+						} else {
+							trimmedMessage = StringUtils.trimString(shortMessage, Constants.LEN_SHORTLOG);
+						}
+						LinkPanel shortlog = new LinkPanel("commitShortMessage", "list",
+								trimmedMessage, CommitPage.class, WicketUtils.newObjectParameter(
+										change.repository, commit.getName()));
+						if (!shortMessage.equals(trimmedMessage)) {
+							WicketUtils.setHtmlTooltip(shortlog, shortMessage);
+						}
+						commitItem.add(shortlog);
+
+						// commit hash link
+						LinkPanel commitHash = new LinkPanel("hashLink", null, commit.getName().substring(0, hashLen),
+								CommitPage.class, WicketUtils.newObjectParameter(
+										change.repository, commit.getName()));
+						WicketUtils.setCssClass(commitHash, "shortsha1");
+						WicketUtils.setHtmlTooltip(commitHash, commit.getName());
+						commitItem.add(commitHash);
+						
+						if (showSwatch) {
+							// set repository color
+							String color = StringUtils.getColor(StringUtils.stripDotGit(change.repository));
+							WicketUtils.setCssStyle(commitItem, MessageFormat.format("border-left: 2px solid {0};", color));
+						}
+					}
+				};
+
+				logItem.add(commitsView);
+			}
+		};
+		
+		add(pushView);
+	}
+
+	public boolean hasMore() {
+		return hasMore;
+	}
+	
+	public boolean hideIfEmpty() {
+		setVisible(hasChanges);
+		return hasChanges;
+	}
+}
diff --git a/src/com/gitblit/wicket/panels/DropDownMenu.html b/src/main/java/com/gitblit/wicket/panels/DropDownMenu.html
similarity index 100%
rename from src/com/gitblit/wicket/panels/DropDownMenu.html
rename to src/main/java/com/gitblit/wicket/panels/DropDownMenu.html
diff --git a/src/com/gitblit/wicket/panels/DropDownMenu.java b/src/main/java/com/gitblit/wicket/panels/DropDownMenu.java
similarity index 100%
rename from src/com/gitblit/wicket/panels/DropDownMenu.java
rename to src/main/java/com/gitblit/wicket/panels/DropDownMenu.java
diff --git a/src/com/gitblit/wicket/panels/FederationProposalsPanel.html b/src/main/java/com/gitblit/wicket/panels/FederationProposalsPanel.html
similarity index 100%
rename from src/com/gitblit/wicket/panels/FederationProposalsPanel.html
rename to src/main/java/com/gitblit/wicket/panels/FederationProposalsPanel.html
diff --git a/src/com/gitblit/wicket/panels/FederationProposalsPanel.java b/src/main/java/com/gitblit/wicket/panels/FederationProposalsPanel.java
similarity index 100%
rename from src/com/gitblit/wicket/panels/FederationProposalsPanel.java
rename to src/main/java/com/gitblit/wicket/panels/FederationProposalsPanel.java
diff --git a/src/com/gitblit/wicket/panels/FederationRegistrationsPanel.html b/src/main/java/com/gitblit/wicket/panels/FederationRegistrationsPanel.html
similarity index 100%
rename from src/com/gitblit/wicket/panels/FederationRegistrationsPanel.html
rename to src/main/java/com/gitblit/wicket/panels/FederationRegistrationsPanel.html
diff --git a/src/com/gitblit/wicket/panels/FederationRegistrationsPanel.java b/src/main/java/com/gitblit/wicket/panels/FederationRegistrationsPanel.java
similarity index 100%
rename from src/com/gitblit/wicket/panels/FederationRegistrationsPanel.java
rename to src/main/java/com/gitblit/wicket/panels/FederationRegistrationsPanel.java
diff --git a/src/com/gitblit/wicket/panels/FederationTokensPanel.html b/src/main/java/com/gitblit/wicket/panels/FederationTokensPanel.html
similarity index 100%
rename from src/com/gitblit/wicket/panels/FederationTokensPanel.html
rename to src/main/java/com/gitblit/wicket/panels/FederationTokensPanel.html
diff --git a/src/com/gitblit/wicket/panels/FederationTokensPanel.java b/src/main/java/com/gitblit/wicket/panels/FederationTokensPanel.java
similarity index 100%
rename from src/com/gitblit/wicket/panels/FederationTokensPanel.java
rename to src/main/java/com/gitblit/wicket/panels/FederationTokensPanel.java
diff --git a/src/main/java/com/gitblit/wicket/panels/FilterableProjectList.html b/src/main/java/com/gitblit/wicket/panels/FilterableProjectList.html
new file mode 100644
index 0000000..4c3aecd
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/panels/FilterableProjectList.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"  
+      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"  
+      xml:lang="en"  
+      lang="en"> 
+
+<wicket:panel>
+	<div wicket:id="listComponent">[component]</div>
+</wicket:panel>
+</html>
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/panels/FilterableProjectList.java b/src/main/java/com/gitblit/wicket/panels/FilterableProjectList.java
new file mode 100644
index 0000000..a5b7413
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/panels/FilterableProjectList.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright 2013 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit.wicket.panels;
+
+import java.io.Serializable;
+import java.text.DateFormat;
+import java.text.MessageFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.wicket.behavior.HeaderContributor;
+import org.apache.wicket.markup.html.basic.Label;
+
+import com.gitblit.GitBlit;
+import com.gitblit.Keys;
+import com.gitblit.models.ProjectModel;
+import com.gitblit.utils.StringUtils;
+import com.gitblit.wicket.WicketUtils;
+import com.gitblit.wicket.freemarker.FreemarkerPanel;
+import com.gitblit.wicket.ng.NgController;
+
+/**
+ * A client-side filterable rich project list which uses Freemarker, Wicket,
+ * and AngularJS. 
+ * 
+ * @author James Moger
+ *
+ */
+public class FilterableProjectList extends BasePanel {
+
+	private static final long serialVersionUID = 1L;
+
+	private final List<ProjectModel> projects;
+	
+	private String title;
+	
+	private String iconClass;
+	
+	public FilterableProjectList(String id, List<ProjectModel> projects) {
+		super(id);
+		this.projects = projects;
+	}
+	
+	public void setTitle(String title, String iconClass) {
+		this.title = title;
+		this.iconClass = iconClass;
+	}
+	
+	@Override
+	protected void onInitialize() {
+		super.onInitialize();
+
+		String id = getId();
+		String ngCtrl = id + "Ctrl";
+		String ngList = id + "List";
+		
+		Map<String, Object> values = new HashMap<String, Object>();
+		values.put("ngCtrl",  ngCtrl);
+		values.put("ngList",  ngList);
+		
+		// use Freemarker to setup an AngularJS/Wicket html snippet
+		FreemarkerPanel panel = new FreemarkerPanel("listComponent", "FilterableProjectList.fm", values);
+		panel.setParseGeneratedMarkup(true);
+		panel.setRenderBodyOnly(true);
+		add(panel);
+		
+		// add the Wicket controls that are referenced in the snippet 
+		String listTitle = StringUtils.isEmpty(title) ? getString("gb.projects") : title;
+		panel.add(new Label(ngList + "Title", MessageFormat.format("{0} ({1})", listTitle, projects.size())));
+		if (StringUtils.isEmpty(iconClass)) {
+			panel.add(new Label(ngList + "Icon").setVisible(false));
+		} else {
+			Label icon = new Label(ngList + "Icon");
+			WicketUtils.setCssClass(icon, iconClass);
+			panel.add(icon);
+		}
+		
+		String format = GitBlit.getString(Keys.web.datestampShortFormat, "MM/dd/yy");
+		final DateFormat df = new SimpleDateFormat(format);
+		df.setTimeZone(getTimeZone());
+		Collections.sort(projects, new Comparator<ProjectModel>() {
+			@Override
+			public int compare(ProjectModel o1, ProjectModel o2) {
+				return o2.lastChange.compareTo(o1.lastChange);
+			}
+		});
+
+		List<ProjectListItem> list = new ArrayList<ProjectListItem>();
+		for (ProjectModel proj : projects) {
+			if (proj.isUserProject() || proj.repositories.isEmpty()) {
+				// exclude user projects from list
+				continue;
+			}
+			ProjectListItem item = new ProjectListItem();
+			item.p = proj.name;
+			item.n = StringUtils.isEmpty(proj.title) ? proj.name : proj.title;
+			item.i = proj.description;
+			item.t = getTimeUtils().timeAgo(proj.lastChange);
+			item.d = df.format(proj.lastChange);
+			item.c = proj.repositories.size();
+			list.add(item);
+		}
+		
+		// inject an AngularJS controller with static data
+		NgController ctrl = new NgController(ngCtrl);
+		ctrl.addVariable(ngList, list);
+		add(new HeaderContributor(ctrl));
+	}
+
+	protected class ProjectListItem implements Serializable {
+
+		private static final long serialVersionUID = 1L;
+		
+		String p; // path
+		String n; // name
+		String t; // time ago
+		String d; // last updated
+		String i; // information/description
+		long c;   // repository count
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/panels/FilterableRepositoryList.html b/src/main/java/com/gitblit/wicket/panels/FilterableRepositoryList.html
new file mode 100644
index 0000000..4c3aecd
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/panels/FilterableRepositoryList.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"  
+      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"  
+      xml:lang="en"  
+      lang="en"> 
+
+<wicket:panel>
+	<div wicket:id="listComponent">[component]</div>
+</wicket:panel>
+</html>
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/panels/FilterableRepositoryList.java b/src/main/java/com/gitblit/wicket/panels/FilterableRepositoryList.java
new file mode 100644
index 0000000..6c43b78
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/panels/FilterableRepositoryList.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright 2013 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit.wicket.panels;
+
+import java.io.Serializable;
+import java.text.DateFormat;
+import java.text.MessageFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.wicket.behavior.HeaderContributor;
+import org.apache.wicket.markup.html.basic.Label;
+
+import com.gitblit.GitBlit;
+import com.gitblit.Keys;
+import com.gitblit.models.RepositoryModel;
+import com.gitblit.utils.StringUtils;
+import com.gitblit.wicket.WicketUtils;
+import com.gitblit.wicket.freemarker.FreemarkerPanel;
+import com.gitblit.wicket.ng.NgController;
+import com.gitblit.wicket.pages.EditRepositoryPage;
+
+/**
+ * A client-side filterable rich repository list which uses Freemarker, Wicket,
+ * and AngularJS. 
+ * 
+ * @author James Moger
+ *
+ */
+public class FilterableRepositoryList extends BasePanel {
+
+	private static final long serialVersionUID = 1L;
+
+	private final List<RepositoryModel> repositories;
+	
+	private String title;
+	
+	private String iconClass;
+	
+	private boolean allowCreate;
+	
+	public FilterableRepositoryList(String id, List<RepositoryModel> repositories) {
+		super(id);
+		this.repositories = repositories;
+	}
+	
+	public void setTitle(String title, String iconClass) {
+		this.title = title;
+		this.iconClass = iconClass;
+	}
+	
+	public void setAllowCreate(boolean value) {
+		this.allowCreate = value;
+	}
+
+	@Override
+	protected void onInitialize() {
+		super.onInitialize();
+
+		String id = getId();
+		String ngCtrl = id + "Ctrl";
+		String ngList = id + "List";
+		
+		Map<String, Object> values = new HashMap<String, Object>();
+		values.put("ngCtrl",  ngCtrl);
+		values.put("ngList",  ngList);
+		
+		// use Freemarker to setup an AngularJS/Wicket html snippet
+		FreemarkerPanel panel = new FreemarkerPanel("listComponent", "FilterableRepositoryList.fm", values);
+		panel.setParseGeneratedMarkup(true);
+		panel.setRenderBodyOnly(true);
+		add(panel);
+		
+		// add the Wicket controls that are referenced in the snippet 
+		String listTitle = StringUtils.isEmpty(title) ? getString("gb.repositories") : title;
+		panel.add(new Label(ngList + "Title", MessageFormat.format("{0} ({1})", listTitle, repositories.size())));
+		if (StringUtils.isEmpty(iconClass)) {
+			panel.add(new Label(ngList + "Icon").setVisible(false));
+		} else {
+			Label icon = new Label(ngList + "Icon");
+			WicketUtils.setCssClass(icon, iconClass);
+			panel.add(icon);
+		}
+		
+		if (allowCreate) {
+			panel.add(new LinkPanel(ngList + "Button", "btn btn-mini", getString("gb.newRepository"), EditRepositoryPage.class));
+		} else {
+			panel.add(new Label(ngList + "Button").setVisible(false));
+		}
+		
+		String format = GitBlit.getString(Keys.web.datestampShortFormat, "MM/dd/yy");
+		final DateFormat df = new SimpleDateFormat(format);
+		df.setTimeZone(getTimeZone());
+
+		// prepare the simplified repository models list
+		List<RepoListItem> list = new ArrayList<RepoListItem>();
+		for (RepositoryModel repo : repositories) {
+			String name = StringUtils.stripDotGit(repo.name); 
+			String path = "";
+			if (name.indexOf('/') > -1) {
+				path = name.substring(0, name.lastIndexOf('/') + 1);
+				name = name.substring(name.lastIndexOf('/') + 1);
+			}
+			
+			RepoListItem item = new RepoListItem();
+			item.n = name;
+			item.p = path;
+			item.r = repo.name;
+			item.i = repo.description;
+			item.s = GitBlit.self().getStarCount(repo);
+			item.t = getTimeUtils().timeAgo(repo.lastChange);
+			item.d = df.format(repo.lastChange);
+			item.c = StringUtils.getColor(StringUtils.stripDotGit(repo.name));
+			item.wc = repo.isBare ? 0 : 1;
+			list.add(item);
+		}
+		
+		// inject an AngularJS controller with static data
+		NgController ctrl = new NgController(ngCtrl);
+		ctrl.addVariable(ngList, list);
+		add(new HeaderContributor(ctrl));
+	}
+	
+	protected class RepoListItem implements Serializable {
+
+		private static final long serialVersionUID = 1L;
+		
+		String r; // repository
+		String n; // name
+		String p; // project/path
+		String t; // time ago
+		String d; // last updated
+		String i; // information/description
+		long s;   // stars
+		String c; // html color
+		int wc;   // working copy: 1 = true, 0 = false
+	}
+}
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/panels/GravatarImage.html b/src/main/java/com/gitblit/wicket/panels/GravatarImage.html
similarity index 100%
rename from src/com/gitblit/wicket/panels/GravatarImage.html
rename to src/main/java/com/gitblit/wicket/panels/GravatarImage.html
diff --git a/src/main/java/com/gitblit/wicket/panels/GravatarImage.java b/src/main/java/com/gitblit/wicket/panels/GravatarImage.java
new file mode 100644
index 0000000..da20e9b
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/panels/GravatarImage.java
@@ -0,0 +1,84 @@
+/*
+ * 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.wicket.panels;
+
+import java.text.MessageFormat;
+
+import org.apache.wicket.behavior.SimpleAttributeModifier;
+import org.apache.wicket.markup.html.link.BookmarkablePageLink;
+import org.apache.wicket.markup.html.link.Link;
+import org.apache.wicket.markup.html.panel.Panel;
+import org.eclipse.jgit.lib.PersonIdent;
+
+import com.gitblit.GitBlit;
+import com.gitblit.Keys;
+import com.gitblit.utils.ActivityUtils;
+import com.gitblit.utils.StringUtils;
+import com.gitblit.wicket.ExternalImage;
+import com.gitblit.wicket.WicketUtils;
+import com.gitblit.wicket.pages.GravatarProfilePage;
+
+/**
+ * Represents a Gravatar image and links to the Gravatar profile page.
+ * 
+ * @author James Moger
+ * 
+ */
+public class GravatarImage extends Panel {
+
+	private static final long serialVersionUID = 1L;
+
+	public GravatarImage(String id, PersonIdent person) {
+		this(id, person, 0);
+	}
+	
+	public GravatarImage(String id, PersonIdent person, int width) {
+		this(id, person, width, true);
+	}
+
+	public GravatarImage(String id, PersonIdent person, int width, boolean linked) {
+		this(id, person.getName(), person.getEmailAddress(), "gravatar", width, linked, true);
+	}
+	
+	public GravatarImage(String id, String username, String emailaddress, String cssClass, int width, boolean linked, boolean identicon) {
+		super(id);
+
+		String email = emailaddress == null ? username.toLowerCase() : emailaddress.toLowerCase();
+		String hash = StringUtils.getMD5(email);
+		Link<Void> link = new BookmarkablePageLink<Void>("link", GravatarProfilePage.class,
+				WicketUtils.newObjectParameter(hash));
+		link.add(new SimpleAttributeModifier("target", "_blank"));
+		String url;
+		if (identicon) {
+			url = ActivityUtils.getGravatarIdenticonUrl(email, width);
+		} else {
+			url = ActivityUtils.getGravatarThumbnailUrl(email, width);
+		}
+		ExternalImage image = new ExternalImage("image", url);
+		if (cssClass != null) {
+			WicketUtils.setCssClass(image, cssClass);
+		}
+		link.add(image);
+		if (linked) {
+			WicketUtils.setHtmlTooltip(link,
+				MessageFormat.format("View Gravatar profile for {0}", username));
+		} else {
+			WicketUtils.setHtmlTooltip(link, username);
+		}
+		add(link.setEnabled(linked));
+		setVisible(GitBlit.getBoolean(Keys.web.allowGravatar, true));
+	}
+}
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/panels/HistoryPanel.html b/src/main/java/com/gitblit/wicket/panels/HistoryPanel.html
similarity index 100%
rename from src/com/gitblit/wicket/panels/HistoryPanel.html
rename to src/main/java/com/gitblit/wicket/panels/HistoryPanel.html
diff --git a/src/main/java/com/gitblit/wicket/panels/HistoryPanel.java b/src/main/java/com/gitblit/wicket/panels/HistoryPanel.java
new file mode 100644
index 0000000..5e03e01
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/panels/HistoryPanel.java
@@ -0,0 +1,341 @@
+/*
+ * 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.wicket.panels;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.link.BookmarkablePageLink;
+import org.apache.wicket.markup.html.panel.Fragment;
+import org.apache.wicket.markup.repeater.Item;
+import org.apache.wicket.markup.repeater.data.DataView;
+import org.apache.wicket.markup.repeater.data.ListDataProvider;
+import org.apache.wicket.model.StringResourceModel;
+import org.eclipse.jgit.diff.DiffEntry.ChangeType;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.treewalk.TreeWalk;
+import org.eclipse.jgit.treewalk.filter.PathFilterGroup;
+
+import com.gitblit.Constants;
+import com.gitblit.GitBlit;
+import com.gitblit.Keys;
+import com.gitblit.models.PathModel;
+import com.gitblit.models.PathModel.PathChangeModel;
+import com.gitblit.models.RefModel;
+import com.gitblit.models.SubmoduleModel;
+import com.gitblit.utils.JGitUtils;
+import com.gitblit.utils.StringUtils;
+import com.gitblit.wicket.WicketUtils;
+import com.gitblit.wicket.pages.BlobDiffPage;
+import com.gitblit.wicket.pages.BlobPage;
+import com.gitblit.wicket.pages.CommitDiffPage;
+import com.gitblit.wicket.pages.CommitPage;
+import com.gitblit.wicket.pages.GitSearchPage;
+import com.gitblit.wicket.pages.HistoryPage;
+import com.gitblit.wicket.pages.TreePage;
+
+public class HistoryPanel extends BasePanel {
+
+	private static final long serialVersionUID = 1L;
+
+	private boolean hasMore;
+
+	public HistoryPanel(String wicketId, final String repositoryName, final String objectId,
+			final String path, Repository r, int limit, int pageOffset, boolean showRemoteRefs) {
+		super(wicketId);
+		boolean pageResults = limit <= 0;
+		int itemsPerPage = GitBlit.getInteger(Keys.web.itemsPerPage, 50);
+		if (itemsPerPage <= 1) {
+			itemsPerPage = 50;
+		}
+
+		RevCommit commit = JGitUtils.getCommit(r, objectId);
+		List<PathChangeModel> paths = JGitUtils.getFilesInCommit(r, commit);
+
+		Map<String, SubmoduleModel> submodules = new HashMap<String, SubmoduleModel>();
+		for (SubmoduleModel model : JGitUtils.getSubmodules(r, commit.getTree())) {
+			submodules.put(model.path, model);
+		}
+
+		PathModel matchingPath = null;
+		for (PathModel p : paths) {
+			if (p.path.equals(path)) {
+				matchingPath = p;
+				break;
+			}
+		}
+		if (matchingPath == null) {
+			// path not in commit
+			// manually locate path in tree
+			TreeWalk tw = new TreeWalk(r);
+			tw.reset();
+			tw.setRecursive(true);
+			try {
+				tw.addTree(commit.getTree());
+				tw.setFilter(PathFilterGroup.createFromStrings(Collections.singleton(path)));
+				while (tw.next()) {
+					if (tw.getPathString().equals(path)) {
+						matchingPath = new PathChangeModel(tw.getPathString(), tw.getPathString(), 0, tw
+							.getRawMode(0), tw.getObjectId(0).getName(), commit.getId().getName(),
+							ChangeType.MODIFY);
+					}
+				}
+			} catch (Exception e) {
+			} finally {
+				tw.release();
+			}
+		}
+		
+		final boolean isTree = matchingPath == null ? true : matchingPath.isTree();
+		final boolean isSubmodule = matchingPath == null ? false : matchingPath.isSubmodule();
+
+		// submodule
+		final String submodulePath;
+		final boolean hasSubmodule; 
+		if (isSubmodule) {
+			SubmoduleModel submodule = getSubmodule(submodules, repositoryName, matchingPath == null ? null : matchingPath.path);
+			submodulePath = submodule.gitblitPath;
+			hasSubmodule = submodule.hasSubmodule;
+		} else {
+			submodulePath = "";
+			hasSubmodule = false;
+		}
+		
+		final Map<ObjectId, List<RefModel>> allRefs = JGitUtils.getAllRefs(r, showRemoteRefs);
+		List<RevCommit> commits;
+		if (pageResults) {
+			// Paging result set
+			commits = JGitUtils.getRevLog(r, objectId, path, pageOffset * itemsPerPage,
+					itemsPerPage);
+		} else {
+			// Fixed size result set
+			commits = JGitUtils.getRevLog(r, objectId, path, 0, limit);
+		}
+
+		// inaccurate way to determine if there are more commits.
+		// works unless commits.size() represents the exact end.
+		hasMore = commits.size() >= itemsPerPage;
+
+		add(new CommitHeaderPanel("commitHeader", repositoryName, commit));
+
+		// breadcrumbs
+		add(new PathBreadcrumbsPanel("breadcrumbs", repositoryName, path, objectId));
+
+		final int hashLen = GitBlit.getInteger(Keys.web.shortCommitIdLength, 6);
+		ListDataProvider<RevCommit> dp = new ListDataProvider<RevCommit>(commits);
+		DataView<RevCommit> logView = new DataView<RevCommit>("commit", dp) {
+			private static final long serialVersionUID = 1L;
+			int counter;
+
+			public void populateItem(final Item<RevCommit> item) {
+				final RevCommit entry = item.getModelObject();
+				final Date date = JGitUtils.getCommitDate(entry);
+
+				item.add(WicketUtils.createDateLabel("commitDate", date, getTimeZone(), getTimeUtils()));
+
+				// author search link
+				String author = entry.getAuthorIdent().getName();
+				LinkPanel authorLink = new LinkPanel("commitAuthor", "list", author,
+						GitSearchPage.class,
+						WicketUtils.newSearchParameter(repositoryName, objectId,
+								author, Constants.SearchType.AUTHOR));
+				setPersonSearchTooltip(authorLink, author, Constants.SearchType.AUTHOR);
+				item.add(authorLink);
+
+				// merge icon
+				if (entry.getParentCount() > 1) {
+					item.add(WicketUtils.newImage("commitIcon", "commit_merge_16x16.png"));
+				} else {
+					item.add(WicketUtils.newBlankImage("commitIcon"));
+				}
+
+				String shortMessage = entry.getShortMessage();
+				String trimmedMessage = shortMessage;
+				if (allRefs.containsKey(entry.getId())) {
+					trimmedMessage = StringUtils.trimString(shortMessage, Constants.LEN_SHORTLOG_REFS);
+				} else {
+					trimmedMessage = StringUtils.trimString(shortMessage, Constants.LEN_SHORTLOG);
+				}
+				LinkPanel shortlog = new LinkPanel("commitShortMessage", "list subject",
+						trimmedMessage, CommitPage.class, WicketUtils.newObjectParameter(
+								repositoryName, entry.getName()));
+				if (!shortMessage.equals(trimmedMessage)) {
+					WicketUtils.setHtmlTooltip(shortlog, shortMessage);
+				}
+				item.add(shortlog);
+
+				item.add(new RefsPanel("commitRefs", repositoryName, entry, allRefs));
+
+				if (isTree) {
+					// tree
+					item.add(new Label("hashLabel", getString("gb.tree") + "@"));
+					LinkPanel commitHash = new LinkPanel("hashLink", null, entry.getName().substring(0, hashLen),
+							TreePage.class, WicketUtils.newObjectParameter(
+									repositoryName, entry.getName()));
+					WicketUtils.setCssClass(commitHash, "shortsha1");
+					WicketUtils.setHtmlTooltip(commitHash, entry.getName());					
+					item.add(commitHash);
+					
+					Fragment links = new Fragment("historyLinks", "treeLinks", this);
+					links.add(new BookmarkablePageLink<Void>("commitdiff", CommitDiffPage.class,
+							WicketUtils.newObjectParameter(repositoryName, entry.getName())));
+					item.add(links);
+				} else if (isSubmodule) {
+					// submodule
+					Repository repository = GitBlit.self().getRepository(repositoryName);
+					String submoduleId = JGitUtils.getSubmoduleCommitId(repository, path, entry);
+					repository.close();
+					if (StringUtils.isEmpty(submoduleId)) {
+						// not a submodule at this commit, just a matching path
+						item.add(new Label("hashLabel").setVisible(false));
+						item.add(new Label("hashLink").setVisible(false));
+					} else {
+						// really a submodule
+						item.add(new Label("hashLabel", submodulePath + "@"));
+						LinkPanel commitHash = new LinkPanel("hashLink", null, submoduleId.substring(0, hashLen),
+								TreePage.class, WicketUtils.newObjectParameter(
+										submodulePath, submoduleId));
+						WicketUtils.setCssClass(commitHash, "shortsha1");
+						WicketUtils.setHtmlTooltip(commitHash, submoduleId);					
+						item.add(commitHash.setEnabled(hasSubmodule));
+					}
+					Fragment links = new Fragment("historyLinks", "treeLinks", this);
+					links.add(new BookmarkablePageLink<Void>("commitdiff", CommitDiffPage.class,
+							WicketUtils.newObjectParameter(repositoryName, entry.getName())));
+					item.add(links);
+				} else {					
+					// commit
+					item.add(new Label("hashLabel", getString("gb.blob") + "@"));
+					LinkPanel commitHash = new LinkPanel("hashLink", null, entry.getName().substring(0, hashLen),
+							BlobPage.class, WicketUtils.newPathParameter(
+									repositoryName, entry.getName(), path));
+					WicketUtils.setCssClass(commitHash, "sha1");
+					WicketUtils.setHtmlTooltip(commitHash, entry.getName());
+					item.add(commitHash);
+					
+					Fragment links = new Fragment("historyLinks", "blobLinks", this);
+					links.add(new BookmarkablePageLink<Void>("commitdiff", CommitDiffPage.class,
+							WicketUtils.newObjectParameter(repositoryName, entry.getName())));
+					links.add(new BookmarkablePageLink<Void>("difftocurrent", BlobDiffPage.class,
+							WicketUtils.newBlobDiffParameter(repositoryName, entry.getName(),
+									objectId, path)).setEnabled(counter > 0));
+					item.add(links);
+				}
+
+				WicketUtils.setAlternatingBackground(item, counter);
+				counter++;
+			}
+		};
+		add(logView);
+
+		// determine to show pager, more, or neither
+		if (limit <= 0) {
+			// no display limit
+			add(new Label("moreHistory", "").setVisible(false));
+		} else {
+			if (pageResults) {
+				// paging
+				add(new Label("moreHistory", "").setVisible(false));
+			} else {
+				// more
+				if (commits.size() == limit) {
+					// show more
+					add(new LinkPanel("moreHistory", "link", new StringResourceModel(
+							"gb.moreHistory", this, null), HistoryPage.class,
+							WicketUtils.newPathParameter(repositoryName, objectId, path)));
+				} else {
+					// no more
+					add(new Label("moreHistory", "").setVisible(false));
+				}
+			}
+		}
+	}
+
+	public boolean hasMore() {
+		return hasMore;
+	}
+	
+	protected SubmoduleModel getSubmodule(Map<String, SubmoduleModel> submodules, String repositoryName, String path) {
+		SubmoduleModel model = submodules.get(path);
+		if (model == null) {
+			// undefined submodule?!
+			model = new SubmoduleModel(path.substring(path.lastIndexOf('/') + 1), path, path);
+			model.hasSubmodule = false;
+			model.gitblitPath = model.name;
+			return model;
+		} else {
+			// extract the repository name from the clone url
+			List<String> patterns = GitBlit.getStrings(Keys.git.submoduleUrlPatterns);
+			String submoduleName = StringUtils.extractRepositoryPath(model.url, patterns.toArray(new String[0]));
+			
+			// determine the current path for constructing paths relative
+			// to the current repository
+			String currentPath = "";
+			if (repositoryName.indexOf('/') > -1) {
+				currentPath = repositoryName.substring(0, repositoryName.lastIndexOf('/') + 1);
+			}
+
+			// try to locate the submodule repository
+			// prefer bare to non-bare names
+			List<String> candidates = new ArrayList<String>();
+
+			// relative
+			candidates.add(currentPath + StringUtils.stripDotGit(submoduleName));
+			candidates.add(candidates.get(candidates.size() - 1) + ".git");
+
+			// relative, no subfolder
+			if (submoduleName.lastIndexOf('/') > -1) {
+				String name = submoduleName.substring(submoduleName.lastIndexOf('/') + 1);
+				candidates.add(currentPath + StringUtils.stripDotGit(name));
+				candidates.add(candidates.get(candidates.size() - 1) + ".git");
+			}
+
+			// absolute
+			candidates.add(StringUtils.stripDotGit(submoduleName));
+			candidates.add(candidates.get(candidates.size() - 1) + ".git");
+
+			// absolute, no subfolder
+			if (submoduleName.lastIndexOf('/') > -1) {
+				String name = submoduleName.substring(submoduleName.lastIndexOf('/') + 1);
+				candidates.add(StringUtils.stripDotGit(name));
+				candidates.add(candidates.get(candidates.size() - 1) + ".git");
+			}
+
+			// create a unique, ordered set of candidate paths
+			Set<String> paths = new LinkedHashSet<String>(candidates);
+			for (String candidate : paths) {
+				if (GitBlit.self().hasRepository(candidate)) {
+					model.hasSubmodule = true;
+					model.gitblitPath = candidate;
+					return model;
+				}
+			}
+			
+			// we do not have a copy of the submodule, but we need a path
+			model.gitblitPath = candidates.get(0);
+			return model;
+		}		
+	}
+}
diff --git a/src/com/gitblit/wicket/panels/LinkPanel.html b/src/main/java/com/gitblit/wicket/panels/LinkPanel.html
similarity index 100%
rename from src/com/gitblit/wicket/panels/LinkPanel.html
rename to src/main/java/com/gitblit/wicket/panels/LinkPanel.html
diff --git a/src/main/java/com/gitblit/wicket/panels/LinkPanel.java b/src/main/java/com/gitblit/wicket/panels/LinkPanel.java
new file mode 100644
index 0000000..21470b2
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/panels/LinkPanel.java
@@ -0,0 +1,116 @@
+/*
+ * 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.wicket.panels;
+
+import org.apache.wicket.Component;
+import org.apache.wicket.PageParameters;
+import org.apache.wicket.behavior.SimpleAttributeModifier;
+import org.apache.wicket.markup.html.WebPage;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.link.BookmarkablePageLink;
+import org.apache.wicket.markup.html.link.ExternalLink;
+import org.apache.wicket.markup.html.link.Link;
+import org.apache.wicket.markup.html.panel.Panel;
+import org.apache.wicket.model.IModel;
+import org.apache.wicket.model.Model;
+
+import com.gitblit.utils.StringUtils;
+import com.gitblit.wicket.WicketUtils;
+
+public class LinkPanel extends Panel {
+
+	private static final long serialVersionUID = 1L;
+
+	private final IModel<String> labelModel;
+
+	public LinkPanel(String wicketId, String linkCssClass, String label,
+			Class<? extends WebPage> clazz) {
+		this(wicketId, null, linkCssClass, new Model<String>(label), clazz, null, false);
+	}
+
+	public LinkPanel(String wicketId, String linkCssClass, String label,
+			Class<? extends WebPage> clazz, PageParameters parameters) {
+		this(wicketId, null, linkCssClass, new Model<String>(label), clazz, parameters, false);
+	}
+
+	public LinkPanel(String wicketId, String linkCssClass, String label,
+			Class<? extends WebPage> clazz, PageParameters parameters, boolean newWindow) {
+		this(wicketId, null, linkCssClass, new Model<String>(label), clazz, parameters, newWindow);
+	}
+
+	public LinkPanel(String wicketId, String bootstrapIcon, String linkCssClass, String label,
+			Class<? extends WebPage> clazz, PageParameters parameters, boolean newWindow) {
+		this(wicketId, bootstrapIcon, linkCssClass, new Model<String>(label), clazz, parameters, newWindow);
+	}
+
+	public LinkPanel(String wicketId, String linkCssClass, IModel<String> model,
+			Class<? extends WebPage> clazz, PageParameters parameters) {
+		this(wicketId, null, linkCssClass, model, clazz, parameters, false);
+	}
+
+	public LinkPanel(String wicketId, String bootstrapIcon, String linkCssClass, IModel<String> model,
+			Class<? extends WebPage> clazz, PageParameters parameters, boolean newWindow) {
+		super(wicketId);
+		this.labelModel = model;
+		Link<Void> link = null;
+		if (parameters == null) {
+			link = new BookmarkablePageLink<Void>("link", clazz);
+		} else {
+			link = new BookmarkablePageLink<Void>("link", clazz, parameters);
+		}
+		if (newWindow) {
+			link.add(new SimpleAttributeModifier("target", "_blank"));
+		}
+		if (linkCssClass != null) {
+			link.add(new SimpleAttributeModifier("class", linkCssClass));
+		}
+		Label icon = new Label("icon");
+		if (StringUtils.isEmpty(bootstrapIcon)) {
+			link.add(icon.setVisible(false));
+		} else {
+			WicketUtils.setCssClass(icon, bootstrapIcon);
+			link.add(icon);
+		}
+		link.add(new Label("label", labelModel).setRenderBodyOnly(true));
+		add(link);
+	}
+
+	public LinkPanel(String wicketId, String linkCssClass, String label, String href) {
+		this(wicketId, linkCssClass, label, href, false);
+	}
+
+	public LinkPanel(String wicketId, String linkCssClass, String label, String href,
+			boolean newWindow) {
+		super(wicketId);
+		this.labelModel = new Model<String>(label);
+		ExternalLink link = new ExternalLink("link", href);
+		if (newWindow) {
+			link.add(new SimpleAttributeModifier("target", "_blank"));
+		}
+		if (linkCssClass != null) {
+			link.add(new SimpleAttributeModifier("class", linkCssClass));
+		}
+		link.add(new Label("icon").setVisible(false));
+		link.add(new Label("label", labelModel));
+		add(link);
+	}
+	
+	public void setNoFollow() {
+		Component c = get("link");
+		c.add(new SimpleAttributeModifier("rel", "nofollow"));
+	}
+
+}
diff --git a/src/main/java/com/gitblit/wicket/panels/LogPanel.html b/src/main/java/com/gitblit/wicket/panels/LogPanel.html
new file mode 100644
index 0000000..1abda87
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/panels/LogPanel.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"  
+      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"  
+      xml:lang="en"  
+      lang="en"> 
+
+<body>
+<wicket:panel>
+
+	<!-- header -->	
+	<div class="header"><i class="icon-refresh"></i> <b><span wicket:id="header">[log header]</span></b></div>
+	<table class="pretty">
+		<tbody>
+       		<tr wicket:id="commit">
+         		<td class="date" style="width:6em;"><span wicket:id="commitDate">[commit date]</span></td>
+         		<td class="hidden-phone author"><span wicket:id="commitAuthor">[commit author]</span></td>
+         		<td class="hidden-phone icon"><img wicket:id="commitIcon" /></td>
+         		<td class="message"><table class="nestedTable"><tr><td><span style="vertical-align:middle;" wicket:id="commitShortMessage">[commit short message]</span></td><td><div style="text-align:right;" wicket:id="commitRefs">[commit refs]</div></td></tr></table></td>
+         		<td class="hidden-phone hidden-tablet rightAlign"><span wicket:id="hashLink">[hash link]</span></td>
+         		<td class="hidden-phone hidden-tablet rightAlign">
+         			<span class="link">
+						<a wicket:id="diff"><wicket:message key="gb.diff"></wicket:message></a> | <a wicket:id="tree"><wicket:message key="gb.tree"></wicket:message></a>
+					</span>
+				</td>
+       		</tr>
+    	</tbody>
+	</table>	
+	<div wicket:id="moreLogs">[more...]</div>
+	
+</wicket:panel>
+</body>
+</html>
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/panels/LogPanel.java b/src/main/java/com/gitblit/wicket/panels/LogPanel.java
new file mode 100644
index 0000000..6c523be
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/panels/LogPanel.java
@@ -0,0 +1,176 @@
+/*
+ * 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.wicket.panels;
+
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.link.BookmarkablePageLink;
+import org.apache.wicket.markup.repeater.Item;
+import org.apache.wicket.markup.repeater.data.DataView;
+import org.apache.wicket.markup.repeater.data.ListDataProvider;
+import org.apache.wicket.model.StringResourceModel;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+
+import com.gitblit.Constants;
+import com.gitblit.GitBlit;
+import com.gitblit.Keys;
+import com.gitblit.models.RefModel;
+import com.gitblit.utils.JGitUtils;
+import com.gitblit.utils.StringUtils;
+import com.gitblit.wicket.WicketUtils;
+import com.gitblit.wicket.pages.CommitDiffPage;
+import com.gitblit.wicket.pages.CommitPage;
+import com.gitblit.wicket.pages.GitSearchPage;
+import com.gitblit.wicket.pages.LogPage;
+import com.gitblit.wicket.pages.TreePage;
+
+public class LogPanel extends BasePanel {
+
+	private static final long serialVersionUID = 1L;
+
+	private boolean hasMore;
+
+	public LogPanel(String wicketId, final String repositoryName, final String objectId,
+			Repository r, int limit, int pageOffset, boolean showRemoteRefs) {
+		super(wicketId);
+		boolean pageResults = limit <= 0;
+		int itemsPerPage = GitBlit.getInteger(Keys.web.itemsPerPage, 50);
+		if (itemsPerPage <= 1) {
+			itemsPerPage = 50;
+		}
+
+		final Map<ObjectId, List<RefModel>> allRefs = JGitUtils.getAllRefs(r, showRemoteRefs);
+		List<RevCommit> commits;
+		if (pageResults) {
+			// Paging result set
+			commits = JGitUtils.getRevLog(r, objectId, pageOffset * itemsPerPage, itemsPerPage);
+		} else {
+			// Fixed size result set
+			commits = JGitUtils.getRevLog(r, objectId, 0, limit);
+		}
+
+		// inaccurate way to determine if there are more commits.
+		// works unless commits.size() represents the exact end.
+		hasMore = commits.size() >= itemsPerPage;
+
+		// header
+		if (pageResults) {
+			// shortlog page
+			add(new Label("header", objectId));
+		} else {
+			// summary page
+			// show shortlog page link
+			add(new LinkPanel("header", "title", objectId, LogPage.class,
+					WicketUtils.newRepositoryParameter(repositoryName)));
+		}
+
+		final int hashLen = GitBlit.getInteger(Keys.web.shortCommitIdLength, 6);
+		ListDataProvider<RevCommit> dp = new ListDataProvider<RevCommit>(commits);
+		DataView<RevCommit> logView = new DataView<RevCommit>("commit", dp) {
+			private static final long serialVersionUID = 1L;
+			int counter;
+
+			public void populateItem(final Item<RevCommit> item) {
+				final RevCommit entry = item.getModelObject();
+				final Date date = JGitUtils.getCommitDate(entry);
+
+				item.add(WicketUtils.createDateLabel("commitDate", date, getTimeZone(), getTimeUtils()));
+
+				// author search link
+				String author = entry.getAuthorIdent().getName();
+				LinkPanel authorLink = new LinkPanel("commitAuthor", "list", author,
+						GitSearchPage.class, WicketUtils.newSearchParameter(repositoryName,
+								objectId, author, Constants.SearchType.AUTHOR));
+				setPersonSearchTooltip(authorLink, author, Constants.SearchType.AUTHOR);
+				item.add(authorLink);
+				
+				// merge icon
+				if (entry.getParentCount() > 1) {
+					item.add(WicketUtils.newImage("commitIcon", "commit_merge_16x16.png"));
+				} else {
+					item.add(WicketUtils.newBlankImage("commitIcon"));
+				}
+
+				// short message
+				String shortMessage = entry.getShortMessage();
+				String trimmedMessage = shortMessage;
+				if (allRefs.containsKey(entry.getId())) {
+					trimmedMessage = StringUtils.trimString(shortMessage, Constants.LEN_SHORTLOG_REFS);
+				} else {
+					trimmedMessage = StringUtils.trimString(shortMessage, Constants.LEN_SHORTLOG);
+				}
+				LinkPanel shortlog = new LinkPanel("commitShortMessage", "list subject",
+						trimmedMessage, CommitPage.class, WicketUtils.newObjectParameter(
+								repositoryName, entry.getName()));
+				if (!shortMessage.equals(trimmedMessage)) {
+					WicketUtils.setHtmlTooltip(shortlog, shortMessage);
+				}
+				item.add(shortlog);
+
+				item.add(new RefsPanel("commitRefs", repositoryName, entry, allRefs));
+
+				// commit hash link
+				LinkPanel commitHash = new LinkPanel("hashLink", null, entry.getName().substring(0, hashLen),
+						CommitPage.class, WicketUtils.newObjectParameter(
+								repositoryName, entry.getName()));
+				WicketUtils.setCssClass(commitHash, "shortsha1");
+				WicketUtils.setHtmlTooltip(commitHash, entry.getName());
+				item.add(commitHash);
+				
+				item.add(new BookmarkablePageLink<Void>("diff", CommitDiffPage.class, WicketUtils
+						.newObjectParameter(repositoryName, entry.getName())).setEnabled(entry
+						.getParentCount() > 0));
+				item.add(new BookmarkablePageLink<Void>("tree", TreePage.class, WicketUtils
+						.newObjectParameter(repositoryName, entry.getName())));
+
+				WicketUtils.setAlternatingBackground(item, counter);
+				counter++;
+			}
+		};
+		add(logView);
+
+		// determine to show pager, more, or neither
+		if (limit <= 0) {
+			// no display limit
+			add(new Label("moreLogs", "").setVisible(false));
+		} else {
+			if (pageResults) {
+				// paging
+				add(new Label("moreLogs", "").setVisible(false));
+			} else {
+				// more
+				if (commits.size() == limit) {
+					// show more
+					add(new LinkPanel("moreLogs", "link", new StringResourceModel("gb.moreLogs",
+							this, null), LogPage.class,
+							WicketUtils.newRepositoryParameter(repositoryName)));
+				} else {
+					// no more
+					add(new Label("moreLogs", "").setVisible(false));
+				}
+			}
+		}
+	}
+
+	public boolean hasMore() {
+		return hasMore;
+	}
+}
diff --git a/src/com/gitblit/wicket/panels/NavigationPanel.html b/src/main/java/com/gitblit/wicket/panels/NavigationPanel.html
similarity index 100%
rename from src/com/gitblit/wicket/panels/NavigationPanel.html
rename to src/main/java/com/gitblit/wicket/panels/NavigationPanel.html
diff --git a/src/main/java/com/gitblit/wicket/panels/NavigationPanel.java b/src/main/java/com/gitblit/wicket/panels/NavigationPanel.java
new file mode 100644
index 0000000..436db37
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/panels/NavigationPanel.java
@@ -0,0 +1,77 @@
+/*
+ * 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.wicket.panels;
+
+import java.util.List;
+
+import org.apache.wicket.Component;
+import org.apache.wicket.markup.html.panel.Panel;
+import org.apache.wicket.markup.repeater.Item;
+import org.apache.wicket.markup.repeater.data.DataView;
+import org.apache.wicket.markup.repeater.data.ListDataProvider;
+
+import com.gitblit.wicket.PageRegistration;
+import com.gitblit.wicket.PageRegistration.DropDownMenuRegistration;
+import com.gitblit.wicket.PageRegistration.OtherPageLink;
+import com.gitblit.wicket.WicketUtils;
+import com.gitblit.wicket.pages.BasePage;
+
+public class NavigationPanel extends Panel {
+
+	private static final long serialVersionUID = 1L;
+
+	public NavigationPanel(String id, final Class<? extends BasePage> pageClass,
+			List<PageRegistration> registeredPages) {
+		super(id);
+
+		ListDataProvider<PageRegistration> refsDp = new ListDataProvider<PageRegistration>(
+				registeredPages);
+		DataView<PageRegistration> refsView = new DataView<PageRegistration>("navLink", refsDp) {
+			private static final long serialVersionUID = 1L;
+
+			public void populateItem(final Item<PageRegistration> item) {
+				PageRegistration entry = item.getModelObject();
+				if (entry.hiddenPhone) {
+					WicketUtils.setCssClass(item, "hidden-phone");
+				}
+				if (entry instanceof OtherPageLink) {
+					// other link
+					OtherPageLink link = (OtherPageLink) entry;
+					Component c = new LinkPanel("link", null, getString(entry.translationKey), link.url);
+					c.setRenderBodyOnly(true);
+					item.add(c);
+				} else if (entry instanceof DropDownMenuRegistration) {
+					// drop down menu
+					DropDownMenuRegistration reg = (DropDownMenuRegistration) entry;
+					Component c = new DropDownMenu("link", getString(entry.translationKey), reg);
+					c.setRenderBodyOnly(true);
+					item.add(c);
+					WicketUtils.setCssClass(item, "dropdown");
+				} else {
+					// standard page link
+					Component c = new LinkPanel("link", null, getString(entry.translationKey),
+							entry.pageClass, entry.params);
+					c.setRenderBodyOnly(true);
+					if (entry.pageClass.equals(pageClass)) {
+						WicketUtils.setCssClass(item, "active");
+					}
+					item.add(c);
+				}
+			}
+		};
+		add(refsView);
+	}
+}
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/panels/ObjectContainer.java b/src/main/java/com/gitblit/wicket/panels/ObjectContainer.java
similarity index 100%
rename from src/com/gitblit/wicket/panels/ObjectContainer.java
rename to src/main/java/com/gitblit/wicket/panels/ObjectContainer.java
diff --git a/src/com/gitblit/wicket/panels/PagerPanel.html b/src/main/java/com/gitblit/wicket/panels/PagerPanel.html
similarity index 100%
rename from src/com/gitblit/wicket/panels/PagerPanel.html
rename to src/main/java/com/gitblit/wicket/panels/PagerPanel.html
diff --git a/src/com/gitblit/wicket/panels/PagerPanel.java b/src/main/java/com/gitblit/wicket/panels/PagerPanel.java
similarity index 100%
rename from src/com/gitblit/wicket/panels/PagerPanel.java
rename to src/main/java/com/gitblit/wicket/panels/PagerPanel.java
diff --git a/src/com/gitblit/wicket/panels/PathBreadcrumbsPanel.html b/src/main/java/com/gitblit/wicket/panels/PathBreadcrumbsPanel.html
similarity index 100%
rename from src/com/gitblit/wicket/panels/PathBreadcrumbsPanel.html
rename to src/main/java/com/gitblit/wicket/panels/PathBreadcrumbsPanel.html
diff --git a/src/com/gitblit/wicket/panels/PathBreadcrumbsPanel.java b/src/main/java/com/gitblit/wicket/panels/PathBreadcrumbsPanel.java
similarity index 100%
rename from src/com/gitblit/wicket/panels/PathBreadcrumbsPanel.java
rename to src/main/java/com/gitblit/wicket/panels/PathBreadcrumbsPanel.java
diff --git a/src/main/java/com/gitblit/wicket/panels/ProjectRepositoryPanel.html b/src/main/java/com/gitblit/wicket/panels/ProjectRepositoryPanel.html
new file mode 100644
index 0000000..02d67e3
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/panels/ProjectRepositoryPanel.html
@@ -0,0 +1,77 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"  
+      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"  
+      xml:lang="en"  
+      lang="en"> 
+
+<wicket:panel>
+	<wicket:fragment wicket:id="repositoryAdminLinks">
+		<span class="link">
+			<a wicket:id="tree"><wicket:message key="gb.tree"></wicket:message></a>
+			| <a wicket:id="log"><wicket:message key="gb.log"></wicket:message></a>
+			| <a wicket:id="editRepository"><wicket:message key="gb.edit">[edit]</wicket:message></a>
+			| <a wicket:id="deleteRepository"><wicket:message key="gb.delete">[delete]</wicket:message></a>
+		</span>
+	</wicket:fragment>
+
+	<wicket:fragment wicket:id="repositoryOwnerLinks">
+		<span class="link">
+			<a wicket:id="tree"><wicket:message key="gb.tree"></wicket:message></a>
+			| <a wicket:id="log"><wicket:message key="gb.log"></wicket:message></a>
+			| <a wicket:id="editRepository"><wicket:message key="gb.edit">[edit]</wicket:message></a>
+		</span>
+	</wicket:fragment>
+
+	<wicket:fragment wicket:id="repositoryUserLinks">
+		<span class="link">
+			<a wicket:id="tree"><wicket:message key="gb.tree"></wicket:message></a>
+			| <a wicket:id="log"><wicket:message key="gb.log"></wicket:message></a>
+		</span>
+	</wicket:fragment>
+
+	<wicket:fragment wicket:id="originFragment">
+		<p class="originRepository" style="margin-left:20px;" ><wicket:message key="gb.forkedFrom">[forked from]</wicket:message> <span wicket:id="originRepository">[origin repository]</span></p>
+	</wicket:fragment>
+
+	<div>
+		<div style="padding-top:15px;padding-bottom:15px;margin-right:15px;">
+			<div class="pull-right" style="text-align:right;padding-right:15px;">
+				<span wicket:id="repositoryLinks"></span>
+				<div>
+					<img class="inlineIcon" wicket:id="sparkleshareIcon" />
+					<img class="inlineIcon" wicket:id="frozenIcon" />
+					<img class="inlineIcon" wicket:id="federatedIcon" />
+        						
+					<a style="text-decoration: none;" wicket:id="tickets" wicket:message="title:gb.tickets">
+						<img style="border:0px;vertical-align:middle;" src="bug_16x16.png"></img>
+					</a>
+					<a style="text-decoration: none;" wicket:id="docs" wicket:message="title:gb.docs">
+						<img style="border:0px;vertical-align:middle;" src="book_16x16.png"></img>
+					</a>
+					<a style="text-decoration: none;" wicket:id="syndication" wicket:message="title:gb.feed">
+						<img style="border:0px;vertical-align:middle;" src="feed_16x16.png"></img>
+					</a>
+				</div>
+				<span style="color: #999;font-style:italic;font-size:0.8em;" wicket:id="repositoryOwner">[owner]</span>
+			</div>	
+			
+			<div class="pageTitle" style="border:0px;">
+				<div>
+					<span class="repositorySwatch" wicket:id="repositorySwatch"></span>
+					<span class="repository" style="padding-left:3px;color:black;" wicket:id="repositoryName">[repository name]</span>
+				</div>
+				<span wicket:id="originRepository">[origin repository]</span>
+			</div>
+			
+			<div style="padding-left:20px;">
+				<div style="padding-bottom:10px" wicket:id="repositoryDescription">[repository description]</div>
+
+    			<div style="color: #999;">
+					<wicket:message key="gb.lastChange">[last change]</wicket:message> <span wicket:id="repositoryLastChange">[last change]</span>,
+					<span style="font-size:0.8em;" wicket:id="repositorySize">[repository size]</span>
+				</div>
+			</div>
+		</div>
+	</div>
+</wicket:panel>
+</html>
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/panels/ProjectRepositoryPanel.java b/src/main/java/com/gitblit/wicket/panels/ProjectRepositoryPanel.java
new file mode 100644
index 0000000..37641d3
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/panels/ProjectRepositoryPanel.java
@@ -0,0 +1,196 @@
+/*
+ * 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.wicket.panels;
+
+import java.text.MessageFormat;
+import java.util.Map;
+
+import org.apache.wicket.Component;
+import org.apache.wicket.Localizer;
+import org.apache.wicket.PageParameters;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.link.BookmarkablePageLink;
+import org.apache.wicket.markup.html.link.ExternalLink;
+import org.apache.wicket.markup.html.link.Link;
+import org.apache.wicket.markup.html.panel.Fragment;
+
+import com.gitblit.Constants.AccessRestrictionType;
+import com.gitblit.GitBlit;
+import com.gitblit.Keys;
+import com.gitblit.SyndicationServlet;
+import com.gitblit.models.RepositoryModel;
+import com.gitblit.models.UserModel;
+import com.gitblit.utils.ArrayUtils;
+import com.gitblit.utils.StringUtils;
+import com.gitblit.wicket.GitBlitWebSession;
+import com.gitblit.wicket.WicketUtils;
+import com.gitblit.wicket.pages.DocsPage;
+import com.gitblit.wicket.pages.EditRepositoryPage;
+import com.gitblit.wicket.pages.LogPage;
+import com.gitblit.wicket.pages.SummaryPage;
+import com.gitblit.wicket.pages.TicketsPage;
+import com.gitblit.wicket.pages.TreePage;
+
+public class ProjectRepositoryPanel extends BasePanel {
+
+	private static final long serialVersionUID = 1L;
+
+	public ProjectRepositoryPanel(String wicketId, Localizer localizer, Component parent,
+			final boolean isAdmin, final RepositoryModel entry,
+			final Map<AccessRestrictionType, String> accessRestrictions) {
+		super(wicketId);
+
+		final boolean showSwatch = GitBlit.getBoolean(Keys.web.repositoryListSwatches, true);
+		final boolean showSize = GitBlit.getBoolean(Keys.web.showRepositorySizes, true);
+
+		// repository swatch
+		Component swatch;
+		if (entry.isBare) {
+			swatch = new Label("repositorySwatch", "&nbsp;").setEscapeModelStrings(false);
+		} else {
+			swatch = new Label("repositorySwatch", "!");
+			WicketUtils.setHtmlTooltip(swatch, localizer.getString("gb.workingCopyWarning", parent));
+		}
+		WicketUtils.setCssBackground(swatch, entry.toString());
+		add(swatch);
+		swatch.setVisible(showSwatch);
+
+		PageParameters pp = WicketUtils.newRepositoryParameter(entry.name);
+		add(new LinkPanel("repositoryName", "list", StringUtils.getRelativePath(entry.projectPath,
+				StringUtils.stripDotGit(entry.name)), SummaryPage.class, pp));
+		add(new Label("repositoryDescription", entry.description).setVisible(!StringUtils
+				.isEmpty(entry.description)));
+
+		if (StringUtils.isEmpty(entry.originRepository)) {
+			add(new Label("originRepository").setVisible(false));
+		} else {
+			Fragment forkFrag = new Fragment("originRepository", "originFragment", this);
+			forkFrag.add(new LinkPanel("originRepository", null, StringUtils.stripDotGit(entry.originRepository), 
+					SummaryPage.class, WicketUtils.newRepositoryParameter(entry.originRepository)));
+			add(forkFrag);
+		}
+
+		if (entry.isSparkleshared()) {
+			add(WicketUtils.newImage("sparkleshareIcon", "star_16x16.png", localizer.getString("gb.isSparkleshared", parent)));
+		} else {
+			add(WicketUtils.newClearPixel("sparkleshareIcon").setVisible(false));
+		}
+
+		add(new BookmarkablePageLink<Void>("tickets", TicketsPage.class, pp).setVisible(entry.useTickets));
+		add(new BookmarkablePageLink<Void>("docs", DocsPage.class, pp).setVisible(entry.useDocs));
+
+		if (entry.isFrozen) {
+			add(WicketUtils.newImage("frozenIcon", "cold_16x16.png", localizer.getString("gb.isFrozen", parent)));
+		} else {
+			add(WicketUtils.newClearPixel("frozenIcon").setVisible(false));
+		}
+
+		if (entry.isFederated) {
+			add(WicketUtils.newImage("federatedIcon", "federated_16x16.png", localizer.getString("gb.isFederated", parent)));
+		} else {
+			add(WicketUtils.newClearPixel("federatedIcon").setVisible(false));
+		}
+
+		if (ArrayUtils.isEmpty(entry.owners)) {
+			add(new Label("repositoryOwner").setVisible(false));
+		} else {
+			String owner = "";
+			for (String username : entry.owners) {
+				UserModel ownerModel = GitBlit.self().getUserModel(username);
+			
+				if (ownerModel != null) {
+					owner = ownerModel.getDisplayName();
+				}				
+			}
+			if (entry.owners.size() > 1) {
+				owner += ", ...";
+			}
+			Label ownerLabel = (new Label("repositoryOwner", owner + " (" +
+					localizer.getString("gb.owner", parent) + ")"));
+			WicketUtils.setHtmlTooltip(ownerLabel, ArrayUtils.toString(entry.owners));
+			add(ownerLabel);
+		}
+
+		UserModel user = GitBlitWebSession.get().getUser();
+		if (user == null) {
+			user = UserModel.ANONYMOUS;
+		}
+		Fragment repositoryLinks;
+		boolean showOwner = entry.isOwner(user.username);
+		// owner of personal repository gets admin powers
+		boolean showAdmin = isAdmin || entry.isUsersPersonalRepository(user.username);
+
+		if (showAdmin || showOwner) {
+			repositoryLinks = new Fragment("repositoryLinks", showAdmin ? "repositoryAdminLinks"
+					: "repositoryOwnerLinks", this);
+			repositoryLinks.add(new BookmarkablePageLink<Void>("editRepository", EditRepositoryPage.class,
+					WicketUtils.newRepositoryParameter(entry.name)));
+			if (showAdmin) {
+				Link<Void> deleteLink = new Link<Void>("deleteRepository") {
+
+					private static final long serialVersionUID = 1L;
+
+					@Override
+					public void onClick() {
+						if (GitBlit.self().deleteRepositoryModel(entry)) {
+							// redirect to the owning page
+							if (entry.isPersonalRepository()) {
+								setResponsePage(getPage().getClass(), WicketUtils.newUsernameParameter(entry.projectPath.substring(1)));
+							} else {
+								setResponsePage(getPage().getClass(), WicketUtils.newProjectParameter(entry.projectPath));
+							}
+						} else {
+							error(MessageFormat.format(getString("gb.repositoryDeleteFailed"), entry));
+						}
+					}
+				};
+				deleteLink.add(new JavascriptEventConfirmation("onclick", MessageFormat.format(
+						localizer.getString("gb.deleteRepository", parent), entry)));
+				repositoryLinks.add(deleteLink);
+			}
+		} else {
+			repositoryLinks = new Fragment("repositoryLinks", "repositoryUserLinks", this);
+		}
+
+		repositoryLinks.add(new BookmarkablePageLink<Void>("tree", TreePage.class, WicketUtils
+				.newRepositoryParameter(entry.name)).setEnabled(entry.hasCommits));
+
+		repositoryLinks.add(new BookmarkablePageLink<Void>("log", LogPage.class, WicketUtils
+				.newRepositoryParameter(entry.name)).setEnabled(entry.hasCommits));
+
+		add(repositoryLinks);
+
+		String lastChange;
+		if (entry.lastChange.getTime() == 0) {
+			lastChange = "--";
+		} else {
+			lastChange = getTimeUtils().timeAgo(entry.lastChange);
+		}
+		Label lastChangeLabel = new Label("repositoryLastChange", lastChange);
+		add(lastChangeLabel);
+		WicketUtils.setCssClass(lastChangeLabel, getTimeUtils().timeAgoCss(entry.lastChange));
+
+		if (entry.hasCommits) {
+			// Existing repository
+			add(new Label("repositorySize", entry.size).setVisible(showSize));
+		} else {
+			// New repository
+			add(new Label("repositorySize", localizer.getString("gb.empty", parent)).setEscapeModelStrings(false));
+		}
+
+		add(new ExternalLink("syndication", SyndicationServlet.asLink("", entry.name, null, 0)));
+	}
+}
diff --git a/src/main/java/com/gitblit/wicket/panels/ReflogPanel.html b/src/main/java/com/gitblit/wicket/panels/ReflogPanel.html
new file mode 100644
index 0000000..8df2849
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/panels/ReflogPanel.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"  
+      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"  
+      xml:lang="en"  
+      lang="en"> 
+
+<body>
+<wicket:panel>
+<div wicket:id="change" class="reflog">
+	<table style="padding: 3px 0px;">
+	<tr>
+		<td class="icon hidden-phone"><i wicket:id="changeIcon"></i></td>
+		<td style="padding-left: 7px;vertical-align:middle;">
+			<div>
+				<span class="when" wicket:id="whenChanged"></span> <span wicket:id="refRewind" class="alert alert-error" style="padding: 1px 5px;font-size: 10px;font-weight: bold;margin-left: 10px;">[rewind]</span>
+			</div>
+			<div style="font-weight:bold;"><span wicket:id="whoChanged">[change author]</span> <span wicket:id="whatChanged"></span> <span wicket:id="refChanged"></span> <span wicket:id="byAuthors"></span></div>
+		</td>
+	</tr>
+	<tr>
+		<td class="hidden-phone"></td>
+		<td style="padding-left: 7px;">
+			<div>
+				<table>
+					<tr wicket:id="commit">
+						<td class="hidden-phone hidden-tablet" style="vertical-align:top;padding-left:7px;"><span wicket:id="commitAuthor"></span></td>
+						<td style="vertical-align:top;"><span wicket:id="hashLink" style="padding-left: 5px;">[hash link]</span></td>
+						<td style="vertical-align:top;padding-left:5px;"><img wicket:id="commitIcon" /></td>
+						<td style="vertical-align:top;">   							
+							<span wicket:id="commitShortMessage">[commit short message]</span>
+						</td>
+					</tr>
+				</table>
+				<span class="link" wicket:id="compareLink"></span>
+			</div>
+		</td>
+	</tr>	
+	</table>
+</div>
+<div wicket:id="moreChanges">[more...]</div>
+</wicket:panel>
+</body>
+</html>
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/panels/ReflogPanel.java b/src/main/java/com/gitblit/wicket/panels/ReflogPanel.java
new file mode 100644
index 0000000..55c19cc
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/panels/ReflogPanel.java
@@ -0,0 +1,309 @@
+/*
+ * Copyright 2013 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit.wicket.panels;
+
+import java.text.DateFormat;
+import java.text.MessageFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+import java.util.TimeZone;
+
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.repeater.Item;
+import org.apache.wicket.markup.repeater.data.DataView;
+import org.apache.wicket.markup.repeater.data.ListDataProvider;
+import org.apache.wicket.model.StringResourceModel;
+import org.eclipse.jgit.lib.Repository;
+
+import com.gitblit.Constants;
+import com.gitblit.GitBlit;
+import com.gitblit.Keys;
+import com.gitblit.models.RefLogEntry;
+import com.gitblit.models.RepositoryCommit;
+import com.gitblit.models.RepositoryModel;
+import com.gitblit.models.UserModel;
+import com.gitblit.utils.RefLogUtils;
+import com.gitblit.utils.StringUtils;
+import com.gitblit.utils.TimeUtils;
+import com.gitblit.wicket.WicketUtils;
+import com.gitblit.wicket.pages.CommitPage;
+import com.gitblit.wicket.pages.ComparePage;
+import com.gitblit.wicket.pages.ReflogPage;
+import com.gitblit.wicket.pages.TagPage;
+import com.gitblit.wicket.pages.TreePage;
+import com.gitblit.wicket.pages.UserPage;
+
+public class ReflogPanel extends BasePanel {
+
+	private static final long serialVersionUID = 1L;
+
+	private final boolean hasChanges;
+	
+	private boolean hasMore;
+
+	public ReflogPanel(String wicketId, final RepositoryModel model, Repository r, int limit, int pageOffset) {
+		super(wicketId);
+		boolean pageResults = limit <= 0;
+		int changesPerPage = GitBlit.getInteger(Keys.web.reflogChangesPerPage, 10);
+		if (changesPerPage <= 1) {
+			changesPerPage = 10;
+		}
+
+		List<RefLogEntry> changes;
+		if (pageResults) {
+			changes = RefLogUtils.getLogByRef(model.name, r, pageOffset * changesPerPage, changesPerPage);
+		} else {
+			changes = RefLogUtils.getLogByRef(model.name, r, limit);
+		}
+
+		// inaccurate way to determine if there are more commits.
+		// works unless commits.size() represents the exact end.
+		hasMore = changes.size() >= changesPerPage;
+		hasChanges = changes.size() > 0;
+		
+		setup(changes);
+		
+		// determine to show pager, more, or neither
+		if (limit <= 0) {
+			// no display limit
+			add(new Label("moreChanges").setVisible(false));
+		} else {
+			if (pageResults) {
+				// paging
+				add(new Label("moreChanges").setVisible(false));
+			} else {
+				// more
+				if (changes.size() == limit) {
+					// show more
+					add(new LinkPanel("moreChanges", "link", new StringResourceModel("gb.moreChanges",
+							this, null), ReflogPage.class,
+							WicketUtils.newRepositoryParameter(model.name)));
+				} else {
+					// no more
+					add(new Label("moreChanges").setVisible(false));
+				}
+			}
+		}
+	}
+	
+	public ReflogPanel(String wicketId, List<RefLogEntry> changes) {
+		super(wicketId);
+		hasChanges = changes.size() > 0;
+		setup(changes);
+		add(new Label("moreChanges").setVisible(false));
+	}
+	
+	protected void setup(List<RefLogEntry> changes) {
+		final int hashLen = GitBlit.getInteger(Keys.web.shortCommitIdLength, 6);
+
+		String dateFormat = GitBlit.getString(Keys.web.datetimestampLongFormat, "EEEE, MMMM d, yyyy HH:mm Z");
+		final TimeZone timezone = getTimeZone();
+		final DateFormat df = new SimpleDateFormat(dateFormat);
+		df.setTimeZone(timezone);
+		final Calendar cal = Calendar.getInstance(timezone);
+		
+		ListDataProvider<RefLogEntry> dp = new ListDataProvider<RefLogEntry>(changes);
+		DataView<RefLogEntry> changeView = new DataView<RefLogEntry>("change", dp) {
+			private static final long serialVersionUID = 1L;
+
+			public void populateItem(final Item<RefLogEntry> changeItem) {
+				final RefLogEntry change = changeItem.getModelObject();
+				String fullRefName = change.getChangedRefs().get(0);
+				String shortRefName = fullRefName;
+				boolean isTag = false;
+				if (shortRefName.startsWith(Constants.R_HEADS)) {
+					shortRefName = shortRefName.substring(Constants.R_HEADS.length());
+				} else if (shortRefName.startsWith(Constants.R_TAGS)) {
+					shortRefName = shortRefName.substring(Constants.R_TAGS.length());
+					isTag = true;
+				}
+				
+				String fuzzydate;
+				TimeUtils tu = getTimeUtils();
+				Date changeDate = change.date;
+				if (TimeUtils.isToday(changeDate, timezone)) {
+					fuzzydate = tu.today();
+				} else if (TimeUtils.isYesterday(changeDate, timezone)) {
+					fuzzydate = tu.yesterday();
+				} else {
+					// calculate a fuzzy time ago date
+                	cal.setTime(changeDate);
+                	cal.set(Calendar.HOUR_OF_DAY, 0);
+                	cal.set(Calendar.MINUTE, 0);
+                	cal.set(Calendar.SECOND, 0);
+                	cal.set(Calendar.MILLISECOND, 0);
+                	changeDate = cal.getTime();
+					fuzzydate = getTimeUtils().timeAgo(changeDate);
+				}
+				changeItem.add(new Label("whenChanged", fuzzydate + ", " + df.format(changeDate)));
+
+				Label changeIcon = new Label("changeIcon");
+				if (isTag) {
+					WicketUtils.setCssClass(changeIcon, "iconic-tag");
+				} else {
+					WicketUtils.setCssClass(changeIcon, "iconic-upload");
+				}
+				changeItem.add(changeIcon);
+
+				if (change.user.username.equals(change.user.emailAddress) && change.user.emailAddress.indexOf('@') > -1) {
+					// username is an email address - 1.2.1 push log bug
+					changeItem.add(new Label("whoChanged", change.user.getDisplayName()));
+				} else if (change.user.username.equals(UserModel.ANONYMOUS.username)) {
+					// anonymous change
+					changeItem.add(new Label("whoChanged", getString("gb.anonymousUser")));
+				} else {
+					// link to user account page
+					changeItem.add(new LinkPanel("whoChanged", null, change.user.getDisplayName(),
+							UserPage.class, WicketUtils.newUsernameParameter(change.user.username)));
+				}
+				
+				boolean isDelete = false;
+				boolean isRewind = false;
+				String what;
+				String by = null;
+				switch(change.getChangeType(fullRefName)) {
+				case CREATE:
+					if (isTag) {
+						// new tag
+						what = getString("gb.pushedNewTag");
+					} else {
+						// new branch
+						what = getString("gb.pushedNewBranch");
+					}
+					break;
+				case DELETE:
+					isDelete = true;
+					if (isTag) {
+						what = getString("gb.deletedTag");
+					} else {
+						what = getString("gb.deletedBranch");
+					}
+					break;
+				case UPDATE_NONFASTFORWARD:
+					isRewind = true;
+				default:
+					what = MessageFormat.format(change.getCommitCount() > 1 ? getString("gb.pushedNCommitsTo") : getString("gb.pushedOneCommitTo") , change.getCommitCount());
+					
+					if (change.getAuthorCount() == 1) {
+						by = MessageFormat.format(getString("gb.byOneAuthor"), change.getAuthorIdent().getName());
+					} else {
+						by = MessageFormat.format(getString("gb.byNAuthors"), change.getAuthorCount());	
+					}
+					break;
+				}
+				changeItem.add(new Label("whatChanged", what));
+				changeItem.add(new Label("byAuthors", by).setVisible(!StringUtils.isEmpty(by)));
+				
+				changeItem.add(new Label("refRewind", getString("gb.rewind")).setVisible(isRewind));
+				
+				if (isDelete) {
+					// can't link to deleted ref
+					changeItem.add(new Label("refChanged", shortRefName));
+				} else if (isTag) {
+					// link to tag
+					changeItem.add(new LinkPanel("refChanged", null, shortRefName,
+							TagPage.class, WicketUtils.newObjectParameter(change.repository, fullRefName)));
+				} else {
+					// link to tree
+					changeItem.add(new LinkPanel("refChanged", null, shortRefName,
+						TreePage.class, WicketUtils.newObjectParameter(change.repository, fullRefName)));
+				}
+				
+				int maxCommitCount = 5;
+				List<RepositoryCommit> commits = change.getCommits();
+				if (commits.size() > maxCommitCount) {
+					commits = new ArrayList<RepositoryCommit>(commits.subList(0,  maxCommitCount));					
+				}
+				
+				// compare link
+				String compareLinkText = null;
+				if ((change.getCommitCount() <= maxCommitCount) && (change.getCommitCount() > 1)) {
+					compareLinkText = MessageFormat.format(getString("gb.viewComparison"), commits.size());
+				} else if (change.getCommitCount() > maxCommitCount) {
+					int diff = change.getCommitCount() - maxCommitCount;
+					compareLinkText = MessageFormat.format(diff > 1 ? getString("gb.nMoreCommits") : getString("gb.oneMoreCommit"), diff);
+				}
+				if (StringUtils.isEmpty(compareLinkText)) {
+					changeItem.add(new Label("compareLink").setVisible(false));
+				} else {
+					String endRangeId = change.getNewId(fullRefName);
+					String startRangeId = change.getOldId(fullRefName);
+					changeItem.add(new LinkPanel("compareLink", null, compareLinkText, ComparePage.class, WicketUtils.newRangeParameter(change.repository, startRangeId, endRangeId)));
+				}
+				
+				ListDataProvider<RepositoryCommit> cdp = new ListDataProvider<RepositoryCommit>(commits);
+				DataView<RepositoryCommit> commitsView = new DataView<RepositoryCommit>("commit", cdp) {
+					private static final long serialVersionUID = 1L;
+
+					public void populateItem(final Item<RepositoryCommit> commitItem) {
+						final RepositoryCommit commit = commitItem.getModelObject();
+
+						// author gravatar
+						commitItem.add(new GravatarImage("commitAuthor", commit.getAuthorIdent().getName(),
+								commit.getAuthorIdent().getEmailAddress(), null, 16, false, false));
+						
+						// merge icon
+						if (commit.getParentCount() > 1) {
+							commitItem.add(WicketUtils.newImage("commitIcon", "commit_merge_16x16.png"));
+						} else {
+							commitItem.add(WicketUtils.newBlankImage("commitIcon"));
+						}
+
+						// short message
+						String shortMessage = commit.getShortMessage();
+						String trimmedMessage = shortMessage;
+						if (commit.getRefs() != null && commit.getRefs().size() > 0) {
+							trimmedMessage = StringUtils.trimString(shortMessage, Constants.LEN_SHORTLOG_REFS);
+						} else {
+							trimmedMessage = StringUtils.trimString(shortMessage, Constants.LEN_SHORTLOG);
+						}
+						LinkPanel shortlog = new LinkPanel("commitShortMessage", "list",
+								trimmedMessage, CommitPage.class, WicketUtils.newObjectParameter(
+										change.repository, commit.getName()));
+						if (!shortMessage.equals(trimmedMessage)) {
+							WicketUtils.setHtmlTooltip(shortlog, shortMessage);
+						}
+						commitItem.add(shortlog);
+
+						// commit hash link
+						LinkPanel commitHash = new LinkPanel("hashLink", null, commit.getName().substring(0, hashLen),
+								CommitPage.class, WicketUtils.newObjectParameter(
+										change.repository, commit.getName()));
+						WicketUtils.setCssClass(commitHash, "shortsha1");
+						WicketUtils.setHtmlTooltip(commitHash, commit.getName());
+						commitItem.add(commitHash);
+					}
+				};
+
+				changeItem.add(commitsView);
+			}
+		};
+		
+		add(changeView);
+	}
+
+	public boolean hasMore() {
+		return hasMore;
+	}
+	
+	public boolean hideIfEmpty() {
+		setVisible(hasChanges);
+		return hasChanges;
+	}
+}
diff --git a/src/com/gitblit/wicket/panels/RefsPanel.html b/src/main/java/com/gitblit/wicket/panels/RefsPanel.html
similarity index 100%
rename from src/com/gitblit/wicket/panels/RefsPanel.html
rename to src/main/java/com/gitblit/wicket/panels/RefsPanel.html
diff --git a/src/main/java/com/gitblit/wicket/panels/RefsPanel.java b/src/main/java/com/gitblit/wicket/panels/RefsPanel.java
new file mode 100644
index 0000000..e477b65
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/panels/RefsPanel.java
@@ -0,0 +1,179 @@
+/*
+ * 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.wicket.panels;
+
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.wicket.Component;
+import org.apache.wicket.markup.html.WebPage;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.panel.Panel;
+import org.apache.wicket.markup.repeater.Item;
+import org.apache.wicket.markup.repeater.data.DataView;
+import org.apache.wicket.markup.repeater.data.ListDataProvider;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.revwalk.RevCommit;
+
+import com.gitblit.Constants;
+import com.gitblit.models.RefModel;
+import com.gitblit.utils.StringUtils;
+import com.gitblit.wicket.WicketUtils;
+import com.gitblit.wicket.pages.CommitPage;
+import com.gitblit.wicket.pages.LogPage;
+import com.gitblit.wicket.pages.TagPage;
+
+public class RefsPanel extends Panel {
+
+	private static final long serialVersionUID = 1L;
+	
+	public RefsPanel(String id, final String repositoryName, RevCommit c,
+			Map<ObjectId, List<RefModel>> refs) {
+		this(id, repositoryName, refs.get(c.getId()));
+	}
+
+	public RefsPanel(String id, final String repositoryName, List<RefModel> refs) {
+		super(id);
+		if (refs == null) {
+			refs = new ArrayList<RefModel>();
+		}
+		Collections.sort(refs, new Comparator<RefModel>() {
+			@Override
+			public int compare(RefModel o1, RefModel o2) {
+				// sort remote heads last, otherwise sort by name
+				// this is so we can insert a break on the refs panel
+				// [head][branch][branch][tag][tag]
+				// [remote][remote][remote]
+				boolean remote1 = o1.displayName.startsWith(Constants.R_REMOTES);
+				boolean remote2 = o2.displayName.startsWith(Constants.R_REMOTES);
+				if (remote1 && remote2) {
+					// both are remote heads, sort by name
+					return o1.displayName.compareTo(o2.displayName);	
+				}
+				if (remote1) {
+					// o1 is remote, o2 comes first
+					return 1;
+				}
+				if (remote2) {
+					// remote is o2, o1 comes first
+					return -1;
+				}
+				// standard sort
+				return o1.displayName.compareTo(o2.displayName);
+			}
+		});
+		
+		// count remote and determine if we should insert a break
+		int remoteCount = 0;
+		for (RefModel ref : refs) {
+			if (ref.displayName.startsWith(Constants.R_REMOTES)) {
+				remoteCount++;
+			}
+		}
+		final boolean shouldBreak = remoteCount < refs.size();
+		
+		ListDataProvider<RefModel> refsDp = new ListDataProvider<RefModel>(refs);
+		DataView<RefModel> refsView = new DataView<RefModel>("ref", refsDp) {
+			private static final long serialVersionUID = 1L;
+			private boolean alreadyInsertedBreak = !shouldBreak;
+
+			public void populateItem(final Item<RefModel> item) {
+				RefModel entry = item.getModelObject();
+				String name = entry.displayName;
+				String objectid = entry.getReferencedObjectId().getName();
+				boolean breakLine = false;
+				Class<? extends WebPage> linkClass = CommitPage.class;
+				String cssClass = "";
+				String tooltip = "";
+				if (name.startsWith(Constants.R_HEADS)) {
+					// local branch
+					linkClass = LogPage.class;
+					name = name.substring(Constants.R_HEADS.length());
+					cssClass = "localBranch";
+				} else if (name.equals(Constants.HEAD)) {
+					// local head
+					linkClass = LogPage.class;
+					cssClass = "headRef";
+				} else if (name.startsWith(Constants.R_CHANGES)) {
+					// Gerrit change ref
+					name = name.substring(Constants.R_CHANGES.length());
+					// strip leading nn/ from nn/#####nn/ps = #####nn-ps
+					name = name.substring(name.indexOf('/') + 1).replace('/', '-');
+					String [] values = name.split("-");
+					tooltip = MessageFormat.format(getString("gb.reviewPatchset"), values[0], values[1]);
+					cssClass = "otherRef";
+				} else if (name.startsWith(Constants.R_PULL)) {
+					// Pull Request ref
+					name = "pull #" + name.substring(Constants.R_PULL.length());
+					if (name.endsWith("/head")) {
+						// strip pull request head from name 
+						name = name.substring(0, name.length() - "/head".length());
+					}					
+					cssClass = "pullRef";
+				} else if (name.startsWith(Constants.R_REMOTES)) {
+					// remote branch
+					linkClass = LogPage.class;
+					name = name.substring(Constants.R_REMOTES.length());
+					cssClass = "remoteBranch";
+					if (!alreadyInsertedBreak) {
+						breakLine = true;
+						alreadyInsertedBreak = true;
+					}
+				} else if (name.startsWith(Constants.R_TAGS)) {
+					// tag
+					if (entry.isAnnotatedTag()) {
+						linkClass = TagPage.class;
+						objectid = entry.getObjectId().getName();
+					} else {
+						linkClass = CommitPage.class;
+						objectid = entry.getReferencedObjectId().getName();
+					}
+					name = name.substring(Constants.R_TAGS.length());
+					cssClass = "tagRef";
+				} else if (name.startsWith(Constants.R_NOTES)) {
+					// codereview refs
+					linkClass = CommitPage.class;
+					cssClass = "otherRef";
+				} else if (name.startsWith(com.gitblit.Constants.R_GITBLIT)) {
+					// gitblit refs
+					linkClass = LogPage.class;
+					cssClass = "otherRef";
+					name = name.substring(com.gitblit.Constants.R_GITBLIT.length());
+				}
+
+				Component c = new LinkPanel("refName", null, name, linkClass,
+						WicketUtils.newObjectParameter(repositoryName, objectid));
+				WicketUtils.setCssClass(c, cssClass);
+				if (StringUtils.isEmpty(tooltip)) {
+					WicketUtils.setHtmlTooltip(c, name);
+				} else {
+					WicketUtils.setHtmlTooltip(c, tooltip);
+				}
+				item.add(c);
+				Label lb = new Label("lineBreak", "<br/>");
+				lb.setVisible(breakLine);
+				lb.setRenderBodyOnly(true);
+				item.add(lb.setEscapeModelStrings(false));
+				item.setRenderBodyOnly(true);
+			}
+		};
+		add(refsView);
+	}
+}
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/panels/RegistrantPermissionsPanel.html b/src/main/java/com/gitblit/wicket/panels/RegistrantPermissionsPanel.html
similarity index 100%
rename from src/com/gitblit/wicket/panels/RegistrantPermissionsPanel.html
rename to src/main/java/com/gitblit/wicket/panels/RegistrantPermissionsPanel.html
diff --git a/src/com/gitblit/wicket/panels/RegistrantPermissionsPanel.java b/src/main/java/com/gitblit/wicket/panels/RegistrantPermissionsPanel.java
similarity index 100%
rename from src/com/gitblit/wicket/panels/RegistrantPermissionsPanel.java
rename to src/main/java/com/gitblit/wicket/panels/RegistrantPermissionsPanel.java
diff --git a/src/com/gitblit/wicket/panels/RepositoriesPanel.html b/src/main/java/com/gitblit/wicket/panels/RepositoriesPanel.html
similarity index 100%
rename from src/com/gitblit/wicket/panels/RepositoriesPanel.html
rename to src/main/java/com/gitblit/wicket/panels/RepositoriesPanel.html
diff --git a/src/main/java/com/gitblit/wicket/panels/RepositoriesPanel.java b/src/main/java/com/gitblit/wicket/panels/RepositoriesPanel.java
new file mode 100644
index 0000000..1b7d0e0
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/panels/RepositoriesPanel.java
@@ -0,0 +1,560 @@
+/*
+ * 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.wicket.panels;
+
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.wicket.Component;
+import org.apache.wicket.PageParameters;
+import org.apache.wicket.extensions.markup.html.repeater.data.sort.OrderByBorder;
+import org.apache.wicket.extensions.markup.html.repeater.util.SortParam;
+import org.apache.wicket.extensions.markup.html.repeater.util.SortableDataProvider;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.link.BookmarkablePageLink;
+import org.apache.wicket.markup.html.link.ExternalLink;
+import org.apache.wicket.markup.html.link.Link;
+import org.apache.wicket.markup.html.panel.Fragment;
+import org.apache.wicket.markup.repeater.Item;
+import org.apache.wicket.markup.repeater.data.DataView;
+import org.apache.wicket.markup.repeater.data.IDataProvider;
+import org.apache.wicket.markup.repeater.data.ListDataProvider;
+import org.apache.wicket.model.IModel;
+import org.apache.wicket.model.Model;
+
+import com.gitblit.Constants.AccessRestrictionType;
+import com.gitblit.GitBlit;
+import com.gitblit.Keys;
+import com.gitblit.SyndicationServlet;
+import com.gitblit.models.ProjectModel;
+import com.gitblit.models.RepositoryModel;
+import com.gitblit.models.UserModel;
+import com.gitblit.utils.ArrayUtils;
+import com.gitblit.utils.StringUtils;
+import com.gitblit.wicket.GitBlitWebSession;
+import com.gitblit.wicket.WicketUtils;
+import com.gitblit.wicket.pages.BasePage;
+import com.gitblit.wicket.pages.EditRepositoryPage;
+import com.gitblit.wicket.pages.EmptyRepositoryPage;
+import com.gitblit.wicket.pages.ProjectPage;
+import com.gitblit.wicket.pages.RepositoriesPage;
+import com.gitblit.wicket.pages.SummaryPage;
+import com.gitblit.wicket.pages.UserPage;
+
+public class RepositoriesPanel extends BasePanel {
+
+	private static final long serialVersionUID = 1L;
+
+	public RepositoriesPanel(String wicketId, final boolean showAdmin, final boolean showManagement,
+			List<RepositoryModel> models, boolean enableLinks,
+			final Map<AccessRestrictionType, String> accessRestrictionTranslations) {
+		super(wicketId);
+
+		final boolean linksActive = enableLinks;
+		final boolean showSize = GitBlit.getBoolean(Keys.web.showRepositorySizes, true);
+
+		final UserModel user = GitBlitWebSession.get().getUser();
+
+		final IDataProvider<RepositoryModel> dp;
+
+		Fragment managementLinks;
+		if (showAdmin) {
+			// user is admin
+			managementLinks = new Fragment("managementPanel", "adminLinks", this);
+			managementLinks.add(new Link<Void>("clearCache") {
+
+				private static final long serialVersionUID = 1L;
+
+				@Override
+				public void onClick() {
+					GitBlit.self().resetRepositoryListCache();
+					setResponsePage(RepositoriesPage.class);
+				}
+			}.setVisible(GitBlit.getBoolean(Keys.git.cacheRepositoryList, true)));
+			managementLinks.add(new BookmarkablePageLink<Void>("newRepository", EditRepositoryPage.class));
+			add(managementLinks);
+		} else if (showManagement && user != null && user.canCreate()) {
+			// user can create personal repositories
+			managementLinks = new Fragment("managementPanel", "personalLinks", this);
+			managementLinks.add(new BookmarkablePageLink<Void>("newRepository", EditRepositoryPage.class));
+			add(managementLinks);
+		} else {
+			// user has no management permissions
+			add (new Label("managementPanel").setVisible(false));
+		}
+
+		if (GitBlit.getString(Keys.web.repositoryListType, "flat").equalsIgnoreCase("grouped")) {
+			List<RepositoryModel> rootRepositories = new ArrayList<RepositoryModel>();
+			Map<String, List<RepositoryModel>> groups = new HashMap<String, List<RepositoryModel>>();
+			for (RepositoryModel model : models) {
+				String rootPath = StringUtils.getRootPath(model.name);
+				if (StringUtils.isEmpty(rootPath)) {
+					// root repository
+					rootRepositories.add(model);
+				} else {
+					// non-root, grouped repository
+					if (!groups.containsKey(rootPath)) {
+						groups.put(rootPath, new ArrayList<RepositoryModel>());
+					}
+					groups.get(rootPath).add(model);
+				}
+			}
+			List<String> roots = new ArrayList<String>(groups.keySet());
+			Collections.sort(roots);
+
+			if (rootRepositories.size() > 0) {
+				// inject the root repositories at the top of the page
+				roots.add(0, "");
+				groups.put("", rootRepositories);
+			}
+						
+			List<RepositoryModel> groupedModels = new ArrayList<RepositoryModel>();
+			for (String root : roots) {
+				List<RepositoryModel> subModels = groups.get(root);
+				ProjectModel project = GitBlit.self().getProjectModel(root);
+				GroupRepositoryModel group = new GroupRepositoryModel(project == null ? root : project.name, subModels.size());
+				if (project != null) {
+					group.title = project.title;
+					group.description = project.description;
+				}
+				groupedModels.add(group);
+				Collections.sort(subModels);
+				groupedModels.addAll(subModels);
+			}
+			dp = new RepositoriesProvider(groupedModels);
+		} else {
+			dp = new SortableRepositoriesProvider(models);
+		}
+
+		final String baseUrl = WicketUtils.getGitblitURL(getRequest());
+		final boolean showSwatch = GitBlit.getBoolean(Keys.web.repositoryListSwatches, true);
+		
+		DataView<RepositoryModel> dataView = new DataView<RepositoryModel>("row", dp) {
+			private static final long serialVersionUID = 1L;
+			int counter;
+			String currGroupName;
+
+			@Override
+			protected void onBeforeRender() {
+				super.onBeforeRender();
+				counter = 0;
+			}
+
+			public void populateItem(final Item<RepositoryModel> item) {
+				final RepositoryModel entry = item.getModelObject();
+				if (entry instanceof GroupRepositoryModel) {
+					GroupRepositoryModel groupRow = (GroupRepositoryModel) entry;
+					currGroupName = entry.name;
+					Fragment row = new Fragment("rowContent", "groupRepositoryRow", this);
+					item.add(row);
+					
+					String name = groupRow.name;
+					if (name.charAt(0) == '~') {
+						// user page
+						String username = name.substring(1);
+						UserModel user = GitBlit.self().getUserModel(username);
+						row.add(new LinkPanel("groupName", null, (user == null ? username : user.getDisplayName()) + " (" + groupRow.count + ")", UserPage.class, WicketUtils.newUsernameParameter(username)));
+						row.add(new Label("groupDescription", getString("gb.personalRepositories")));
+					} else {
+						// project page
+						row.add(new LinkPanel("groupName", null, groupRow.toString(), ProjectPage.class, WicketUtils.newProjectParameter(entry.name)));
+						row.add(new Label("groupDescription", entry.description == null ? "":entry.description));
+					}
+					WicketUtils.setCssClass(item, "group");
+					// reset counter so that first row is light background
+					counter = 0;
+					return;
+				}
+				Fragment row = new Fragment("rowContent", "repositoryRow", this);
+				item.add(row);
+
+				// try to strip group name for less cluttered list
+				String repoName = entry.toString();
+				if (!StringUtils.isEmpty(currGroupName) && (repoName.indexOf('/') > -1)) {
+					repoName = repoName.substring(currGroupName.length() + 1);
+				}
+								
+				// repository swatch
+				Component swatch;
+				if (entry.isBare){
+					swatch = new Label("repositorySwatch", "&nbsp;").setEscapeModelStrings(false);
+				} else {
+					swatch = new Label("repositorySwatch", "!");
+					WicketUtils.setHtmlTooltip(swatch, getString("gb.workingCopyWarning"));
+				}
+				WicketUtils.setCssBackground(swatch, entry.toString());
+				row.add(swatch);
+				swatch.setVisible(showSwatch);
+
+				if (linksActive) {
+					Class<? extends BasePage> linkPage;
+					if (entry.hasCommits) {
+						// repository has content
+						linkPage = SummaryPage.class;
+					} else {
+						// new/empty repository OR proposed repository
+						linkPage = EmptyRepositoryPage.class;
+					}
+
+					PageParameters pp = WicketUtils.newRepositoryParameter(entry.name);
+					row.add(new LinkPanel("repositoryName", "list", repoName, linkPage, pp));
+					row.add(new LinkPanel("repositoryDescription", "list", entry.description,
+							linkPage, pp));
+				} else {
+					// no links like on a federation page
+					row.add(new Label("repositoryName", repoName));
+					row.add(new Label("repositoryDescription", entry.description));
+				}
+				if (entry.hasCommits) {
+					// Existing repository
+					row.add(new Label("repositorySize", entry.size).setVisible(showSize));
+				} else {
+					// New repository
+					row.add(new Label("repositorySize", "<span class='empty'>(" + getString("gb.empty") + ")</span>")
+							.setEscapeModelStrings(false));
+				}
+
+				if (entry.isSparkleshared()) {
+					row.add(WicketUtils.newImage("sparkleshareIcon", "star_16x16.png",
+							getString("gb.isSparkleshared")));
+				} else {
+					row.add(WicketUtils.newClearPixel("sparkleshareIcon").setVisible(false));
+				}
+				
+				if (entry.isFork()) {
+					row.add(WicketUtils.newImage("forkIcon", "commit_divide_16x16.png",
+							getString("gb.isFork")));
+				} else {
+					row.add(WicketUtils.newClearPixel("forkIcon").setVisible(false));
+				}
+
+				if (entry.useTickets) {
+					row.add(WicketUtils.newImage("ticketsIcon", "bug_16x16.png",
+							getString("gb.tickets")));
+				} else {
+					row.add(WicketUtils.newBlankImage("ticketsIcon"));
+				}
+
+				if (entry.useDocs) {
+					row.add(WicketUtils
+							.newImage("docsIcon", "book_16x16.png", getString("gb.docs")));
+				} else {
+					row.add(WicketUtils.newBlankImage("docsIcon"));
+				}
+
+				if (entry.isFrozen) {
+					row.add(WicketUtils.newImage("frozenIcon", "cold_16x16.png",
+							getString("gb.isFrozen")));
+				} else {
+					row.add(WicketUtils.newClearPixel("frozenIcon").setVisible(false));
+				}
+
+				if (entry.isFederated) {
+					row.add(WicketUtils.newImage("federatedIcon", "federated_16x16.png",
+							getString("gb.isFederated")));
+				} else {
+					row.add(WicketUtils.newClearPixel("federatedIcon").setVisible(false));
+				}
+				switch (entry.accessRestriction) {
+				case NONE:
+					row.add(WicketUtils.newBlankImage("accessRestrictionIcon"));
+					break;
+				case PUSH:
+					row.add(WicketUtils.newImage("accessRestrictionIcon", "lock_go_16x16.png",
+							accessRestrictionTranslations.get(entry.accessRestriction)));
+					break;
+				case CLONE:
+					row.add(WicketUtils.newImage("accessRestrictionIcon", "lock_pull_16x16.png",
+							accessRestrictionTranslations.get(entry.accessRestriction)));
+					break;
+				case VIEW:
+					row.add(WicketUtils.newImage("accessRestrictionIcon", "shield_16x16.png",
+							accessRestrictionTranslations.get(entry.accessRestriction)));
+					break;
+				default:
+					row.add(WicketUtils.newBlankImage("accessRestrictionIcon"));
+				}
+
+				String owner = "";
+				if (!ArrayUtils.isEmpty(entry.owners)) {
+					// display first owner
+					for (String username : entry.owners) {
+						UserModel ownerModel = GitBlit.self().getUserModel(username);
+						if (ownerModel != null) {
+							owner = ownerModel.getDisplayName();
+							break;
+						}
+					}
+					if (entry.owners.size() > 1) {
+						owner += ", ...";
+					}
+				}
+				Label ownerLabel = new Label("repositoryOwner", owner);
+				WicketUtils.setHtmlTooltip(ownerLabel, ArrayUtils.toString(entry.owners));
+				row.add(ownerLabel);
+
+				String lastChange;
+				if (entry.lastChange.getTime() == 0) {
+					lastChange = "--";
+				} else {
+					lastChange = getTimeUtils().timeAgo(entry.lastChange);
+				}
+				Label lastChangeLabel = new Label("repositoryLastChange", lastChange);
+				row.add(lastChangeLabel);
+				WicketUtils.setCssClass(lastChangeLabel, getTimeUtils().timeAgoCss(entry.lastChange));
+				if (!StringUtils.isEmpty(entry.lastChangeAuthor)) {
+					WicketUtils.setHtmlTooltip(lastChangeLabel, getString("gb.author") + ": " + entry.lastChangeAuthor);
+				}
+
+				boolean showOwner = user != null && entry.isOwner(user.username);
+				boolean myPersonalRepository = showOwner && entry.isUsersPersonalRepository(user.username);
+				if (showAdmin || myPersonalRepository) {
+					Fragment repositoryLinks = new Fragment("repositoryLinks",
+							"repositoryAdminLinks", this);
+					repositoryLinks.add(new BookmarkablePageLink<Void>("editRepository",
+							EditRepositoryPage.class, WicketUtils
+									.newRepositoryParameter(entry.name)));
+					Link<Void> deleteLink = new Link<Void>("deleteRepository") {
+
+						private static final long serialVersionUID = 1L;
+
+						@Override
+						public void onClick() {
+							if (GitBlit.self().deleteRepositoryModel(entry)) {
+								if (dp instanceof SortableRepositoriesProvider) {
+									info(MessageFormat.format(getString("gb.repositoryDeleted"), entry));
+									((SortableRepositoriesProvider) dp).remove(entry);
+								} else {
+									setResponsePage(getPage().getClass(), getPage().getPageParameters());
+								}
+							} else {
+								error(MessageFormat.format(getString("gb.repositoryDeleteFailed"), entry));
+							}
+						}
+					};
+					deleteLink.add(new JavascriptEventConfirmation("onclick", MessageFormat.format(
+							getString("gb.deleteRepository"), entry)));
+					repositoryLinks.add(deleteLink);
+					row.add(repositoryLinks);
+				} else if (showOwner) {
+					Fragment repositoryLinks = new Fragment("repositoryLinks",
+							"repositoryOwnerLinks", this);
+					repositoryLinks.add(new BookmarkablePageLink<Void>("editRepository",
+							EditRepositoryPage.class, WicketUtils
+									.newRepositoryParameter(entry.name)));
+					row.add(repositoryLinks);
+				} else {
+					row.add(new Label("repositoryLinks"));
+				}
+				row.add(new ExternalLink("syndication", SyndicationServlet.asLink(baseUrl,
+						entry.name, null, 0)).setVisible(linksActive));
+				WicketUtils.setAlternatingBackground(item, counter);
+				counter++;
+			}
+		};
+		add(dataView);
+
+		if (dp instanceof SortableDataProvider<?>) {
+			// add sortable header
+			SortableDataProvider<?> sdp = (SortableDataProvider<?>) dp;
+			Fragment fragment = new Fragment("headerContent", "flatRepositoryHeader", this);
+			fragment.add(newSort("orderByRepository", SortBy.repository, sdp, dataView));
+			fragment.add(newSort("orderByDescription", SortBy.description, sdp, dataView));
+			fragment.add(newSort("orderByOwner", SortBy.owner, sdp, dataView));
+			fragment.add(newSort("orderByDate", SortBy.date, sdp, dataView));
+			add(fragment);
+		} else {
+			// not sortable
+			Fragment fragment = new Fragment("headerContent", "groupRepositoryHeader", this);
+			add(fragment);
+		}
+	}
+
+	private static class GroupRepositoryModel extends RepositoryModel {
+
+		private static final long serialVersionUID = 1L;
+
+		int count;
+		String title;
+
+		GroupRepositoryModel(String name, int count) {
+			super(name, "", "", new Date(0));
+			this.count = count;
+		}
+
+		@Override
+		public String toString() {
+			return (StringUtils.isEmpty(title) ? name  : title) + " (" + count + ")";
+		}
+	}
+
+	protected enum SortBy {
+		repository, description, owner, date;
+	}
+
+	protected OrderByBorder newSort(String wicketId, SortBy field, SortableDataProvider<?> dp,
+			final DataView<?> dataView) {
+		return new OrderByBorder(wicketId, field.name(), dp) {
+			private static final long serialVersionUID = 1L;
+
+			@Override
+			protected void onSortChanged() {
+				dataView.setCurrentPage(0);
+			}
+		};
+	}
+
+	private static class RepositoriesProvider extends ListDataProvider<RepositoryModel> {
+
+		private static final long serialVersionUID = 1L;
+
+		public RepositoriesProvider(List<RepositoryModel> list) {
+			super(list);
+		}
+
+		@Override
+		public List<RepositoryModel> getData() {
+			return super.getData();
+		}
+
+		public void remove(RepositoryModel model) {
+			int index = getData().indexOf(model);
+			RepositoryModel groupModel = null;
+			if (index == (getData().size() - 1)) {
+				// last element
+				if (index > 0) {
+					// previous element is group header, then this is last
+					// repository in group. remove group too.
+					if (getData().get(index - 1) instanceof GroupRepositoryModel) {
+						groupModel = getData().get(index - 1);
+					}
+				}
+			} else if (index < (getData().size() - 1)) {
+				// not last element. check next element for group match.
+				if (getData().get(index - 1) instanceof GroupRepositoryModel
+						&& getData().get(index + 1) instanceof GroupRepositoryModel) {
+					// repository is sandwiched by group headers so this
+					// repository is the only element in the group. remove
+					// group.
+					groupModel = getData().get(index - 1);
+				}
+			}
+
+			if (groupModel == null) {
+				// Find the group and decrement the count
+				for (int i = index; i >= 0; i--) {
+					if (getData().get(i) instanceof GroupRepositoryModel) {
+						((GroupRepositoryModel) getData().get(i)).count--;
+						break;
+					}
+				}
+			} else {
+				// Remove the group header
+				getData().remove(groupModel);
+			}
+
+			getData().remove(model);
+		}
+	}
+
+	private static class SortableRepositoriesProvider extends SortableDataProvider<RepositoryModel> {
+
+		private static final long serialVersionUID = 1L;
+
+		private List<RepositoryModel> list;
+
+		protected SortableRepositoriesProvider(List<RepositoryModel> list) {
+			this.list = list;
+			setSort(SortBy.date.name(), false);
+		}
+
+		public void remove(RepositoryModel model) {
+			list.remove(model);
+		}
+
+		@Override
+		public int size() {
+			if (list == null) {
+				return 0;
+			}
+			return list.size();
+		}
+
+		@Override
+		public IModel<RepositoryModel> model(RepositoryModel header) {
+			return new Model<RepositoryModel>(header);
+		}
+
+		@Override
+		public Iterator<RepositoryModel> iterator(int first, int count) {
+			SortParam sp = getSort();
+			String prop = sp.getProperty();
+			final boolean asc = sp.isAscending();
+
+			if (prop == null || prop.equals(SortBy.date.name())) {
+				Collections.sort(list, new Comparator<RepositoryModel>() {
+					@Override
+					public int compare(RepositoryModel o1, RepositoryModel o2) {
+						if (asc) {
+							return o1.lastChange.compareTo(o2.lastChange);
+						}
+						return o2.lastChange.compareTo(o1.lastChange);
+					}
+				});
+			} else if (prop.equals(SortBy.repository.name())) {
+				Collections.sort(list, new Comparator<RepositoryModel>() {
+					@Override
+					public int compare(RepositoryModel o1, RepositoryModel o2) {
+						if (asc) {
+							return o1.name.compareTo(o2.name);
+						}
+						return o2.name.compareTo(o1.name);
+					}
+				});
+			} else if (prop.equals(SortBy.owner.name())) {
+				Collections.sort(list, new Comparator<RepositoryModel>() {
+					@Override
+					public int compare(RepositoryModel o1, RepositoryModel o2) {
+						String own1 = ArrayUtils.toString(o1.owners);
+						String own2 = ArrayUtils.toString(o2.owners);
+						if (asc) {
+							return own1.compareTo(own2);
+						}
+						return own2.compareTo(own1);
+					}
+				});
+			} else if (prop.equals(SortBy.description.name())) {
+				Collections.sort(list, new Comparator<RepositoryModel>() {
+					@Override
+					public int compare(RepositoryModel o1, RepositoryModel o2) {
+						if (asc) {
+							return o1.description.compareTo(o2.description);
+						}
+						return o2.description.compareTo(o1.description);
+					}
+				});
+			}
+			return list.subList(first, first + count).iterator();
+		}
+	}
+}
diff --git a/src/main/java/com/gitblit/wicket/panels/RepositoryUrlPanel.html b/src/main/java/com/gitblit/wicket/panels/RepositoryUrlPanel.html
new file mode 100644
index 0000000..4b28e71
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/panels/RepositoryUrlPanel.html
@@ -0,0 +1,114 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"  
+      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"  
+      xml:lang="en"  
+      lang="en"> 
+
+<wicket:panel>
+
+	<div wicket:id="repositoryUrlPanel"></div>
+	<div wicket:id="applicationMenusPanel"></div>
+	<div wicket:id="repositoryIndicators"></div>
+	
+	<wicket:fragment wicket:id="repositoryUrlFragment">
+		<div class="btn-toolbar" style="margin: 0px;">
+			<div class="btn-group repositoryUrlContainer">
+				<img style="vertical-align: middle;padding: 0px 0px 1px 3px;" wicket:id="accessRestrictionIcon"></img>
+				<span wicket:id="menu"></span>
+   				<div class="repositoryUrl">
+   					<span wicket:id="primaryUrl">[repository primary url]</span>
+   					<span class="hidden-phone hidden-tablet" wicket:id="copyFunction"></span>
+   				</div>
+   				<span class="hidden-phone hidden-tablet repositoryUrlRightCap" wicket:id="primaryUrlPermission">[repository primary url permission]</span>
+   			</div>
+		</div>
+	</wicket:fragment>
+	
+	<wicket:fragment wicket:id="indicatorsFragment">
+		<div>
+	   		<div wicket:id="workingCopyIndicator"></div>
+			<div wicket:id="forksProhibitedIndicator"></div>
+		</div>
+	</wicket:fragment>
+	
+	<wicket:fragment wicket:id="applicationMenusFragment">
+		<div class="btn-toolbar" style="margin: 4px 0px 0px 0px;">
+			<div class="btn-group" wicket:id="appMenus">
+				<span wicket:id="appMenu"></span>
+   			</div>
+		</div>
+	</wicket:fragment>
+	
+	<wicket:fragment wicket:id="appMenuFragment">
+		<a class="btn btn-mini btn-appmenu" data-toggle="dropdown" href="#">   				
+	    	<span wicket:id="applicationName"></span>
+    		<span class="caret"></span>
+   		</a>
+   		<ul class="dropdown-menu applicationMenu">
+   			<li>
+   				<div class="applicationHeaderMenuItem">
+   					<div style="float:right">
+   						<img style="padding-right: 5px;vertical-align: middle;" wicket:id="applicationIcon"></img>
+   					</div>
+   					<span class="applicationTitle" wicket:id="applicationTitle"></span>
+   				</div>
+   			</li>
+	   		<li><div class="applicationHeaderMenuItem"><span wicket:id="applicationDescription"></span></div></li>
+   			<li><div class="applicationLegalMenuItem"><span wicket:id="applicationLegal"></span></div></li>
+   			
+   			<li class="divider" style="margin: 5px 1px 0px 1px;clear:both;" ></li>
+   		
+   			<li class="action" wicket:id="actionItems">
+   				<span wicket:id="actionItem"></span>
+   			</li>
+   		</ul>
+	</wicket:fragment>
+	
+	<wicket:fragment wicket:id="urlProtocolMenuFragment">
+		<a class="" data-toggle="dropdown" href="#">   				
+    		<span class="repositoryUrlLeftCap" wicket:id="menuText">URLs</span>
+	   		<span class="caret" style="vertical-align: middle;"></span>
+   		</a>
+   		<ul class="dropdown-menu urlMenu">
+   			<li class="url" wicket:id="repoUrls"><span wicket:id="repoUrl"></span></li>
+	   	</ul>
+	</wicket:fragment>
+	
+	<wicket:fragment wicket:id="actionFragment">
+		<span wicket:id="permission" style="margin: 0px 10px 0px 5px;"></span><span wicket:id="content"></span><span class="hidden-phone hidden-tablet" wicket:id="copyFunction"></span>
+	</wicket:fragment>
+
+    <!-- Plain JavaScript manual copy & paste -->
+    <wicket:fragment wicket:id="jsPanel">
+    	<span style="vertical-align:baseline;">
+    		<img wicket:id="copyIcon" wicket:message="title:gb.copyToClipboard"></img>
+    	</span>
+    </wicket:fragment>
+    
+    <!-- flash-based button-press copy & paste -->
+    <wicket:fragment wicket:id="clippyPanel">
+   		<object wicket:message="title:gb.copyToClipboard" style="vertical-align:middle;"
+   			wicket:id="clippy"
+   			width="14" 
+   			height="14"
+   			bgcolor="#ffffff" 
+       		quality="high"
+       		wmode="transparent"
+       		scale="noscale"
+       		allowScriptAccess="always"></object>
+	</wicket:fragment>
+
+	<wicket:fragment wicket:id="workingCopyFragment">
+		<div class="repositoryIndicator">
+			<span class="alert alert-info"><i class="icon-exclamation-sign"></i>&nbsp;<span class="hidden-phone" wicket:id="workingCopy">[working copy]</span></span>
+		</div>
+	</wicket:fragment>
+
+	<wicket:fragment wicket:id="forksProhibitedFragment">
+		<div class="repositoryIndicator">
+			<span class="alert alert-error"><i class="icon-ban-circle"></i>&nbsp;<span class="hidden-phone" wicket:id="forksProhibited">[forks prohibited]</span></span>
+		</div>
+	</wicket:fragment>
+			
+</wicket:panel>
+</html>
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/panels/RepositoryUrlPanel.java b/src/main/java/com/gitblit/wicket/panels/RepositoryUrlPanel.java
new file mode 100644
index 0000000..aaab2b1
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/panels/RepositoryUrlPanel.java
@@ -0,0 +1,470 @@
+/*
+ * 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.wicket.panels;
+
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.wicket.Component;
+import org.apache.wicket.RequestCycle;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.image.ContextImage;
+import org.apache.wicket.markup.html.panel.Fragment;
+import org.apache.wicket.markup.repeater.Item;
+import org.apache.wicket.markup.repeater.data.DataView;
+import org.apache.wicket.markup.repeater.data.ListDataProvider;
+import org.apache.wicket.protocol.http.WebRequest;
+import org.apache.wicket.protocol.http.request.WebClientInfo;
+
+import com.gitblit.Constants.AccessPermission;
+import com.gitblit.Constants.AccessRestrictionType;
+import com.gitblit.GitBlit;
+import com.gitblit.Keys;
+import com.gitblit.models.GitClientApplication;
+import com.gitblit.models.RepositoryModel;
+import com.gitblit.models.RepositoryUrl;
+import com.gitblit.models.UserModel;
+import com.gitblit.utils.StringUtils;
+import com.gitblit.wicket.ExternalImage;
+import com.gitblit.wicket.GitBlitWebSession;
+import com.gitblit.wicket.WicketUtils;
+
+/**
+ * Smart repository url panel which can display multiple Gitblit repository urls
+ * and also supports 3rd party app clone links.
+ * 
+ * @author James Moger
+ *
+ */
+public class RepositoryUrlPanel extends BasePanel {
+
+	private static final long serialVersionUID = 1L;
+
+	private final String externalPermission = "?";
+
+	private boolean onlyUrls;
+	private UserModel user; 
+	private RepositoryModel repository;
+	private RepositoryUrl primaryUrl;
+	private Map<String, String> urlPermissionsMap;
+	private Map<AccessRestrictionType, String> accessRestrictionsMap;
+	
+	public RepositoryUrlPanel(String wicketId, boolean onlyUrls, UserModel user, RepositoryModel repository) {
+		super(wicketId);
+		this.onlyUrls = onlyUrls;
+		this.user = user == null ? UserModel.ANONYMOUS : user;
+		this.repository = repository;
+		this.urlPermissionsMap = new HashMap<String, String>();
+	}
+	
+	@Override
+	protected void onInitialize() {
+		super.onInitialize();
+
+		HttpServletRequest req = ((WebRequest) getRequest()).getHttpServletRequest();
+
+		List<RepositoryUrl> repositoryUrls = GitBlit.self().getRepositoryUrls(req, user, repository);
+		// grab primary url from the top of the list
+		primaryUrl = repositoryUrls.size() == 0 ? null : repositoryUrls.get(0);
+
+		boolean canClone = primaryUrl != null && ((primaryUrl.permission == null) || primaryUrl.permission.atLeast(AccessPermission.CLONE));
+
+		if (repositoryUrls.size() == 0 || !canClone) {
+			// no urls, nothing to show.
+			add(new Label("repositoryUrlPanel").setVisible(false));
+			add(new Label("applicationMenusPanel").setVisible(false));
+			add(new Label("repositoryIndicators").setVisible(false));
+			return;
+		}
+		
+		// display primary url
+		add(createPrimaryUrlPanel("repositoryUrlPanel", repository, repositoryUrls));
+
+		if (onlyUrls) {
+			add(new Label("repositoryIndicators").setVisible(false));
+		} else {
+			add(createRepositoryIndicators(repository));
+		}
+
+		boolean allowAppLinks = GitBlit.getBoolean(Keys.web.allowAppCloneLinks, true);
+		if (onlyUrls || !canClone || !allowAppLinks) {
+			// only display the url(s)
+			add(new Label("applicationMenusPanel").setVisible(false));
+			return;
+		}
+		// create the git client application menus
+		add(createApplicationMenus("applicationMenusPanel", user, repository, repositoryUrls));
+	}
+
+	public String getPrimaryUrl() {
+		return primaryUrl == null ? "" : primaryUrl.url;
+	}
+
+	protected Fragment createPrimaryUrlPanel(String wicketId, final RepositoryModel repository, List<RepositoryUrl> repositoryUrls) {
+
+		Fragment urlPanel = new Fragment(wicketId, "repositoryUrlFragment", this);
+		urlPanel.setRenderBodyOnly(true);
+		
+		if (repositoryUrls.size() == 1) {
+			//
+			// Single repository url, no dropdown menu
+			//
+			urlPanel.add(new Label("menu").setVisible(false));
+		} else {
+			//
+			// Multiple repository urls, show url drop down menu
+			//
+			ListDataProvider<RepositoryUrl> urlsDp = new ListDataProvider<RepositoryUrl>(repositoryUrls);
+			DataView<RepositoryUrl> repoUrlMenuItems = new DataView<RepositoryUrl>("repoUrls", urlsDp) {
+				private static final long serialVersionUID = 1L;
+
+				public void populateItem(final Item<RepositoryUrl> item) {
+					RepositoryUrl repoUrl = item.getModelObject();
+					// repository url
+					Fragment fragment = new Fragment("repoUrl", "actionFragment", this);					
+					Component content = new Label("content", repoUrl.url).setRenderBodyOnly(true);
+					WicketUtils.setCssClass(content, "commandMenuItem");
+					fragment.add(content);
+					item.add(fragment);
+					
+					Label permissionLabel = new Label("permission", repoUrl.isExternal() ? externalPermission : repoUrl.permission.toString());
+					WicketUtils.setPermissionClass(permissionLabel, repoUrl.permission);
+					String tooltip = getProtocolPermissionDescription(repository, repoUrl);
+					WicketUtils.setHtmlTooltip(permissionLabel, tooltip);
+					fragment.add(permissionLabel);
+					fragment.add(createCopyFragment(repoUrl.url));
+				}
+			};
+
+			Fragment urlMenuFragment = new Fragment("menu", "urlProtocolMenuFragment", this);
+			urlMenuFragment.setRenderBodyOnly(true);
+			urlMenuFragment.add(new Label("menuText", getString("gb.url")));
+			urlMenuFragment.add(repoUrlMenuItems);
+			urlPanel.add(urlMenuFragment);
+		}
+
+		// access restriction icon and tooltip
+		if (GitBlit.isServingRepositories()) {
+			switch (repository.accessRestriction) {
+			case NONE:
+				urlPanel.add(WicketUtils.newClearPixel("accessRestrictionIcon").setVisible(false));
+				break;
+			case PUSH:
+				urlPanel.add(WicketUtils.newImage("accessRestrictionIcon", "lock_go_16x16.png",
+						getAccessRestrictions().get(repository.accessRestriction)));
+				break;
+			case CLONE:
+				urlPanel.add(WicketUtils.newImage("accessRestrictionIcon", "lock_pull_16x16.png",
+						getAccessRestrictions().get(repository.accessRestriction)));
+				break;
+			case VIEW:
+				urlPanel.add(WicketUtils.newImage("accessRestrictionIcon", "shield_16x16.png",
+						getAccessRestrictions().get(repository.accessRestriction)));
+				break;
+			default:
+				if (repositoryUrls.size() == 1) {
+					// force left end cap to have some width
+					urlPanel.add(WicketUtils.newBlankIcon("accessRestrictionIcon"));
+				} else {
+					urlPanel.add(WicketUtils.newClearPixel("accessRestrictionIcon").setVisible(false));
+				}
+			}
+		} else {
+			if (repositoryUrls.size() == 1) {
+				// force left end cap to have some width
+				urlPanel.add(WicketUtils.newBlankIcon("accessRestrictionIcon"));
+			} else {
+				urlPanel.add(WicketUtils.newClearPixel("accessRestrictionIcon").setVisible(false));
+			}
+		}
+		
+		urlPanel.add(new Label("primaryUrl", primaryUrl.url).setRenderBodyOnly(true));
+
+		Label permissionLabel = new Label("primaryUrlPermission", primaryUrl.isExternal() ? externalPermission : primaryUrl.permission.toString());		
+		String tooltip = getProtocolPermissionDescription(repository, primaryUrl);
+		WicketUtils.setHtmlTooltip(permissionLabel, tooltip);
+		urlPanel.add(permissionLabel);
+		urlPanel.add(createCopyFragment(primaryUrl.url));
+		
+		return urlPanel;
+	}
+	
+	protected Fragment createApplicationMenus(String wicketId, UserModel user, final RepositoryModel repository, final List<RepositoryUrl> repositoryUrls) {
+		final List<GitClientApplication> displayedApps = new ArrayList<GitClientApplication>();
+		final String userAgent = ((WebClientInfo) GitBlitWebSession.get().getClientInfo()).getUserAgent();
+		
+		if (user.canClone(repository)) {
+			for (GitClientApplication app : GitBlit.self().getClientApplications()) {
+				if (app.isActive && app.allowsPlatform(userAgent)) {
+					displayedApps.add(app);
+				}
+			}
+		}
+
+		final String baseURL = WicketUtils.getGitblitURL(RequestCycle.get().getRequest());
+		ListDataProvider<GitClientApplication> displayedAppsDp = new ListDataProvider<GitClientApplication>(displayedApps);
+		DataView<GitClientApplication> appMenus = new DataView<GitClientApplication>("appMenus", displayedAppsDp) {
+			private static final long serialVersionUID = 1L;
+
+			public void populateItem(final Item<GitClientApplication> item) {
+				final GitClientApplication clientApp = item.getModelObject();
+
+				// filter the urls for the client app
+				List<RepositoryUrl> urls = new ArrayList<RepositoryUrl>();
+				for (RepositoryUrl repoUrl : repositoryUrls) {
+					if (clientApp.minimumPermission == null || repoUrl.permission == null) {
+						// no minimum permission or external permissions, assume it is satisfactory
+						if (clientApp.supportsTransport(repoUrl.url)) {
+							urls.add(repoUrl);
+						}
+					} else if (repoUrl.permission.atLeast(clientApp.minimumPermission)) {
+						// repo url meets minimum permission requirement
+						if (clientApp.supportsTransport(repoUrl.url)) {
+							urls.add(repoUrl);
+						}
+					}
+				}
+				
+				if (urls.size() == 0) {
+					// do not show this app menu because there are no urls
+					item.add(new Label("appMenu").setVisible(false));
+					return;
+				}
+				
+				Fragment appMenu = new Fragment("appMenu", "appMenuFragment", this);
+				appMenu.setRenderBodyOnly(true);
+				item.add(appMenu);
+				
+				// menu button
+				appMenu.add(new Label("applicationName", clientApp.name));
+				
+				// application icon
+				Component img;
+				if (StringUtils.isEmpty(clientApp.icon)) {
+					img = WicketUtils.newClearPixel("applicationIcon").setVisible(false);	
+				} else {
+					if (clientApp.icon.contains("://")) {
+						// external image
+						img = new ExternalImage("applicationIcon", clientApp.icon);
+					} else {
+						// context image
+						img = WicketUtils.newImage("applicationIcon", clientApp.icon);
+					}
+				}				
+				appMenu.add(img);
+				
+				// application menu title, may be a link
+				if (StringUtils.isEmpty(clientApp.productUrl)) {
+					appMenu.add(new Label("applicationTitle", clientApp.toString()));
+				} else {
+					appMenu.add(new LinkPanel("applicationTitle", null, clientApp.toString(), clientApp.productUrl, true));
+				}
+				
+				// brief application description
+				if (StringUtils.isEmpty(clientApp.description)) {
+					appMenu.add(new Label("applicationDescription").setVisible(false));
+				} else {
+					appMenu.add(new Label("applicationDescription", clientApp.description));
+				}
+				
+				// brief application legal info, copyright, license, etc
+				if (StringUtils.isEmpty(clientApp.legal)) {
+					appMenu.add(new Label("applicationLegal").setVisible(false));
+				} else {
+					appMenu.add(new Label("applicationLegal", clientApp.legal));
+				}
+				
+				// a nested repeater for all action items
+				ListDataProvider<RepositoryUrl> urlsDp = new ListDataProvider<RepositoryUrl>(urls);
+				DataView<RepositoryUrl> actionItems = new DataView<RepositoryUrl>("actionItems", urlsDp) {
+					private static final long serialVersionUID = 1L;
+
+					public void populateItem(final Item<RepositoryUrl> repoLinkItem) {
+						RepositoryUrl repoUrl = repoLinkItem.getModelObject();
+						Fragment fragment = new Fragment("actionItem", "actionFragment", this);
+						fragment.add(createPermissionBadge("permission", repoUrl));
+
+						if (!StringUtils.isEmpty(clientApp.cloneUrl)) {
+							// custom registered url
+							String url = substitute(clientApp.cloneUrl, repoUrl.url, baseURL);
+							fragment.add(new LinkPanel("content", "applicationMenuItem", getString("gb.clone") + " " + repoUrl.url, url));
+							repoLinkItem.add(fragment);
+							fragment.add(new Label("copyFunction").setVisible(false));
+						} else if (!StringUtils.isEmpty(clientApp.command)) {
+							// command-line
+							String command = substitute(clientApp.command, repoUrl.url, baseURL);
+							Label content = new Label("content", command);
+							WicketUtils.setCssClass(content, "commandMenuItem");
+							fragment.add(content);
+							repoLinkItem.add(fragment);
+							
+							// copy function for command
+							fragment.add(createCopyFragment(command));
+						}
+					}};
+					appMenu.add(actionItems);
+			}
+		};
+		
+		Fragment applicationMenus = new Fragment(wicketId, "applicationMenusFragment", this);
+		applicationMenus.add(appMenus);
+		return applicationMenus;
+	}
+	
+	protected String substitute(String pattern, String repoUrl, String baseUrl) {
+		return pattern.replace("${repoUrl}", repoUrl).replace("${baseUrl}", baseUrl);
+	}
+	
+	protected Label createPermissionBadge(String wicketId, RepositoryUrl repoUrl) {
+		Label permissionLabel = new Label(wicketId, repoUrl.isExternal() ? externalPermission : repoUrl.permission.toString());
+		WicketUtils.setPermissionClass(permissionLabel, repoUrl.permission);
+		String tooltip = getProtocolPermissionDescription(repository, repoUrl);
+		WicketUtils.setHtmlTooltip(permissionLabel, tooltip);
+		return permissionLabel;
+	}
+	
+	protected Fragment createCopyFragment(String text) {
+		if (GitBlit.getBoolean(Keys.web.allowFlashCopyToClipboard, true)) {
+			// clippy: flash-based copy & paste
+			Fragment copyFragment = new Fragment("copyFunction", "clippyPanel", this);
+			String baseUrl = WicketUtils.getGitblitURL(getRequest());
+			ShockWaveComponent clippy = new ShockWaveComponent("clippy", baseUrl + "/clippy.swf");
+			clippy.setValue("flashVars", "text=" + StringUtils.encodeURL(text));
+			copyFragment.add(clippy);
+			return copyFragment;
+		} else {
+			// javascript: manual copy & paste with modal browser prompt dialog
+			Fragment copyFragment = new Fragment("copyFunction", "jsPanel", this);
+			ContextImage img = WicketUtils.newImage("copyIcon", "clippy.png");
+			img.add(new JavascriptTextPrompt("onclick", "Copy to Clipboard (Ctrl+C, Enter)", text));
+			copyFragment.add(img);
+			return copyFragment;
+		}
+	}
+	
+	protected String getProtocolPermissionDescription(RepositoryModel repository,
+			RepositoryUrl repoUrl) {
+		if (!urlPermissionsMap.containsKey(repoUrl.url)) {
+			String note;
+			if (repoUrl.isExternal()) {
+				String protocol = repoUrl.url.substring(0, repoUrl.url.indexOf("://"));
+				note = MessageFormat.format(getString("gb.externalPermissions"), protocol);			
+			} else {
+				note = null;			
+				String key;
+				switch (repoUrl.permission) {
+				case OWNER:
+				case REWIND:
+					key = "gb.rewindPermission";
+					break;
+				case DELETE:
+					key = "gb.deletePermission";
+					break;
+				case CREATE:
+					key = "gb.createPermission";
+					break;
+				case PUSH:
+					key = "gb.pushPermission";
+					break;
+				case CLONE:
+					key = "gb.clonePermission";
+					break;
+				default:
+					key = null;
+					note = getString("gb.viewAccess");
+					break;
+				}
+
+				if (note == null) {
+					String pattern = getString(key);
+					String description = MessageFormat.format(pattern, repoUrl.permission.toString());
+					note = description;
+				}
+			}
+			urlPermissionsMap.put(repoUrl.url, note);
+		}
+		return urlPermissionsMap.get(repoUrl.url);
+	}
+	
+	protected Map<AccessRestrictionType, String> getAccessRestrictions() {
+		if (accessRestrictionsMap == null) {
+			accessRestrictionsMap = new HashMap<AccessRestrictionType, String>();
+			for (AccessRestrictionType type : AccessRestrictionType.values()) {
+				switch (type) {
+				case NONE:
+					accessRestrictionsMap.put(type, getString("gb.notRestricted"));
+					break;
+				case PUSH:
+					accessRestrictionsMap.put(type, getString("gb.pushRestricted"));
+					break;
+				case CLONE:
+					accessRestrictionsMap.put(type, getString("gb.cloneRestricted"));
+					break;
+				case VIEW:
+					accessRestrictionsMap.put(type, getString("gb.viewRestricted"));
+					break;
+				}
+			}
+		}
+		return accessRestrictionsMap;
+	}
+	
+	protected Component createRepositoryIndicators(RepositoryModel repository) {
+		Fragment fragment = new Fragment("repositoryIndicators", "indicatorsFragment", this);
+		if (repository.isBare) {
+			fragment.add(new Label("workingCopyIndicator").setVisible(false));
+		} else {
+			Fragment wc = new Fragment("workingCopyIndicator", "workingCopyFragment", this);
+			Label lbl = new Label("workingCopy", getString("gb.workingCopy"));
+			WicketUtils.setHtmlTooltip(lbl,  getString("gb.workingCopyWarning"));
+			wc.add(lbl);
+			fragment.add(wc);
+		}
+		
+		boolean allowForking = GitBlit.getBoolean(Keys.web.allowForking, true);
+		if (!allowForking || user == null || !user.isAuthenticated) {
+			// must be logged-in to fork, hide all fork controls
+			fragment.add(new Label("forksProhibitedIndicator").setVisible(false));
+		} else {
+			String fork = GitBlit.self().getFork(user.username, repository.name);
+			boolean hasFork = fork != null;
+			boolean canFork = user.canFork(repository);
+
+			if (hasFork || !canFork) {
+				if (user.canFork() && !repository.allowForks) {
+					// show forks prohibited indicator
+					Fragment wc = new Fragment("forksProhibitedIndicator", "forksProhibitedFragment", this);
+					Label lbl = new Label("forksProhibited", getString("gb.forksProhibited"));
+					WicketUtils.setHtmlTooltip(lbl,  getString("gb.forksProhibitedWarning"));
+					wc.add(lbl);
+					fragment.add(wc);
+				} else {
+					// can not fork, no need for forks prohibited indicator
+					fragment.add(new Label("forksProhibitedIndicator").setVisible(false));
+				}
+			} else if (canFork) {
+				// can fork and we do not have one
+				fragment.add(new Label("forksProhibitedIndicator").setVisible(false));
+			}
+		}
+		return fragment;
+	}
+}
diff --git a/src/com/gitblit/wicket/panels/SearchPanel.html b/src/main/java/com/gitblit/wicket/panels/SearchPanel.html
similarity index 100%
rename from src/com/gitblit/wicket/panels/SearchPanel.html
rename to src/main/java/com/gitblit/wicket/panels/SearchPanel.html
diff --git a/src/com/gitblit/wicket/panels/SearchPanel.java b/src/main/java/com/gitblit/wicket/panels/SearchPanel.java
similarity index 100%
rename from src/com/gitblit/wicket/panels/SearchPanel.java
rename to src/main/java/com/gitblit/wicket/panels/SearchPanel.java
diff --git a/src/com/gitblit/wicket/panels/ShockWaveComponent.java b/src/main/java/com/gitblit/wicket/panels/ShockWaveComponent.java
similarity index 100%
rename from src/com/gitblit/wicket/panels/ShockWaveComponent.java
rename to src/main/java/com/gitblit/wicket/panels/ShockWaveComponent.java
diff --git a/src/main/java/com/gitblit/wicket/panels/TagsPanel.html b/src/main/java/com/gitblit/wicket/panels/TagsPanel.html
new file mode 100644
index 0000000..7bd6278
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/panels/TagsPanel.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"  
+      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"  
+      xml:lang="en"  
+      lang="en"> 
+
+<body>
+<wicket:panel>
+
+	<!-- tags -->
+	<div class="header"><i class="icon-tags"></i> <b><span wicket:id="header">[tags header]</span></b></div>	
+	<table class="pretty">
+		<tbody>
+    		<tr wicket:id="tag">
+    			<td class="date"><span wicket:id="tagDate">[tag date]</span></td>    			
+    			<td><b><span wicket:id="tagName">[tag name]</span></b></td>
+    			<td class="hidden-phone icon"><img wicket:id="tagIcon" /></td>
+    			<td class="hidden-phone"><span wicket:id="tagDescription">[tag description]</span></td>
+    			<td class="hidden-phone rightAlign">
+    				<span wicket:id="tagLinks"></span>
+				</td>
+    		</tr>
+    	</tbody>
+	</table>
+	
+	<div wicket:id="allTags">[all tags]</div>	
+
+	<!--  annotated tag links -->
+	<wicket:fragment wicket:id="annotatedLinks">
+		<span class="link">
+			<a wicket:id="tag"><wicket:message key="gb.tag"></wicket:message></a> | <a wicket:id="commit"><wicket:message key="gb.commit"></wicket:message></a> | <a wicket:id="log"><wicket:message key="gb.log"></wicket:message></a>
+		</span>
+	</wicket:fragment>
+	
+	<!-- lightweight tag links -->
+	<wicket:fragment wicket:id="lightweightLinks">
+		<span class="link">
+			<a wicket:id="commit"><wicket:message key="gb.commit"></wicket:message></a> | <a wicket:id="log"><wicket:message key="gb.log"></wicket:message></a>
+		</span>
+	</wicket:fragment>
+
+	<!-- blob tag links -->
+	<wicket:fragment wicket:id="blobLinks">
+		<span class="link">
+			<a wicket:id="tag"><wicket:message key="gb.tag"></wicket:message></a> | <a wicket:id="blob"><wicket:message key="gb.blob"></wicket:message></a> | <a wicket:id="raw"><wicket:message key="gb.raw"></wicket:message></a>
+		</span>
+	</wicket:fragment>
+	
+</wicket:panel>
+</body>
+</html>
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/panels/TagsPanel.java b/src/main/java/com/gitblit/wicket/panels/TagsPanel.java
new file mode 100644
index 0000000..907b317
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/panels/TagsPanel.java
@@ -0,0 +1,170 @@
+/*
+ * 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.wicket.panels;
+
+import java.util.List;
+
+import org.apache.wicket.markup.html.WebPage;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.link.BookmarkablePageLink;
+import org.apache.wicket.markup.html.panel.Fragment;
+import org.apache.wicket.markup.repeater.Item;
+import org.apache.wicket.markup.repeater.data.DataView;
+import org.apache.wicket.markup.repeater.data.ListDataProvider;
+import org.apache.wicket.model.StringResourceModel;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.Repository;
+
+import com.gitblit.models.RefModel;
+import com.gitblit.utils.JGitUtils;
+import com.gitblit.utils.StringUtils;
+import com.gitblit.wicket.WicketUtils;
+import com.gitblit.wicket.pages.BlobPage;
+import com.gitblit.wicket.pages.CommitPage;
+import com.gitblit.wicket.pages.LogPage;
+import com.gitblit.wicket.pages.RawPage;
+import com.gitblit.wicket.pages.TagPage;
+import com.gitblit.wicket.pages.TagsPage;
+import com.gitblit.wicket.pages.TreePage;
+
+public class TagsPanel extends BasePanel {
+
+	private static final long serialVersionUID = 1L;
+
+	private final boolean hasTags;
+
+	public TagsPanel(String wicketId, final String repositoryName, Repository r, final int maxCount) {
+		super(wicketId);
+
+		// header
+		List<RefModel> tags = JGitUtils.getTags(r, false, maxCount);
+		if (maxCount > 0) {
+			// summary page
+			// show tags page link
+			add(new LinkPanel("header", "title", new StringResourceModel("gb.tags", this, null),
+					TagsPage.class, WicketUtils.newRepositoryParameter(repositoryName)));
+		} else {
+			// tags page
+			add(new Label("header", new StringResourceModel("gb.tags", this, null)));
+		}
+
+		ListDataProvider<RefModel> tagsDp = new ListDataProvider<RefModel>(tags);
+		DataView<RefModel> tagView = new DataView<RefModel>("tag", tagsDp) {
+			private static final long serialVersionUID = 1L;
+			int counter;
+
+			public void populateItem(final Item<RefModel> item) {
+				RefModel entry = item.getModelObject();
+
+				item.add(WicketUtils.createDateLabel("tagDate", entry.getDate(), getTimeZone(), getTimeUtils()));
+
+				Class<? extends WebPage> linkClass;
+				switch (entry.getReferencedObjectType()) {
+				case Constants.OBJ_BLOB:
+					linkClass = BlobPage.class;
+					break;
+				case Constants.OBJ_TREE:
+					linkClass = TreePage.class;
+					break;
+				case Constants.OBJ_COMMIT:
+				default:
+					linkClass = CommitPage.class;
+					break;
+				}
+				item.add(new LinkPanel("tagName", "list name", entry.displayName, linkClass,
+						WicketUtils.newObjectParameter(repositoryName, entry
+								.getReferencedObjectId().getName())));
+
+				// workaround for RevTag returning a lengthy shortlog. :(
+				String message = StringUtils.trimString(entry.getShortMessage(), 
+						com.gitblit.Constants.LEN_SHORTLOG);
+
+				if (linkClass.equals(BlobPage.class)) {
+					// Blob Tag Object
+					item.add(WicketUtils.newImage("tagIcon", "file_16x16.png"));
+					item.add(new LinkPanel("tagDescription", "list", message, TagPage.class,
+							WicketUtils.newObjectParameter(repositoryName, entry.getObjectId()
+									.getName())));
+
+					Fragment fragment = new Fragment("tagLinks", "blobLinks", this);
+					fragment.add(new BookmarkablePageLink<Void>("tag", TagPage.class, WicketUtils
+							.newObjectParameter(repositoryName, entry.getObjectId().getName()))
+							.setEnabled(entry.isAnnotatedTag()));
+
+					fragment.add(new BookmarkablePageLink<Void>("blob", linkClass, WicketUtils
+							.newObjectParameter(repositoryName, entry.getReferencedObjectId()
+									.getName())));
+
+					fragment.add(new BookmarkablePageLink<Void>("raw", RawPage.class, WicketUtils
+							.newObjectParameter(repositoryName, entry.getReferencedObjectId()
+									.getName())));
+					item.add(fragment);
+				} else {
+					// TODO Tree Tag Object
+					// Standard Tag Object
+					if (entry.isAnnotatedTag()) {
+						item.add(WicketUtils.newImage("tagIcon", "tag_16x16.png"));
+						item.add(new LinkPanel("tagDescription", "list", message, TagPage.class,
+								WicketUtils.newObjectParameter(repositoryName, entry.getObjectId()
+										.getName())));
+
+						Fragment fragment = new Fragment("tagLinks", "annotatedLinks", this);
+						fragment.add(new BookmarkablePageLink<Void>("tag", TagPage.class,
+								WicketUtils.newObjectParameter(repositoryName, entry.getObjectId()
+										.getName())).setEnabled(entry.isAnnotatedTag()));
+
+						fragment.add(new BookmarkablePageLink<Void>("commit", linkClass,
+								WicketUtils.newObjectParameter(repositoryName, entry
+										.getReferencedObjectId().getName())));
+
+						fragment.add(new BookmarkablePageLink<Void>("log", LogPage.class,
+								WicketUtils.newObjectParameter(repositoryName, entry.getName())));
+						item.add(fragment);
+					} else {
+						item.add(WicketUtils.newBlankImage("tagIcon"));
+						item.add(new LinkPanel("tagDescription", "list", message, CommitPage.class,
+								WicketUtils.newObjectParameter(repositoryName, entry.getObjectId()
+										.getName())));
+						Fragment fragment = new Fragment("tagLinks", "lightweightLinks", this);
+						fragment.add(new BookmarkablePageLink<Void>("commit", CommitPage.class,
+								WicketUtils.newObjectParameter(repositoryName, entry
+										.getReferencedObjectId().getName())));
+						fragment.add(new BookmarkablePageLink<Void>("log", LogPage.class,
+								WicketUtils.newObjectParameter(repositoryName, entry.getName())));
+						item.add(fragment);
+					}
+				}
+
+				WicketUtils.setAlternatingBackground(item, counter);
+				counter++;
+			}
+		};
+		add(tagView);
+		if (tags.size() < maxCount || maxCount <= 0) {
+			add(new Label("allTags", "").setVisible(false));
+		} else {
+			add(new LinkPanel("allTags", "link", new StringResourceModel("gb.allTags", this, null),
+					TagsPage.class, WicketUtils.newRepositoryParameter(repositoryName)));
+		}
+
+		hasTags = tags.size() > 0;
+	}
+
+	public TagsPanel hideIfEmpty() {
+		setVisible(hasTags);
+		return this;
+	}
+}
diff --git a/src/com/gitblit/wicket/panels/TeamsPanel.html b/src/main/java/com/gitblit/wicket/panels/TeamsPanel.html
similarity index 100%
rename from src/com/gitblit/wicket/panels/TeamsPanel.html
rename to src/main/java/com/gitblit/wicket/panels/TeamsPanel.html
diff --git a/src/com/gitblit/wicket/panels/TeamsPanel.java b/src/main/java/com/gitblit/wicket/panels/TeamsPanel.java
similarity index 100%
rename from src/com/gitblit/wicket/panels/TeamsPanel.java
rename to src/main/java/com/gitblit/wicket/panels/TeamsPanel.java
diff --git a/src/com/gitblit/wicket/panels/UsersPanel.html b/src/main/java/com/gitblit/wicket/panels/UsersPanel.html
similarity index 100%
rename from src/com/gitblit/wicket/panels/UsersPanel.html
rename to src/main/java/com/gitblit/wicket/panels/UsersPanel.html
diff --git a/src/com/gitblit/wicket/panels/UsersPanel.java b/src/main/java/com/gitblit/wicket/panels/UsersPanel.java
similarity index 100%
rename from src/com/gitblit/wicket/panels/UsersPanel.java
rename to src/main/java/com/gitblit/wicket/panels/UsersPanel.java
diff --git a/src/log4j.properties b/src/main/java/log4j.properties
similarity index 100%
rename from src/log4j.properties
rename to src/main/java/log4j.properties
diff --git a/resources/login.mkd b/src/main/java/login.mkd
similarity index 100%
rename from resources/login.mkd
rename to src/main/java/login.mkd
diff --git a/resources/login_es.mkd b/src/main/java/login_es.mkd
similarity index 100%
rename from resources/login_es.mkd
rename to src/main/java/login_es.mkd
diff --git a/resources/login_ko.mkd b/src/main/java/login_ko.mkd
similarity index 100%
rename from resources/login_ko.mkd
rename to src/main/java/login_ko.mkd
diff --git a/resources/login_nl.mkd b/src/main/java/login_nl.mkd
similarity index 100%
rename from resources/login_nl.mkd
rename to src/main/java/login_nl.mkd
diff --git a/resources/login_pl.mkd b/src/main/java/login_pl.mkd
similarity index 100%
rename from resources/login_pl.mkd
rename to src/main/java/login_pl.mkd
diff --git a/resources/login_pt_br.mkd b/src/main/java/login_pt_br.mkd
similarity index 100%
rename from resources/login_pt_br.mkd
rename to src/main/java/login_pt_br.mkd
diff --git a/resources/login_zh_CN.mkd b/src/main/java/login_zh_CN.mkd
similarity index 100%
rename from resources/login_zh_CN.mkd
rename to src/main/java/login_zh_CN.mkd
diff --git a/src/main/java/logo.png b/src/main/java/logo.png
new file mode 100644
index 0000000..e909530
--- /dev/null
+++ b/src/main/java/logo.png
Binary files differ
diff --git a/resources/welcome.mkd b/src/main/java/welcome.mkd
similarity index 100%
rename from resources/welcome.mkd
rename to src/main/java/welcome.mkd
diff --git a/resources/welcome_es.mkd b/src/main/java/welcome_es.mkd
similarity index 100%
rename from resources/welcome_es.mkd
rename to src/main/java/welcome_es.mkd
diff --git a/resources/welcome_ko.mkd b/src/main/java/welcome_ko.mkd
similarity index 100%
rename from resources/welcome_ko.mkd
rename to src/main/java/welcome_ko.mkd
diff --git a/resources/welcome_nl.mkd b/src/main/java/welcome_nl.mkd
similarity index 100%
rename from resources/welcome_nl.mkd
rename to src/main/java/welcome_nl.mkd
diff --git a/resources/welcome_pl.mkd b/src/main/java/welcome_pl.mkd
similarity index 100%
rename from resources/welcome_pl.mkd
rename to src/main/java/welcome_pl.mkd
diff --git a/resources/welcome_pt_br.mkd b/src/main/java/welcome_pt_br.mkd
similarity index 100%
rename from resources/welcome_pt_br.mkd
rename to src/main/java/welcome_pt_br.mkd
diff --git a/resources/welcome_zh_CN.mkd b/src/main/java/welcome_zh_CN.mkd
similarity index 100%
rename from resources/welcome_zh_CN.mkd
rename to src/main/java/welcome_zh_CN.mkd
diff --git a/resources/add_16x16.png b/src/main/resources/add_16x16.png
similarity index 100%
rename from resources/add_16x16.png
rename to src/main/resources/add_16x16.png
Binary files differ
diff --git a/resources/arrow_down.png b/src/main/resources/arrow_down.png
similarity index 100%
rename from resources/arrow_down.png
rename to src/main/resources/arrow_down.png
Binary files differ
diff --git a/resources/arrow_left.png b/src/main/resources/arrow_left.png
similarity index 100%
rename from resources/arrow_left.png
rename to src/main/resources/arrow_left.png
Binary files differ
diff --git a/resources/arrow_off.png b/src/main/resources/arrow_off.png
similarity index 100%
rename from resources/arrow_off.png
rename to src/main/resources/arrow_off.png
Binary files differ
diff --git a/src/main/resources/arrow_page.png b/src/main/resources/arrow_page.png
new file mode 100644
index 0000000..93a125a
--- /dev/null
+++ b/src/main/resources/arrow_page.png
Binary files differ
diff --git a/src/main/resources/arrow_project.png b/src/main/resources/arrow_project.png
new file mode 100644
index 0000000..608c060
--- /dev/null
+++ b/src/main/resources/arrow_project.png
Binary files differ
diff --git a/resources/arrow_up.png b/src/main/resources/arrow_up.png
similarity index 100%
rename from resources/arrow_up.png
rename to src/main/resources/arrow_up.png
Binary files differ
diff --git a/resources/background.png b/src/main/resources/background.png
similarity index 100%
rename from resources/background.png
rename to src/main/resources/background.png
Binary files differ
diff --git a/resources/blank.png b/src/main/resources/blank.png
similarity index 100%
rename from resources/blank.png
rename to src/main/resources/blank.png
Binary files differ
diff --git a/resources/book_16x16.png b/src/main/resources/book_16x16.png
similarity index 100%
rename from resources/book_16x16.png
rename to src/main/resources/book_16x16.png
Binary files differ
diff --git a/src/main/resources/bootstrap/css/bootstrap-responsive.css b/src/main/resources/bootstrap/css/bootstrap-responsive.css
new file mode 100644
index 0000000..7e7ec69
--- /dev/null
+++ b/src/main/resources/bootstrap/css/bootstrap-responsive.css
@@ -0,0 +1,810 @@
+/*!
+ * Bootstrap Responsive v2.0.4
+ *
+ * Copyright 2012 Twitter, Inc
+ * Licensed under the Apache License v2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Designed and built with all the love in the world @twitter by @mdo and @fat.
+ */
+
+.clearfix {
+  *zoom: 1;
+}
+
+.clearfix:before,
+.clearfix:after {
+  display: table;
+  content: "";
+}
+
+.clearfix:after {
+  clear: both;
+}
+
+.hide-text {
+  font: 0/0 a;
+  color: transparent;
+  text-shadow: none;
+  background-color: transparent;
+  border: 0;
+}
+
+.input-block-level {
+  display: block;
+  width: 100%;
+  min-height: 28px;
+  -webkit-box-sizing: border-box;
+     -moz-box-sizing: border-box;
+      -ms-box-sizing: border-box;
+          box-sizing: border-box;
+}
+
+.hidden {
+  display: none;
+  visibility: hidden;
+}
+
+.visible-phone {
+  display: none !important;
+}
+
+.visible-tablet {
+  display: none !important;
+}
+
+.hidden-desktop {
+  display: none !important;
+}
+
+@media (max-width: 767px) {
+  .visible-phone {
+    display: inherit !important;
+  }
+  .hidden-phone {
+    display: none !important;
+  }
+  .hidden-desktop {
+    display: inherit !important;
+  }
+  .visible-desktop {
+    display: none !important;
+  }
+}
+
+@media (min-width: 768px) and (max-width: 979px) {
+  .visible-tablet {
+    display: inherit !important;
+  }
+  .hidden-tablet {
+    display: none !important;
+  }
+  .hidden-desktop {
+    display: inherit !important;
+  }
+  .visible-desktop {
+    display: none !important ;
+  }
+}
+
+@media (max-width: 480px) {
+  .nav-collapse {
+    -webkit-transform: translate3d(0, 0, 0);
+  }
+  .page-header h1 small {
+    display: block;
+    line-height: 18px;
+  }
+  input[type="checkbox"],
+  input[type="radio"] {
+    border: 1px solid #ccc;
+  }
+  .form-horizontal .control-group > label {
+    float: none;
+    width: auto;
+    padding-top: 0;
+    text-align: left;
+  }
+  .form-horizontal .controls {
+    margin-left: 0;
+  }
+  .form-horizontal .control-list {
+    padding-top: 0;
+  }
+  .form-horizontal .form-actions {
+    padding-right: 10px;
+    padding-left: 10px;
+  }
+  .modal {
+    position: absolute;
+    top: 10px;
+    right: 10px;
+    left: 10px;
+    width: auto;
+    margin: 0;
+  }
+  .modal.fade.in {
+    top: auto;
+  }
+  .modal-header .close {
+    padding: 10px;
+    margin: -10px;
+  }
+  .carousel-caption {
+    position: static;
+  }
+}
+
+@media (max-width: 767px) {
+  body {
+    padding-right: 20px;
+    padding-left: 20px;
+  }
+  .navbar-fixed-top,
+  .navbar-fixed-bottom {
+    margin-right: -20px;
+    margin-left: -20px;
+  }
+  .container-fluid {
+    padding: 0;
+  }
+  .dl-horizontal dt {
+    float: none;
+    width: auto;
+    clear: none;
+    text-align: left;
+  }
+  .dl-horizontal dd {
+    margin-left: 0;
+  }
+  .container {
+    width: auto;
+  }
+  .row-fluid {
+    width: 100%;
+  }
+  .row,
+  .thumbnails {
+    margin-left: 0;
+  }
+  [class*="span"],
+  .row-fluid [class*="span"] {
+    display: block;
+    float: none;
+    width: auto;
+    margin-left: 0;
+  }
+  .input-large,
+  .input-xlarge,
+  .input-xxlarge,
+  input[class*="span"],
+  select[class*="span"],
+  textarea[class*="span"],
+  .uneditable-input {
+    display: block;
+    width: 100%;
+    min-height: 28px;
+    -webkit-box-sizing: border-box;
+       -moz-box-sizing: border-box;
+        -ms-box-sizing: border-box;
+            box-sizing: border-box;
+  }
+  .input-prepend input,
+  .input-append input,
+  .input-prepend input[class*="span"],
+  .input-append input[class*="span"] {
+    display: inline-block;
+    width: auto;
+  }
+}
+
+@media (min-width: 768px) and (max-width: 979px) {
+  .row {
+    margin-left: -20px;
+    *zoom: 1;
+  }
+  .row:before,
+  .row:after {
+    display: table;
+    content: "";
+  }
+  .row:after {
+    clear: both;
+  }
+  [class*="span"] {
+    float: left;
+    margin-left: 20px;
+  }
+  .container,
+  .navbar-fixed-top .container,
+  .navbar-fixed-bottom .container {
+    width: 724px;
+  }
+  .span12 {
+    width: 724px;
+  }
+  .span11 {
+    width: 662px;
+  }
+  .span10 {
+    width: 600px;
+  }
+  .span9 {
+    width: 538px;
+  }
+  .span8 {
+    width: 476px;
+  }
+  .span7 {
+    width: 414px;
+  }
+  .span6 {
+    width: 352px;
+  }
+  .span5 {
+    width: 290px;
+  }
+  .span4 {
+    width: 228px;
+  }
+  .span3 {
+    width: 166px;
+  }
+  .span2 {
+    width: 104px;
+  }
+  .span1 {
+    width: 42px;
+  }
+  .offset12 {
+    margin-left: 764px;
+  }
+  .offset11 {
+    margin-left: 702px;
+  }
+  .offset10 {
+    margin-left: 640px;
+  }
+  .offset9 {
+    margin-left: 578px;
+  }
+  .offset8 {
+    margin-left: 516px;
+  }
+  .offset7 {
+    margin-left: 454px;
+  }
+  .offset6 {
+    margin-left: 392px;
+  }
+  .offset5 {
+    margin-left: 330px;
+  }
+  .offset4 {
+    margin-left: 268px;
+  }
+  .offset3 {
+    margin-left: 206px;
+  }
+  .offset2 {
+    margin-left: 144px;
+  }
+  .offset1 {
+    margin-left: 82px;
+  }
+  .row-fluid {
+    width: 100%;
+    *zoom: 1;
+  }
+  .row-fluid:before,
+  .row-fluid:after {
+    display: table;
+    content: "";
+  }
+  .row-fluid:after {
+    clear: both;
+  }
+  .row-fluid [class*="span"] {
+    display: block;
+    float: left;
+    width: 100%;
+    min-height: 28px;
+    margin-left: 2.762430939%;
+    *margin-left: 2.709239449638298%;
+    -webkit-box-sizing: border-box;
+       -moz-box-sizing: border-box;
+        -ms-box-sizing: border-box;
+            box-sizing: border-box;
+  }
+  .row-fluid [class*="span"]:first-child {
+    margin-left: 0;
+  }
+  .row-fluid .span12 {
+    width: 99.999999993%;
+    *width: 99.9468085036383%;
+  }
+  .row-fluid .span11 {
+    width: 91.436464082%;
+    *width: 91.38327259263829%;
+  }
+  .row-fluid .span10 {
+    width: 82.87292817100001%;
+    *width: 82.8197366816383%;
+  }
+  .row-fluid .span9 {
+    width: 74.30939226%;
+    *width: 74.25620077063829%;
+  }
+  .row-fluid .span8 {
+    width: 65.74585634900001%;
+    *width: 65.6926648596383%;
+  }
+  .row-fluid .span7 {
+    width: 57.182320438000005%;
+    *width: 57.129128948638304%;
+  }
+  .row-fluid .span6 {
+    width: 48.618784527%;
+    *width: 48.5655930376383%;
+  }
+  .row-fluid .span5 {
+    width: 40.055248616%;
+    *width: 40.0020571266383%;
+  }
+  .row-fluid .span4 {
+    width: 31.491712705%;
+    *width: 31.4385212156383%;
+  }
+  .row-fluid .span3 {
+    width: 22.928176794%;
+    *width: 22.874985304638297%;
+  }
+  .row-fluid .span2 {
+    width: 14.364640883%;
+    *width: 14.311449393638298%;
+  }
+  .row-fluid .span1 {
+    width: 5.801104972%;
+    *width: 5.747913482638298%;
+  }
+  input,
+  textarea,
+  .uneditable-input {
+    margin-left: 0;
+  }
+  input.span12,
+  textarea.span12,
+  .uneditable-input.span12 {
+    width: 714px;
+  }
+  input.span11,
+  textarea.span11,
+  .uneditable-input.span11 {
+    width: 652px;
+  }
+  input.span10,
+  textarea.span10,
+  .uneditable-input.span10 {
+    width: 590px;
+  }
+  input.span9,
+  textarea.span9,
+  .uneditable-input.span9 {
+    width: 528px;
+  }
+  input.span8,
+  textarea.span8,
+  .uneditable-input.span8 {
+    width: 466px;
+  }
+  input.span7,
+  textarea.span7,
+  .uneditable-input.span7 {
+    width: 404px;
+  }
+  input.span6,
+  textarea.span6,
+  .uneditable-input.span6 {
+    width: 342px;
+  }
+  input.span5,
+  textarea.span5,
+  .uneditable-input.span5 {
+    width: 280px;
+  }
+  input.span4,
+  textarea.span4,
+  .uneditable-input.span4 {
+    width: 218px;
+  }
+  input.span3,
+  textarea.span3,
+  .uneditable-input.span3 {
+    width: 156px;
+  }
+  input.span2,
+  textarea.span2,
+  .uneditable-input.span2 {
+    width: 94px;
+  }
+  input.span1,
+  textarea.span1,
+  .uneditable-input.span1 {
+    width: 32px;
+  }
+}
+
+@media (min-width: 1200px) {
+  .row {
+    margin-left: -30px;
+    *zoom: 1;
+  }
+  .row:before,
+  .row:after {
+    display: table;
+    content: "";
+  }
+  .row:after {
+    clear: both;
+  }
+  [class*="span"] {
+    float: left;
+    margin-left: 30px;
+  }
+  .container,
+  .navbar-fixed-top .container,
+  .navbar-fixed-bottom .container {
+    width: 1170px;
+  }
+  .span12 {
+    width: 1170px;
+  }
+  .span11 {
+    width: 1070px;
+  }
+  .span10 {
+    width: 970px;
+  }
+  .span9 {
+    width: 870px;
+  }
+  .span8 {
+    width: 770px;
+  }
+  .span7 {
+    width: 670px;
+  }
+  .span6 {
+    width: 570px;
+  }
+  .span5 {
+    width: 470px;
+  }
+  .span4 {
+    width: 370px;
+  }
+  .span3 {
+    width: 270px;
+  }
+  .span2 {
+    width: 170px;
+  }
+  .span1 {
+    width: 70px;
+  }
+  .offset12 {
+    margin-left: 1230px;
+  }
+  .offset11 {
+    margin-left: 1130px;
+  }
+  .offset10 {
+    margin-left: 1030px;
+  }
+  .offset9 {
+    margin-left: 930px;
+  }
+  .offset8 {
+    margin-left: 830px;
+  }
+  .offset7 {
+    margin-left: 730px;
+  }
+  .offset6 {
+    margin-left: 630px;
+  }
+  .offset5 {
+    margin-left: 530px;
+  }
+  .offset4 {
+    margin-left: 430px;
+  }
+  .offset3 {
+    margin-left: 330px;
+  }
+  .offset2 {
+    margin-left: 230px;
+  }
+  .offset1 {
+    margin-left: 130px;
+  }
+  .row-fluid {
+    width: 100%;
+    *zoom: 1;
+  }
+  .row-fluid:before,
+  .row-fluid:after {
+    display: table;
+    content: "";
+  }
+  .row-fluid:after {
+    clear: both;
+  }
+  .row-fluid [class*="span"] {
+    display: block;
+    float: left;
+    width: 100%;
+    min-height: 28px;
+    margin-left: 2.564102564%;
+    *margin-left: 2.510911074638298%;
+    -webkit-box-sizing: border-box;
+       -moz-box-sizing: border-box;
+        -ms-box-sizing: border-box;
+            box-sizing: border-box;
+  }
+  .row-fluid [class*="span"]:first-child {
+    margin-left: 0;
+  }
+  .row-fluid .span12 {
+    width: 100%;
+    *width: 99.94680851063829%;
+  }
+  .row-fluid .span11 {
+    width: 91.45299145300001%;
+    *width: 91.3997999636383%;
+  }
+  .row-fluid .span10 {
+    width: 82.905982906%;
+    *width: 82.8527914166383%;
+  }
+  .row-fluid .span9 {
+    width: 74.358974359%;
+    *width: 74.30578286963829%;
+  }
+  .row-fluid .span8 {
+    width: 65.81196581200001%;
+    *width: 65.7587743226383%;
+  }
+  .row-fluid .span7 {
+    width: 57.264957265%;
+    *width: 57.2117657756383%;
+  }
+  .row-fluid .span6 {
+    width: 48.717948718%;
+    *width: 48.6647572286383%;
+  }
+  .row-fluid .span5 {
+    width: 40.170940171000005%;
+    *width: 40.117748681638304%;
+  }
+  .row-fluid .span4 {
+    width: 31.623931624%;
+    *width: 31.5707401346383%;
+  }
+  .row-fluid .span3 {
+    width: 23.076923077%;
+    *width: 23.0237315876383%;
+  }
+  .row-fluid .span2 {
+    width: 14.529914530000001%;
+    *width: 14.4767230406383%;
+  }
+  .row-fluid .span1 {
+    width: 5.982905983%;
+    *width: 5.929714493638298%;
+  }
+  input,
+  textarea,
+  .uneditable-input {
+    margin-left: 0;
+  }
+  input.span12,
+  textarea.span12,
+  .uneditable-input.span12 {
+    width: 1160px;
+  }
+  input.span11,
+  textarea.span11,
+  .uneditable-input.span11 {
+    width: 1060px;
+  }
+  input.span10,
+  textarea.span10,
+  .uneditable-input.span10 {
+    width: 960px;
+  }
+  input.span9,
+  textarea.span9,
+  .uneditable-input.span9 {
+    width: 860px;
+  }
+  input.span8,
+  textarea.span8,
+  .uneditable-input.span8 {
+    width: 760px;
+  }
+  input.span7,
+  textarea.span7,
+  .uneditable-input.span7 {
+    width: 660px;
+  }
+  input.span6,
+  textarea.span6,
+  .uneditable-input.span6 {
+    width: 560px;
+  }
+  input.span5,
+  textarea.span5,
+  .uneditable-input.span5 {
+    width: 460px;
+  }
+  input.span4,
+  textarea.span4,
+  .uneditable-input.span4 {
+    width: 360px;
+  }
+  input.span3,
+  textarea.span3,
+  .uneditable-input.span3 {
+    width: 260px;
+  }
+  input.span2,
+  textarea.span2,
+  .uneditable-input.span2 {
+    width: 160px;
+  }
+  input.span1,
+  textarea.span1,
+  .uneditable-input.span1 {
+    width: 60px;
+  }
+  .thumbnails {
+    margin-left: -30px;
+  }
+  .thumbnails > li {
+    margin-left: 30px;
+  }
+  .row-fluid .thumbnails {
+    margin-left: 0;
+  }
+}
+
+@media (max-width: 979px) {
+  body {
+    padding-top: 0;
+  }
+  .navbar-fixed-top,
+  .navbar-fixed-bottom {
+    position: static;
+  }
+  .navbar-fixed-top {
+    margin-bottom: 0px;
+  }
+  .navbar-fixed-bottom {
+    margin-top: 18px;
+  }
+  .navbar-fixed-top .navbar-inner,
+  .navbar-fixed-bottom .navbar-inner {
+    padding: 0px 5px 1px;
+  }
+  .navbar .container {
+    width: auto;
+    padding: 0;
+  }
+  .nav-collapse {
+    clear: both;
+  }
+  .nav-collapse .nav {
+    float: none;
+    margin: 0 0 9px;
+  }
+  .nav-collapse .nav > li {
+    float: none;
+  }
+  .nav-collapse .nav > li > a {
+    margin-bottom: 2px;
+  }
+  .nav-collapse .nav > .divider-vertical {
+    display: none;
+  }
+  .nav-collapse .nav .nav-header {
+    color: #999999;
+    text-shadow: none;
+  }
+  .nav-collapse .nav > li > a,
+  .nav-collapse .dropdown-menu a {
+    padding: 6px 15px;
+    font-weight: bold;
+    color: #999999;
+    -webkit-border-radius: 3px;
+       -moz-border-radius: 3px;
+            border-radius: 3px;
+  }
+  .nav-collapse .btn {
+    padding: 4px 10px 4px;
+    font-weight: normal;
+    -webkit-border-radius: 4px;
+       -moz-border-radius: 4px;
+            border-radius: 4px;
+  }
+  .nav-collapse .dropdown-menu li + li a {
+    margin-bottom: 2px;
+  }
+  .nav-collapse .nav > li > a:hover,
+  .nav-collapse .dropdown-menu a:hover {
+    background-color: #222222;
+  }
+  .nav-collapse.in .btn-group {
+    padding: 0;
+    margin-top: 5px;
+  }
+  .nav-collapse .dropdown-menu {
+    position: static;
+    top: auto;
+    left: auto;
+    display: block;
+    float: none;
+    max-width: none;
+    padding: 0;
+    margin: 0 15px;
+    background-color: transparent;
+    border: none;
+    -webkit-border-radius: 0;
+       -moz-border-radius: 0;
+            border-radius: 0;
+    -webkit-box-shadow: none;
+       -moz-box-shadow: none;
+            box-shadow: none;
+  }
+  .nav-collapse .dropdown-menu:before,
+  .nav-collapse .dropdown-menu:after {
+    display: none;
+  }
+  .nav-collapse .dropdown-menu .divider {
+    display: none;
+  }
+  .nav-collapse .navbar-form,
+  .nav-collapse .navbar-search {
+    float: none;
+    padding: 9px 15px;
+    margin: 9px 0;
+    border-top: 1px solid #222222;
+    border-bottom: 1px solid #222222;
+    -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);
+       -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);
+            box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);
+  }
+  .navbar .nav-collapse .nav.pull-right {
+    float: none;
+    margin-left: 0;
+  }
+  .nav-collapse,
+  .nav-collapse.collapse {
+    height: 0;
+    overflow: hidden;
+  }
+  .navbar .btn-navbar {
+    display: block;
+  }
+  .navbar-static .navbar-inner {
+    padding-right: 10px;
+    padding-left: 10px;
+  }
+}
+
+@media (min-width: 980px) {
+  .nav-collapse.collapse {
+    height: auto !important;
+    overflow: visible !important;
+  }
+}
diff --git a/resources/bootstrap/css/bootstrap.css b/src/main/resources/bootstrap/css/bootstrap.css
similarity index 100%
rename from resources/bootstrap/css/bootstrap.css
rename to src/main/resources/bootstrap/css/bootstrap.css
diff --git a/src/main/resources/bootstrap/css/iconic.css b/src/main/resources/bootstrap/css/iconic.css
new file mode 100644
index 0000000..137606c
--- /dev/null
+++ b/src/main/resources/bootstrap/css/iconic.css
@@ -0,0 +1,567 @@
+/*
+ * Iconic CSS by Jansy
+ * http://jasny.github.io/bootstrap
+ *
+ */
+ 
+@font-face {
+  font-family: IconicStroke;
+  font-weight: normal;
+  src: url(../font/iconic_stroke.eot);
+  src: local('IconicStroke'), url(../font/iconic_stroke.eot?#iefix) format('embedded-opentype'), url(../font/iconic_stroke.woff) format('woff'), url(../font/iconic_stroke.ttf) format('truetype'), url(../font/iconic_stroke.svg#iconic) format('svg'), url(../font/iconic_stroke.otf) format('opentype');
+}
+
+@font-face {
+  font-family: IconicFill;
+  font-weight: normal;
+  src: url(../font/iconic_fill.eot);
+  src: local('IconicFill'), url(../font/iconic_fill.eot?#iefix) format('embedded-opentype'), url(../font/iconic_fill.woff) format('woff'), url(../font/iconic_fill.ttf) format('truetype'), url(../font/iconic_fill.svg#iconic) format('svg'), url(../font/iconic_fill.otf) format('opentype');
+}
+
+@media screen, print {
+  [class*="iconic-"] {
+    font-style: inherit;
+    font-weight: normal;
+    vertical-align: middle;
+  }
+  [class*="iconic-"]:before {
+    display: inline-block;
+    width: 1em;
+    font-family: IconicFill;
+    text-align: center;
+    content: "";
+  }
+  .iconic-stroke:before {
+    font-family: IconicStroke;
+  }
+  .iconic-hash:before {
+    content: '\23';
+  }
+  .iconic-question-mark:before {
+    content: '\3f';
+  }
+  .iconic-at:before {
+    content: '\40';
+  }
+  .iconic-pilcrow:before {
+    content: '\b6';
+  }
+  .iconic-info:before {
+    content: '\2139';
+  }
+  .iconic-arrow-left:before {
+    content: '\2190';
+  }
+  .iconic-arrow-up:before {
+    content: '\2191';
+  }
+  .iconic-arrow-right:before {
+    content: '\2192';
+  }
+  .iconic-arrow-down:before {
+    content: '\2193';
+  }
+  .iconic-home:before {
+    content: '\2302';
+  }
+  .iconic-sun:before {
+    content: '\2600';
+  }
+  .iconic-cloud:before {
+    content: '\2601';
+  }
+  .iconic-umbrella:before {
+    content: '\2602';
+  }
+  .iconic-star:before {
+    content: '\2605';
+  }
+  .iconic-moon:before {
+    content: '\263e';
+  }
+  .iconic-heart:before {
+    content: '\2764';
+  }
+  .iconic-cog:before {
+    content: '\2699';
+  }
+  .iconic-bolt:before {
+    content: '\26a1';
+  }
+  .iconic-key:before {
+    content: '\26bf';
+  }
+  .iconic-rain:before {
+    content: '\26c6';
+  }
+  .iconic-denied:before {
+    content: '\26d4';
+  }
+  .iconic-mail:before {
+    content: '\2709';
+  }
+  .iconic-pen:before {
+    content: '\270e';
+  }
+  .iconic-x:before {
+    content: '\2717';
+  }
+  .iconic-o-x:before {
+    content: '\2718';
+  }
+  .iconic-check:before {
+    content: '\2713';
+  }
+  .iconic-o-check:before {
+    content: '\2714';
+  }
+  .iconic-left-quote:before {
+    content: '\275d';
+  }
+  .iconic-right-quote:before {
+    content: '\275e';
+  }
+  .iconic-plus:before {
+    content: '\2795';
+  }
+  .iconic-minus:before {
+    content: '\2796';
+  }
+  .iconic-curved-arrow:before {
+    content: '\2935';
+  }
+  .iconic-document-alt:before {
+    content: '\e000';
+  }
+  .iconic-calendar:before {
+    content: '\e001';
+  }
+  .iconic-map-pin-alt:before {
+    content: '\e002';
+  }
+  .iconic-comment-alt1:before {
+    content: '\e003';
+  }
+  .iconic-comment-alt2:before {
+    content: '\e004';
+  }
+  .iconic-pen-alt:before {
+    content: '\e005';
+  }
+  .iconic-pen-alt2:before {
+    content: '\e006';
+  }
+  .iconic-chat-alt:before {
+    content: '\e007';
+  }
+  .iconic-o-plus:before {
+    content: '\e008';
+  }
+  .iconic-o-minus:before {
+    content: '\e009';
+  }
+  .iconic-bars-alt:before {
+    content: '\e00a';
+  }
+  .iconic-book-alt:before {
+    content: '\e00b';
+  }
+  .iconic-aperture-alt:before {
+    content: '\e00c';
+  }
+  .iconic-beaker-alt:before {
+    content: '\e010';
+  }
+  .iconic-left-quote-alt:before {
+    content: '\e011';
+  }
+  .iconic-right-quote-alt:before {
+    content: '\e012';
+  }
+  .iconic-o-arrow-left:before {
+    content: '\e013';
+  }
+  .iconic-o-arrow-up:before {
+    content: '\e014';
+  }
+  .iconic-o-arrow-right:before {
+    content: '\e015';
+  }
+  .iconic-o-arrow-down:before {
+    content: '\e016';
+  }
+  .iconic-o-arrow-left-alt:before {
+    content: '\e017';
+  }
+  .iconic-o-arrow-up-alt:before {
+    content: '\e018';
+  }
+  .iconic-o-arrow-right-alt:before {
+    content: '\e019';
+  }
+  .iconic-o-arrow-down-alt:before {
+    content: '\e01a';
+  }
+  .iconic-brush:before {
+    content: '\e01b';
+  }
+  .iconic-brush-alt:before {
+    content: '\e01c';
+  }
+  .iconic-eyedropper:before {
+    content: '\e01e';
+  }
+  .iconic-layers:before {
+    content: '\e01f';
+  }
+  .iconic-layers-alt:before {
+    content: '\e020';
+  }
+  .iconic-compass:before {
+    content: '\e021';
+  }
+  .iconic-award:before {
+    content: '\e022';
+  }
+  .iconic-beaker:before {
+    content: '\e023';
+  }
+  .iconic-steering-wheel:before {
+    content: '\e024';
+  }
+  .iconic-eye:before {
+    content: '\e025';
+  }
+  .iconic-aperture:before {
+    content: '\e026';
+  }
+  .iconic-image:before {
+    content: '\e027';
+  }
+  .iconic-chart:before {
+    content: '\e028';
+  }
+  .iconic-chart-alt:before {
+    content: '\e029';
+  }
+  .iconic-target:before {
+    content: '\e02a';
+  }
+  .iconic-tag:before {
+    content: '\e02b';
+  }
+  .iconic-rss:before {
+    content: '\e02c';
+  }
+  .iconic-rss-alt:before {
+    content: '\e02d';
+  }
+  .iconic-share:before {
+    content: '\e02e';
+  }
+  .iconic-undo:before {
+    content: '\e02f';
+  }
+  .iconic-reload:before {
+    content: '\e030';
+  }
+  .iconic-reload-alt:before {
+    content: '\e031';
+  }
+  .iconic-loop:before {
+    content: '\e032';
+  }
+  .iconic-loop-alt:before {
+    content: '\e033';
+  }
+  .iconic-back-forth:before {
+    content: '\e034';
+  }
+  .iconic-back-forth-alt:before {
+    content: '\e035';
+  }
+  .iconic-spin:before {
+    content: '\e036';
+  }
+  .iconic-spin-alt:before {
+    content: '\e037';
+  }
+  .iconic-move-horizontal:before {
+    content: '\e038';
+  }
+  .iconic-move-horizontal-alt:before {
+    content: '\e039';
+  }
+  .iconic-o-move-horizontal:before {
+    content: '\e03a';
+  }
+  .iconic-move-vertical:before {
+    content: '\e03b';
+  }
+  .iconic-move-vertical-alt:before {
+    content: '\e03c';
+  }
+  .iconic-o-move-vertical:before {
+    content: '\e03d';
+  }
+  .iconic-move:before {
+    content: '\e03e';
+  }
+  .iconic-move-alt:before {
+    content: '\e03f';
+  }
+  .iconic-o-move:before {
+    content: '\e040';
+  }
+  .iconic-transfer:before {
+    content: '\e041';
+  }
+  .iconic-download:before {
+    content: '\e042';
+  }
+  .iconic-upload:before {
+    content: '\e043';
+  }
+  .iconic-cloud-download:before {
+    content: '\e044';
+  }
+  .iconic-cloud-upload:before {
+    content: '\e045';
+  }
+  .iconic-fork:before {
+    content: '\e046';
+  }
+  .iconic-play:before {
+    content: '\e047';
+  }
+  .iconic-o-play:before {
+    content: '\e048';
+  }
+  .iconic-pause:before {
+    content: '\e049';
+  }
+  .iconic-stop:before {
+    content: '\e04a';
+  }
+  .iconic-eject:before {
+    content: '\e04b';
+  }
+  .iconic-first:before {
+    content: '\e04c';
+  }
+  .iconic-last:before {
+    content: '\e04d';
+  }
+  .iconic-fullscreen:before {
+    content: '\e04e';
+  }
+  .iconic-fullscreen-alt:before {
+    content: '\e04f';
+  }
+  .iconic-fullscreen-exit:before {
+    content: '\e050';
+  }
+  .iconic-fullscreen-exit-alt:before {
+    content: '\e051';
+  }
+  .iconic-equalizer:before {
+    content: '\e052';
+  }
+  .iconic-article:before {
+    content: '\e053';
+  }
+  .iconic-read-more:before {
+    content: '\e054';
+  }
+  .iconic-list:before {
+    content: '\e055';
+  }
+  .iconic-list-nested:before {
+    content: '\e056';
+  }
+  .iconic-cursor:before {
+    content: '\e057';
+  }
+  .iconic-dial:before {
+    content: '\e058';
+  }
+  .iconic-new-window:before {
+    content: '\e059';
+  }
+  .iconic-trash:before {
+    content: '\e05a';
+  }
+  .iconic-battery-half:before {
+    content: '\e05b';
+  }
+  .iconic-battery-empty:before {
+    content: '\e05c';
+  }
+  .iconic-battery-charging:before {
+    content: '\e05d';
+  }
+  .iconic-chat:before {
+    content: '\e05e';
+  }
+  .iconic-mic:before {
+    content: '\e05f';
+  }
+  .iconic-movie:before {
+    content: '\e060';
+  }
+  .iconic-headphones:before {
+    content: '\e061';
+  }
+  .iconic-user:before {
+    content: '\e062';
+  }
+  .iconic-lightbulb:before {
+    content: '\e063';
+  }
+  .iconic-cd:before {
+    content: '\e064';
+  }
+  .iconic-folder:before {
+    content: '\e065';
+  }
+  .iconic-document:before {
+    content: '\e066';
+  }
+  .iconic-pin:before {
+    content: '\e067';
+  }
+  .iconic-map-pin:before {
+    content: '\e068';
+  }
+  .iconic-book:before {
+    content: '\e069';
+  }
+  .iconic-book-alt2:before {
+    content: '\e06a';
+  }
+  .iconic-box:before {
+    content: '\e06b';
+  }
+  .iconic-calendar-alt:before {
+    content: '\e06c';
+  }
+  .iconic-comment:before {
+    content: '\e06d';
+  }
+  .iconic-iphone:before {
+    content: '\e06e';
+  }
+  .iconic-bars:before {
+    content: '\e06f';
+  }
+  .iconic-camera:before {
+    content: '\e070';
+  }
+  .iconic-volume-mute:before {
+    content: '\e071';
+  }
+  .iconic-volume:before {
+    content: '\e072';
+  }
+  .iconic-battery-full:before {
+    content: '\e073';
+  }
+  .iconic-magnifying-glass:before {
+    content: '\e074';
+  }
+  .iconic-lock:before {
+    content: '\e075';
+  }
+  .iconic-unlock:before {
+    content: '\e076';
+  }
+  .iconic-link:before {
+    content: '\e077';
+  }
+  .iconic-wrench:before {
+    content: '\e078';
+  }
+  .iconic-clock:before {
+    content: '\e079';
+  }
+  .iconic-sun-stroke:before {
+    font-family: IconicStroke;
+    content: '\2600';
+  }
+  .iconic-moon-stroke:before {
+    font-family: IconicStroke;
+    content: '\263e';
+  }
+  .iconic-star-stroke:before {
+    font-family: IconicStroke;
+    content: '\2605';
+  }
+  .iconic-heart-stroke:before {
+    font-family: IconicStroke;
+    content: '\2764';
+  }
+  .iconic-key-stroke:before {
+    font-family: IconicStroke;
+    content: '\26bf';
+  }
+  .iconic-document-alt-stroke:before {
+    font-family: IconicStroke;
+    content: '\e000';
+  }
+  .iconic-comment-alt1-stroke:before {
+    font-family: IconicStroke;
+    content: '\e003';
+  }
+  .iconic-comment-alt2-stroke:before {
+    font-family: IconicStroke;
+    content: '\e004';
+  }
+  .iconic-pen-alt-stroke:before {
+    font-family: IconicStroke;
+    content: '\e005';
+  }
+  .iconic-chat-alt-stroke:before {
+    font-family: IconicStroke;
+    content: '\e007';
+  }
+  .iconic-award-stroke:before {
+    font-family: IconicStroke;
+    content: '\e022';
+  }
+  .iconic-tag-stroke:before {
+    font-family: IconicStroke;
+    content: '\e02b';
+  }
+  .iconic-trash-stroke:before {
+    font-family: IconicStroke;
+    content: '\e05a';
+  }
+  .iconic-folder-stroke:before {
+    font-family: IconicStroke;
+    content: '\e065';
+  }
+  .iconic-document-stroke:before {
+    font-family: IconicStroke;
+    content: '\e066';
+  }
+  .iconic-map-pin-stroke:before {
+    font-family: IconicStroke;
+    content: '\e068';
+  }
+  .iconic-calendar-alt-stroke:before {
+    font-family: IconicStroke;
+    content: '\e06c';
+  }
+  .iconic-comment-stroke:before {
+    font-family: IconicStroke;
+    content: '\e06d';
+  }
+  .iconic-lock-stroke:before {
+    font-family: IconicStroke;
+    content: '\e075';
+  }
+  .iconic-unlock-stroke:before {
+    font-family: IconicStroke;
+    content: '\e076';
+  }
+}
\ No newline at end of file
diff --git a/src/main/resources/bootstrap/font/iconic_fill.afm b/src/main/resources/bootstrap/font/iconic_fill.afm
new file mode 100644
index 0000000..0cca7c4
--- /dev/null
+++ b/src/main/resources/bootstrap/font/iconic_fill.afm
@@ -0,0 +1,170 @@
+StartFontMetrics 2.0
+Comment Generated by FontForge 20110222
+Comment Creation Date: Sun Apr  1 19:42:26 2012
+FontName IconicFill
+FullName Iconic Fill
+FamilyName Iconic
+Weight Medium
+Notice (Icons by PJ Onori, font creation script by Yann)
+ItalicAngle 0
+IsFixedPitch false
+UnderlinePosition -100
+UnderlineThickness 50
+Version 001.000
+EncodingScheme ISOLatin1Encoding
+FontBBox 14 -14 760 731
+Descender -2147483648
+StartCharMetrics 151
+C 35 ; WX 681 ; N numbersign ; B 15 -14 667 731 ;
+C 63 ; WX 402 ; N question ; B 15 -14 388 731 ;
+C 64 ; WX 774 ; N at ; B 15 -14 760 731 ;
+C 182 ; WX 588 ; N paragraph ; B 15 -14 574 731 ;
+C -1 ; WX 495 ; N glyph0 ; B 15 -14 481 731 ;
+C -1 ; WX 774 ; N glyph1 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph2 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph3 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph4 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph5 ; B 15 32 760 684 ;
+C -1 ; WX 774 ; N glyph6 ; B 15 32 760 684 ;
+C -1 ; WX 774 ; N glyph7 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph8 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph9 ; B 15 -14 760 731 ;
+C -1 ; WX 728 ; N glyph10 ; B 14 -14 713 731 ;
+C -1 ; WX 774 ; N glyph11 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph12 ; B 15 -14 760 731 ;
+C -1 ; WX 681 ; N glyph13 ; B 15 -14 667 730 ;
+C -1 ; WX 774 ; N glyph14 ; B 15 32 760 684 ;
+C -1 ; WX 774 ; N glyph15 ; B 15 79 760 638 ;
+C -1 ; WX 774 ; N glyph16 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph17 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph18 ; B 15 172 760 545 ;
+C -1 ; WX 588 ; N glyph19 ; B 15 -14 574 731 ;
+C -1 ; WX 774 ; N glyph20 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph21 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph22 ; B 15 218 760 498 ;
+C -1 ; WX 774 ; N glyph23 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph24 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph25 ; B 15 172 760 545 ;
+C -1 ; WX 774 ; N glyph26 ; B 15 32 760 684 ;
+C -1 ; WX 402 ; N glyph27 ; B 15 -14 388 731 ;
+C -1 ; WX 774 ; N glyph29 ; B 15 32 760 684 ;
+C -1 ; WX 588 ; N glyph30 ; B 15 -14 574 731 ;
+C -1 ; WX 588 ; N glyph31 ; B 15 32 574 684 ;
+C -1 ; WX 774 ; N glyph32 ; B 15 79 760 638 ;
+C -1 ; WX 774 ; N glyph33 ; B 15 79 760 638 ;
+C -1 ; WX 774 ; N glyph34 ; B 15 32 760 684 ;
+C -1 ; WX 774 ; N glyph35 ; B 15 79 760 638 ;
+C -1 ; WX 774 ; N glyph36 ; B 15 79 760 638 ;
+C -1 ; WX 681 ; N glyph37 ; B 15 32 667 684 ;
+C -1 ; WX 774 ; N glyph38 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph39 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph40 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph41 ; B 15 -14 760 731 ;
+C -1 ; WX 588 ; N glyph42 ; B 15 -14 574 731 ;
+C -1 ; WX 774 ; N glyph43 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph44 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph45 ; B 15 79 760 638 ;
+C -1 ; WX 588 ; N glyph46 ; B 15 -14 574 731 ;
+C -1 ; WX 774 ; N glyph47 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph48 ; B 15 -12 760 729 ;
+C -1 ; WX 774 ; N glyph49 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph50 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph51 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph52 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph53 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph54 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph55 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph56 ; B 15 79 760 638 ;
+C -1 ; WX 774 ; N glyph57 ; B 15 79 760 638 ;
+C -1 ; WX 774 ; N glyph58 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph59 ; B 15 32 760 684 ;
+C -1 ; WX 774 ; N glyph60 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph61 ; B 15 -14 760 731 ;
+C -1 ; WX 588 ; N glyph62 ; B 15 -14 574 731 ;
+C -1 ; WX 774 ; N glyph63 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph64 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph65 ; B 15 -14 760 731 ;
+C -1 ; WX 402 ; N glyph66 ; B 15 -14 388 731 ;
+C -1 ; WX 402 ; N glyph67 ; B 15 -14 388 731 ;
+C -1 ; WX 774 ; N glyph68 ; B 15 172 760 545 ;
+C -1 ; WX 774 ; N glyph69 ; B 15 -14 760 731 ;
+C -1 ; WX 588 ; N glyph70 ; B 15 -14 574 731 ;
+C -1 ; WX 774 ; N glyph71 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph72 ; B 15 -14 760 731 ;
+C -1 ; WX 402 ; N glyph73 ; B 15 -14 388 731 ;
+C -1 ; WX 774 ; N glyph74 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph75 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph76 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph77 ; B 14 79 760 638 ;
+C -1 ; WX 774 ; N glyph78 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph79 ; B 15 79 760 638 ;
+C -1 ; WX 774 ; N glyph80 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph81 ; B 15 125 760 591 ;
+C -1 ; WX 774 ; N glyph82 ; B 15 79 760 638 ;
+C -1 ; WX 774 ; N glyph83 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph84 ; B 15 79 760 638 ;
+C -1 ; WX 774 ; N glyph85 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph86 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph87 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph88 ; B 15 79 760 638 ;
+C -1 ; WX 774 ; N glyph89 ; B 15 218 760 498 ;
+C -1 ; WX 774 ; N glyph90 ; B 15 -14 760 731 ;
+C -1 ; WX 588 ; N glyph91 ; B 15 32 574 684 ;
+C -1 ; WX 774 ; N glyph92 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph93 ; B 15 -14 760 731 ;
+C -1 ; WX 681 ; N glyph94 ; B 15 -14 667 731 ;
+C -1 ; WX 774 ; N glyph95 ; B 15 -14 760 731 ;
+C -1 ; WX 495 ; N glyph96 ; B 15 -14 481 731 ;
+C -1 ; WX 681 ; N glyph97 ; B 15 -14 667 731 ;
+C -1 ; WX 774 ; N glyph98 ; B 15 79 760 638 ;
+C -1 ; WX 774 ; N glyph99 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph100 ; B 14 -14 760 731 ;
+C -1 ; WX 309 ; N glyph101 ; B 15 -14 295 731 ;
+C -1 ; WX 774 ; N glyph102 ; B 15 -14 759 731 ;
+C -1 ; WX 681 ; N glyph103 ; B 15 -14 667 731 ;
+C -1 ; WX 774 ; N glyph104 ; B 15 -14 760 731 ;
+C -1 ; WX 402 ; N glyph105 ; B 15 -14 388 731 ;
+C -1 ; WX 774 ; N glyph106 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph107 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph108 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph109 ; B 15 265 760 452 ;
+C -1 ; WX 774 ; N glyph110 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph111 ; B 15 32 760 684 ;
+C -1 ; WX 774 ; N glyph113 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph114 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph115 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph116 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph117 ; B 15 -14 760 731 ;
+C -1 ; WX 681 ; N glyph118 ; B 15 -14 667 731 ;
+C -1 ; WX 774 ; N glyph119 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph121 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph122 ; B 15 -14 760 731 ;
+C -1 ; WX 309 ; N glyph123 ; B 15 -14 295 731 ;
+C -1 ; WX 774 ; N glyph124 ; B 15 58 760 658 ;
+C -1 ; WX 588 ; N glyph125 ; B 15 -14 574 731 ;
+C -1 ; WX 681 ; N glyph126 ; B 15 -14 667 731 ;
+C -1 ; WX 774 ; N glyph127 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph128 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph129 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph130 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph131 ; B 15 -14 760 731 ;
+C -1 ; WX 588 ; N glyph132 ; B 15 -14 574 731 ;
+C -1 ; WX 774 ; N glyph133 ; B 15 -14 760 731 ;
+C -1 ; WX 588 ; N glyph134 ; B 15 -14 574 731 ;
+C -1 ; WX 774 ; N glyph135 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph136 ; B 15 32 760 684 ;
+C -1 ; WX 774 ; N glyph137 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph138 ; B 15 -14 760 731 ;
+C -1 ; WX 588 ; N glyph139 ; B 15 -14 574 731 ;
+C -1 ; WX 774 ; N glyph140 ; B 15 172 760 545 ;
+C -1 ; WX 774 ; N glyph141 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph142 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph143 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph145 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph146 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph147 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph148 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph149 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph150 ; B 15 -14 760 731 ;
+EndCharMetrics
+EndFontMetrics
diff --git a/src/main/resources/bootstrap/font/iconic_fill.css b/src/main/resources/bootstrap/font/iconic_fill.css
new file mode 100644
index 0000000..8943a93
--- /dev/null
+++ b/src/main/resources/bootstrap/font/iconic_fill.css
@@ -0,0 +1 @@
+@font-face { font-family: 'IconicFill'; src: url('iconic_fill.eot'); src: url('iconic_fill.eot?#iefix') format('embedded-opentype'), url('iconic_fill.ttf') format('truetype'), url('iconic_fill.svg#iconic') format('svg'); font-weight: normal; font-style: normal; }.iconic { display:inline-block; font-family: 'IconicFill'; }.lightbulb:before {content:'\e063';}.equalizer:before {content:'\e052';}.brush_alt:before {content:'\e01c';}.move:before {content:'\e03e';}.tag_fill:before {content:'\e02b';}.book_alt2:before {content:'\e06a';}.layers:before {content:'\e01f';}.chat_alt_fill:before {content:'\e007';}.layers_alt:before {content:'\e020';}.cloud_upload:before {content:'\e045';}.chart_alt:before {content:'\e029';}.fullscreen_exit_alt:before {content:'\e051';}.cloud_download:before {content:'\e044';}.paperclip:before {content:'\e08a';}.heart_fill:before {content:'\2764';}.mail:before {content:'\2709';}.pen_alt_fill:before {content:'\e005';}.check_alt:before {content:'\2718';}.battery_charging:before {content:'\e05d';}.lock_fill:before {content:'\e075';}.stop:before {content:'\e04a';}.arrow_up:before {content:'\2191';}.move_horizontal:before {content:'\e038';}.compass:before {content:'\e021';}.minus_alt:before {content:'\e009';}.battery_empty:before {content:'\e05c';}.comment_fill:before {content:'\e06d';}.map_pin_alt:before {content:'\e002';}.question_mark:before {content:'\003f';}.list:before {content:'\e055';}.upload:before {content:'\e043';}.reload:before {content:'\e030';}.loop_alt4:before {content:'\e035';}.loop_alt3:before {content:'\e034';}.loop_alt2:before {content:'\e033';}.loop_alt1:before {content:'\e032';}.left_quote:before {content:'\275d';}.x:before {content:'\2713';}.last:before {content:'\e04d';}.bars:before {content:'\e06f';}.arrow_left:before {content:'\2190';}.arrow_down:before {content:'\2193';}.download:before {content:'\e042';}.home:before {content:'\2302';}.calendar:before {content:'\e001';}.right_quote_alt:before {content:'\e012';}.unlock_fill:before {content:'\e076';}.fullscreen:before {content:'\e04e';}.dial:before {content:'\e058';}.plus_alt:before {content:'\e008';}.clock:before {content:'\e079';}.movie:before {content:'\e060';}.steering_wheel:before {content:'\e024';}.pen:before {content:'\270e';}.pin:before {content:'\e067';}.denied:before {content:'\26d4';}.left_quote_alt:before {content:'\e011';}.volume_mute:before {content:'\e071';}.umbrella:before {content:'\2602';}.list_nested:before {content:'\e056';}.arrow_up_alt1:before {content:'\e014';}.undo:before {content:'\e02f';}.pause:before {content:'\e049';}.bolt:before {content:'\26a1';}.article:before {content:'\e053';}.read_more:before {content:'\e054';}.beaker:before {content:'\e023';}.beaker_alt:before {content:'\e010';}.battery_full:before {content:'\e073';}.arrow_right:before {content:'\2192';}.iphone:before {content:'\e06e';}.arrow_up_alt2:before {content:'\e018';}.cog:before {content:'\2699';}.award_fill:before {content:'\e022';}.first:before {content:'\e04c';}.trash_fill:before {content:'\e05a';}.image:before {content:'\e027';}.comment_alt1_fill:before {content:'\e003';}.cd:before {content:'\e064';}.right_quote:before {content:'\275e';}.brush:before {content:'\e01b';}.cloud:before {content:'\2601';}.eye:before {content:'\e025';}.play_alt:before {content:'\e048';}.transfer:before {content:'\e041';}.pen_alt2:before {content:'\e006';}.camera:before {content:'\e070';}.move_horizontal_alt2:before {content:'\e03a';}.curved_arrow:before {content:'\2935';}.move_horizontal_alt1:before {content:'\e039';}.aperture:before {content:'\e026';}.reload_alt:before {content:'\e031';}.magnifying_glass:before {content:'\e074';}.calendar_alt_fill:before {content:'\e06c';}.fork:before {content:'\e046';}.box:before {content:'\e06b';}.map_pin_fill:before {content:'\e068';}.bars_alt:before {content:'\e00a';}.volume:before {content:'\e072';}.x_alt:before {content:'\2714';}.link:before {content:'\e077';}.move_vertical:before {content:'\e03b';}.eyedropper:before {content:'\e01e';}.spin:before {content:'\e036';}.rss:before {content:'\e02c';}.info:before {content:'\2139';}.target:before {content:'\e02a';}.cursor:before {content:'\e057';}.key_fill:before {content:'\26bf';}.minus:before {content:'\2796';}.book_alt:before {content:'\e00b';}.headphones:before {content:'\e061';}.hash:before {content:'\0023';}.arrow_left_alt1:before {content:'\e013';}.arrow_left_alt2:before {content:'\e017';}.fullscreen_exit:before {content:'\e050';}.share:before {content:'\e02e';}.fullscreen_alt:before {content:'\e04f';}.comment_alt2_fill:before {content:'\e004';}.moon_fill:before {content:'\263e';}.at:before {content:'\0040';}.chat:before {content:'\e05e';}.move_vertical_alt2:before {content:'\e03d';}.move_vertical_alt1:before {content:'\e03c';}.check:before {content:'\2717';}.mic:before {content:'\e05f';}.book:before {content:'\e069';}.move_alt1:before {content:'\e03f';}.move_alt2:before {content:'\e040';}.document_fill:before {content:'\e066';}.plus:before {content:'\2795';}.wrench:before {content:'\e078';}.play:before {content:'\e047';}.star:before {content:'\2605';}.document_alt_fill:before {content:'\e000';}.chart:before {content:'\e028';}.rain:before {content:'\26c6';}.folder_fill:before {content:'\e065';}.new_window:before {content:'\e059';}.user:before {content:'\e062';}.battery_half:before {content:'\e05b';}.aperture_alt:before {content:'\e00c';}.eject:before {content:'\e04b';}.arrow_down_alt1:before {content:'\e016';}.pilcrow:before {content:'\00b6';}.arrow_down_alt2:before {content:'\e01a';}.arrow_right_alt1:before {content:'\e015';}.arrow_right_alt2:before {content:'\e019';}.rss_alt:before {content:'\e02d';}.spin_alt:before {content:'\e037';}.sun_fill:before {content:'\2600';}
\ No newline at end of file
diff --git a/src/main/resources/bootstrap/font/iconic_fill.eot b/src/main/resources/bootstrap/font/iconic_fill.eot
new file mode 100644
index 0000000..f1e8f85
--- /dev/null
+++ b/src/main/resources/bootstrap/font/iconic_fill.eot
Binary files differ
diff --git a/src/main/resources/bootstrap/font/iconic_fill.otf b/src/main/resources/bootstrap/font/iconic_fill.otf
new file mode 100644
index 0000000..1b8caff
--- /dev/null
+++ b/src/main/resources/bootstrap/font/iconic_fill.otf
Binary files differ
diff --git a/src/main/resources/bootstrap/font/iconic_fill.svg b/src/main/resources/bootstrap/font/iconic_fill.svg
new file mode 100644
index 0000000..95e1359
--- /dev/null
+++ b/src/main/resources/bootstrap/font/iconic_fill.svg
@@ -0,0 +1,539 @@
+<?xml version="1.0" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
+<svg>
+<metadata>
+Created by FontForge 20110222 at Sun Apr  1 19:42:26 2012
+ By PJ Onori,,,
+Icons by PJ Onori, font creation script by Yann
+</metadata>
+<defs>
+<font id="IconicFill" horiz-adv-x="774" >
+  <font-face 
+    font-family="Iconic"
+    font-weight="500"
+    font-stretch="normal"
+    units-per-em="1000"
+    panose-1="2 0 6 3 0 0 0 0 0 0"
+    ascent="800"
+    descent="-200"
+    bbox="14.9996 -13.9766 759.209 730.262"
+    underline-thickness="50"
+    underline-position="-100"
+    unicode-range="U+0023-E08A"
+  />
+    <missing-glyph />
+    <glyph glyph-name="glyph0" unicode="&#xe063;" horiz-adv-x="495" 
+d="M247.559 637.023c-77 0 -139.582 -62.7676 -139.582 -139.907c0 -44.0469 20.1631 -84.5576 55.2324 -111c52.4883 -39.6973 73.7676 -83.5811 81.0234 -120.465h6.65137c7.30176 36.8838 28.5117 80.7676 81.0234 120.465c35.1162 26.4189 55.1855 66.9307 55.1855 111
+c0 77.1396 -62.5342 139.907 -139.534 139.907zM247.559 730.232c128.441 0 232.558 -104.372 232.558 -233.093c0 -76.3252 -35.8838 -143 -92.3018 -185.512c-25.8838 -19.6279 -47.2793 -44.8838 -47.2793 -79.7676v-59.4189h-185.907v59.4189
+c0 34.8838 -21.3955 60.1396 -47.3252 79.7676c-56.3721 42.5117 -92.3027 109.187 -92.3027 185.512c0 128.721 104.116 233.093 232.559 233.093zM154.628 -13.9531v93.209h185.907v-93.209h-185.907z" />
+    <glyph glyph-name="glyph1" unicode="&#xe052;" 
+d="M480.116 265.116v-93.0234h-46.5117v-139.534c0 -25.6982 -20.7207 -46.5117 -46.5117 -46.5117c-25.6279 0 -46.5117 20.8135 -46.5117 46.5117v139.534h-46.5117v93.0234h46.5117v418.604c0 25.6982 20.8838 46.5117 46.5117 46.5117
+c25.791 0 46.5117 -20.8135 46.5117 -46.5117v-418.604h46.5117zM759.186 544.186v-93.0225h-46.5107v-418.604c0 -25.6982 -20.7217 -46.5117 -46.5117 -46.5117c-25.6279 0 -46.5117 20.8135 -46.5117 46.5117v418.604h-46.5117v93.0225h46.5117v139.535
+c0 25.6982 20.8838 46.5117 46.5117 46.5117c25.79 0 46.5117 -20.8135 46.5117 -46.5117v-139.535h46.5107zM201.047 451.163v-93.0234h-46.5117v-325.581c0 -25.6982 -20.7217 -46.5117 -46.5117 -46.5117c-25.6279 0 -46.5117 20.8135 -46.5117 46.5117v325.581h-46.5117
+v93.0234h46.5117v232.558c0 25.6982 20.8838 46.5117 46.5117 46.5117c25.79 0 46.5117 -20.8135 46.5117 -46.5117v-232.558h46.5117z" />
+    <glyph glyph-name="glyph2" unicode="&#xe01c;" 
+d="M294.069 172.093c0 -102.744 -83.3018 -186.046 -186.046 -186.046c-33.791 0 -65.3955 9 -92.6514 24.6973l-0.37207 0.279297c55.6045 32.1631 93.0234 92.1162 93.0234 160.884v0.185547c0 51.4189 41.6045 93.0234 93.0234 93.0234
+c51.3252 0 93.0225 -41.6045 93.0225 -93.0234zM731.93 702.977c36.3496 -36.3252 36.3496 -95.209 0 -131.535l-351.744 -351.837c-17.0693 64.2324 -67.3945 114.465 -131.534 131.628l351.744 351.744c36.3252 36.3262 95.209 36.3262 131.534 0z" />
+    <glyph glyph-name="glyph3" unicode="&#xe03e;" 
+d="M433.604 265.116v-139.535h93.0234l-139.535 -139.534l-139.534 139.534h93.0225v139.535h93.0234zM340.581 451.163v139.534h-93.0225l139.534 139.535l139.535 -139.535h-93.0234v-139.534h-93.0234zM294.069 311.628h-139.534v-93.0234l-139.535 139.535
+l139.535 139.535v-93.0234h139.534v-93.0234zM480.116 404.651h139.535v93.0234l139.534 -139.535l-139.534 -139.535v93.0234h-139.535v93.0234z" />
+    <glyph glyph-name="glyph4" unicode="&#xe02b;" 
+d="M323.768 730.232l435.418 -435.418h-308.86v-308.768l-435.325 435.418v308.768h308.768zM185.047 471.604c48.8838 0 88.4883 39.6045 88.4883 88.4883s-39.6045 88.4883 -88.4883 88.4883s-88.4883 -39.6045 -88.4883 -88.4883s39.6045 -88.4883 88.4883 -88.4883z" />
+    <glyph glyph-name="glyph5" unicode="&#xe06a;" 
+d="M387.093 590.697c0 0 93.0234 93.0234 372.093 93.0234v-558.14c-281.977 0 -372.093 -93.0225 -372.093 -93.0225s-90.1162 93.0225 -372.093 93.0225v558.14c279.069 0 372.093 -93.0234 372.093 -93.0234zM108.023 590.697v-374.627
+c118.232 -10.001 190.768 -37.3496 232.558 -59.4189v374.628c-41.79 22.1621 -114.325 49.5117 -232.558 59.418zM666.163 216.07v374.627c-118.279 -9.90625 -190.814 -37.2559 -232.559 -59.418v-374.628c41.7441 22.0928 114.279 49.418 232.559 59.4189z" />
+    <glyph glyph-name="glyph6" unicode="&#xe01f;" 
+d="M15 218.604l372.093 -93.0234l372.093 93.0234v-93.0234l-372.093 -93.0225l-372.093 93.0225v93.0234zM15 404.651l372.093 -93.0234l372.093 93.0234v-93.0234l-372.093 -93.0234l-372.093 93.0234v93.0234zM15 590.697l372.093 93.0234l372.093 -93.0234v-93.0225
+l-372.093 -93.0234l-372.093 93.0234v93.0225z" />
+    <glyph glyph-name="glyph7" unicode="&#xe007;" 
+d="M573.047 451.163c0 -102.744 -83.21 -186.047 -185.954 -186.047c0 51.3252 -41.6973 93.0234 -93.0234 93.0234h-93.1162c-92.9297 0 -185.953 52.3252 -185.953 186.046c0 136.629 93.0234 186.047 185.953 186.047h186.14
+c93.0234 0 185.954 -57.6514 185.954 -186.047v-93.0225zM294.069 451.163zM294.069 451.163zM619.186 446.209c75.2334 -14.5811 140 -67.3018 140 -181.093c0 -133.721 -93.0225 -186.047 -186.139 -186.047h-92.9307c-51.3252 0 -93.0234 -41.6045 -93.0234 -93.0225
+c-102.744 0 -186.14 83.3018 -186.14 186.046v93.0234c0 16.7207 1.81445 32.1162 4.72168 46.5117h88.3945c25.6279 0 46.5117 -20.8369 46.5117 -46.5117v-46.5117h46.5117c126.466 0 229.372 101.744 232.093 227.604z" />
+    <glyph glyph-name="glyph8" unicode="&#xe020;" 
+d="M15 172.093l372.093 -93.0234l372.093 93.0234v-93.0234l-372.093 -93.0225l-372.093 93.0225v93.0234zM15 358.14l372.093 -93.0234l372.093 93.0234v-93.0234l-372.093 -93.0234l-372.093 93.0234v93.0234zM15 544.186l372.093 -93.0225l372.093 93.0225v-93.0225
+l-372.093 -93.0234l-372.093 93.0234v93.0225zM15 730.232l372.093 -93.0234l372.093 93.0234v-93.0234l-372.093 -93.0234l-372.093 93.0234v93.0234z" />
+    <glyph glyph-name="glyph9" unicode="&#xe045;" 
+d="M573.14 637.209c102.651 0 186.046 -83.3945 186.046 -186.046s-83.3945 -186.047 -186.046 -186.047h-372.093c-102.605 0 -186.047 83.3955 -186.047 186.047s83.4414 186.046 186.047 186.046c8.30176 0 16.4414 -1.27832 24.6279 -2.37207
+c31.9297 56.6982 91.9297 95.3955 161.418 95.3955c68.0469 0 128 -38.3252 160.419 -95.5576c8.46484 1.09277 16.9072 2.53418 25.6279 2.53418zM573.14 358.14c51.2324 0 93.0234 41.791 93.0234 93.0234s-41.791 93.0225 -93.0234 93.0225
+s-93.0234 -41.79 -93.0234 -93.0225h-93.0234c0 60.5811 29.5352 113.906 74.4424 147.884c-17.1631 23.2559 -44.3262 38.1621 -74.4424 38.1621c-51.2783 0 -93.0234 -41.79 -93.0234 -93.0234c0 -11.2549 1.9541 -22.1621 5.81445 -32.5342l-87.1162 -32.5117
+c-7.62793 20.3486 -11.4424 41.791 -11.6279 63.7676c-47.9307 -1.44238 -93.1162 -40.6982 -93.1162 -91.7441c0 -51.2324 41.7441 -93.0234 93.0234 -93.0234h372.093zM433.651 125.581v-139.534h-93.0234v139.534h-93.0234l139.581 139.535l139.442 -139.535h-92.9766z
+" />
+    <glyph glyph-name="glyph10" unicode="&#xe029;" horiz-adv-x="728" 
+d="M353.006 278.559l-246 -246c-122.675 141.534 -122.675 350.465 0 492zM389.797 311.628l-301.86 299.953c83.0225 67.9541 185.86 111.559 301.86 118.651v-418.604zM435.587 636.837c157.466 -24.1621 277.116 -157.512 277.116 -322.116
+c0 -181.512 -145.256 -328.674 -327.069 -328.674c-74.0469 0 -139.279 25.0693 -193.419 64.8604l243.372 241.465v344.465z" />
+    <glyph glyph-name="glyph11" unicode="&#xe051;" 
+d="M540.256 139.209l-60.1396 -60.1396v186.047h186.047l-60.1396 -60.1396l153.162 -153.162l-65.7666 -65.7676zM167.256 512.303l-152.256 152.162l65.7676 65.7676l152.256 -152.163l61.0459 59.1396l-0.90625 -185.046l-185.14 -1zM15 51.8145l153.163 153.162
+l-60.1396 60.1396h186.046v-186.047l-60.1387 60.1396l-153.163 -153.162zM666.884 451.163h-186.768v186.721l60.4189 -60.3486l152.697 152.697l65.9531 -66l-152.604 -152.697z" />
+    <glyph glyph-name="glyph12" unicode="&#xe044;" 
+d="M573.14 637.209c102.651 0 186.046 -83.4883 186.046 -186.046c0 -102.559 -83.3945 -186.047 -186.046 -186.047h-139.535v-139.535h93.0234l-139.581 -139.534l-139.488 139.534h93.0225v139.535h-139.534c-102.605 0 -186.047 83.4883 -186.047 186.047
+c0 102.558 83.4414 186.046 186.047 186.046c8.30176 0 16.4414 -1.18555 24.6279 -2.27832c31.9297 56.5107 91.9297 95.3018 161.418 95.3018c68.0469 0 128 -38.2559 160.419 -95.6514c8.46484 1.25586 16.9072 2.62793 25.6279 2.62793zM573.14 358.14
+c51.2324 0 93.0234 41.6973 93.0234 93.0234c0 51.3252 -41.791 93.0225 -93.0234 93.0225s-93.0234 -41.6973 -93.0234 -93.0225h-93.0234c0 60.5811 29.5352 114 74.4424 147.977c-17.1631 23.1631 -44.3262 38.0693 -74.4424 38.0693
+c-51.2783 0 -93.0234 -41.6973 -93.0234 -93.0234c0 -11.1621 1.9541 -22.1621 5.81445 -32.4414l-87.1162 -32.6973c-7.62793 20.3486 -11.4424 41.9766 -11.6279 63.8604c-47.9307 -1.34863 -93.1162 -40.791 -93.1162 -91.7441
+c0 -51.3262 41.7441 -93.0234 93.0234 -93.0234h372.093z" />
+    <glyph glyph-name="glyph13" unicode="&#xe08a;" horiz-adv-x="681" 
+d="M270.813 -13.0469c-68.3486 0 -132.581 26.6982 -180.86 75.0469c-48.3252 48.3252 -74.9531 112.465 -74.9531 180.86c0.046875 68.3252 26.6279 132.535 74.9531 180.814l258.978 253.372c69.6279 69.6279 192.488 69.9062 262.721 -0.37207
+c72.4883 -72.582 72.4883 -190.582 0 -263.07l-233.023 -227.325c-44.8838 -44.8838 -118.651 -44.9766 -163.977 0.37207c-45.3262 45.418 -45.3262 119.14 0 164.465l90.209 90.209l65.7676 -65.7666l-90.209 -90.21
+c-5.9541 -5.95312 -6.81445 -12.8604 -6.81445 -16.4414c0 -3.58203 0.860352 -10.4883 6.81445 -16.4883c11.9062 -11.8145 20.9766 -11.8145 32.8838 0l232.93 227.372c35.8838 35.8369 35.8838 94.8369 -0.37207 131.14c-35.1631 35.1621 -96.3955 35.1621 -131.535 0
+l-258.977 -253.372c-30.3721 -30.3955 -47.3252 -71.2334 -47.3252 -114.698c0 -43.5117 16.9531 -84.3955 47.6973 -115.093c61.5117 -61.582 168.698 -61.582 230.187 0l116.279 116.279l65.7666 -65.7676l-116.278 -116.279
+c-48.3262 -48.3252 -112.535 -75.0469 -180.861 -75.0469z" />
+    <glyph glyph-name="glyph14" unicode="&#x2764;" 
+d="M387.093 594.325c37.5117 52.6982 96.0234 89.3955 165.419 89.3955c114.023 0 206.674 -92.8369 206.674 -206.768v-17.0693l-371.906 -427.325l-372.279 427.325v17.0693c0 113.931 92.6514 206.768 206.675 206.768c69.3945 0 127.906 -36.6973 165.418 -89.3955z" />
+    <glyph glyph-name="glyph15" unicode="&#x2709;" 
+d="M388.907 370.768l-373.907 175.604v90.8369h744.186v-91.1162zM389.093 267.931l370.093 175.232v-364.094h-744.186v364.651z" />
+    <glyph glyph-name="glyph16" unicode="&#xe005;" 
+d="M704.675 675.721c72.6738 -72.6738 72.6738 -190.396 0 -263.069l-426.605 -426.604h-263.069l0.720703 263.813l425.884 425.86c72.5811 72.6748 190.488 72.6748 263.07 0zM238.559 79.0693c36.3486 36.3262 36.3252 95.9541 0 132.279
+c-36.3262 36.3262 -95.21 36.3262 -131.535 0v-132.279h131.535zM637.186 478.419c36.3262 36.3252 36.3262 95.209 0 131.534c-36.3252 36.3262 -95.209 36.3262 -131.534 0z" />
+    <glyph glyph-name="glyph17" unicode="&#x2718;" 
+d="M387.093 730.232c205.488 0 372.093 -166.604 372.093 -372.093s-166.604 -372.093 -372.093 -372.093s-372.093 166.604 -372.093 372.093s166.604 372.093 372.093 372.093zM329.419 186.441l289.395 289.419l-65.7666 65.7676l-223.651 -223.651l-105.419 105.465
+l-65.7676 -65.7666z" />
+    <glyph glyph-name="glyph18" unicode="&#xe05d;" 
+d="M666.163 451.163h93.0225v-186.047h-93.0225v-46.5117c0 -25.6279 -20.8838 -46.5117 -46.5117 -46.5117h-558.14c-25.6279 0 -46.5117 20.8838 -46.5117 46.5117v92.0234h148.535c19.1621 -54.0469 70.3252 -93.0234 131 -93.0234v47.5117h94.5576v46.5117h-94.5576
+v93.0234h92.6504v46.5117h-92.6504v45.5117c-60.6982 0 -111.838 -38.9775 -131 -93.0234h-148.535v94.0234c0 25.5811 20.8838 46.5107 46.5117 46.5107h558.14c25.6279 0 46.5117 -20.9297 46.5117 -46.5107v-46.5117z" />
+    <glyph glyph-name="glyph19" unicode="&#xe075;" horiz-adv-x="588" 
+d="M340.581 730.232c128.187 0 232.559 -104.279 232.559 -232.558v-372.094c0 -76.9531 -62.5811 -139.534 -139.535 -139.534h-279.069c-76.9541 0 -139.535 62.5811 -139.535 139.534v325.582h93.1162v46.5117c0 128.278 104.372 232.558 232.465 232.558z
+M293.977 172.093c25.7207 0 46.5117 20.8145 46.5117 46.5117c0 25.6982 -20.8135 46.5117 -46.5117 46.5117c-25.6045 0 -46.418 -20.8135 -46.418 -46.5117c0 -25.6973 20.8135 -46.5117 46.418 -46.5117zM480.116 451.163v46.5117
+c0 76.9531 -62.5811 139.534 -139.535 139.534c-76.8604 0 -139.441 -62.5811 -139.441 -139.534v-46.5117h278.977z" />
+    <glyph glyph-name="glyph20" unicode="&#xe04a;" 
+d="M15 -13.9531v744.186h744.186v-744.186h-744.186z" />
+    <glyph glyph-name="glyph21" unicode="&#x2191;" 
+d="M15 358.512l372.14 371.721l372.046 -371.721l-131.93 -131.907l-146.977 146.977v-387.534h-186.535v387.534c-72.8369 -72.8369 -146.837 -146.79 -146.837 -146.79z" />
+    <glyph glyph-name="glyph22" unicode="&#xe038;" 
+d="M294.069 311.628h-139.534v-93.0234l-139.535 139.535l139.535 139.535v-93.0234h139.534v-93.0234zM480.116 404.651h139.535v93.0234l139.534 -139.535l-139.534 -139.535v93.0234h-139.535v93.0234z" />
+    <glyph glyph-name="glyph23" unicode="&#xe021;" 
+d="M387.093 637.209c-153.884 0 -279.069 -125.186 -279.069 -279.069s125.186 -279.07 279.069 -279.07s279.07 125.187 279.07 279.07s-125.187 279.069 -279.07 279.069zM387.093 730.232c205.488 0 372.093 -166.604 372.093 -372.093
+s-166.604 -372.093 -372.093 -372.093s-372.093 166.604 -372.093 372.093s166.604 372.093 372.093 372.093zM436.512 407.465c-17.9072 17.9072 -42.8838 22.8604 -65.7676 17.21l202.396 119.511l-119.907 -205.162c6.7207 23.5576 1.81445 49.8604 -16.7207 68.4414z
+M337.86 308.814c18.6279 -18.6289 44.9531 -23.5352 68.5811 -16.7217l-205.395 -120l119.558 202.675c-5.62793 -22.9307 -0.720703 -48.0234 17.2559 -65.9531z" />
+    <glyph glyph-name="glyph24" unicode="&#xe009;" 
+d="M387.093 730.232c205.488 0 372.093 -166.604 372.093 -372.093s-166.604 -372.093 -372.093 -372.093s-372.093 166.604 -372.093 372.093s166.604 372.093 372.093 372.093zM573.14 311.628v93.0234h-372.093v-93.0234h372.093z" />
+    <glyph glyph-name="glyph25" unicode="&#xe05c;" 
+d="M759.186 451.163v-186.047h-93.0225v-46.5117c0 -25.5352 -20.8838 -46.5117 -46.5117 -46.5117h-558.14c-25.6279 0 -46.5117 20.9766 -46.5117 46.5117v279.07c0 25.5342 20.8838 46.5107 46.5117 46.5107h558.14c25.6279 0 46.5117 -20.9766 46.5117 -46.5107
+v-46.5117h93.0225zM573.14 265.116v186.047h-465.116v-186.047h465.116z" />
+    <glyph glyph-name="glyph26" unicode="&#xe06d;" 
+d="M15 218.604v232.559c0 128.441 104.116 232.558 232.559 232.558h232.558c154.163 0 279.069 -125 279.069 -279.069c0 -154.07 -124.906 -279.07 -279.069 -279.07h-186.047c-51.3252 0 -93.0225 -41.6045 -93.0225 -93.0225
+c-102.651 0 -186.047 83.3018 -186.047 186.046z" />
+    <glyph glyph-name="glyph27" unicode="&#xe002;" horiz-adv-x="402" 
+d="M201.047 730.232c102.744 0 186.046 -83.3018 186.046 -186.047c0 -102.744 -186.046 -558.139 -186.046 -558.139s-186.047 455.395 -186.047 558.139c0 102.745 83.3027 186.047 186.047 186.047zM201.047 451.163c51.3721 0 93.0225 41.6514 93.0225 93.0225
+c0 51.373 -41.6504 93.0234 -93.0225 93.0234s-93.0234 -41.6504 -93.0234 -93.0234c0 -51.3711 41.6514 -93.0225 93.0234 -93.0225z" />
+    <glyph glyph-name="question" unicode="?" horiz-adv-x="402" 
+d="M238.256 32.5586c0 -25.6279 -20.8145 -46.5117 -46.5117 -46.5117s-46.5117 20.8838 -46.5117 46.5117c0 25.79 20.8145 46.5107 46.5117 46.5107s46.5117 -20.7207 46.5117 -46.5107zM193.512 126.116c-31.0703 0 -60.2324 12.1631 -82.209 34.1631
+c-21.9775 25.6973 -34.0703 54.8604 -34.0703 85.9297c0 31.0703 12.0928 60.2324 34.0703 78.3955l155.534 155.535c17.5586 17.6279 27.2324 40.9766 27.2324 65.7676c0 24.8838 -9.67383 48.2324 -27.2559 65.7676c-36.2549 36.2559 -95.2549 36.3252 -131.488 0
+c-17.6279 -17.5352 -27.3018 -40.8604 -27.3018 -65.7676h-93.0234c0 49.6973 19.3955 96.4648 54.6045 131.628c70.2324 70.3018 192.675 70.2324 263 -0.09375c35.1396 -35.0459 54.4883 -81.7441 54.4883 -131.534c0 -49.6982 -19.3486 -96.3955 -54.5117 -131.535
+l-155.534 -151.697c-9.04688 -9.09375 -9.04688 -23.791 0 -32.8838c9.09277 -9.09375 23.79 -9.09375 32.8838 0c5.90625 6 6.81348 12.9062 6.81348 16.5342h93.0234c0 -31.0693 -12.0928 -60.3252 -34.0703 -86.0225c-21.9531 -22 -51.1162 -34.1865 -82.1855 -34.1865z
+" />
+    <glyph glyph-name="glyph29" unicode="&#xe055;" 
+d="M15 637.209c0 31.0078 15.5039 46.5117 46.5117 46.5117s46.5117 -15.5039 46.5117 -46.5117s-15.5039 -46.5117 -46.5117 -46.5117s-46.5117 15.5039 -46.5117 46.5117zM15 451.163c0 31.0078 15.5039 46.5117 46.5117 46.5117s46.5117 -15.5039 46.5117 -46.5117
+s-15.5039 -46.5117 -46.5117 -46.5117s-46.5117 15.5039 -46.5117 46.5117zM15 265.116c0 31.0078 15.5039 46.5117 46.5117 46.5117s46.5117 -15.5039 46.5117 -46.5117s-15.5039 -46.5117 -46.5117 -46.5117s-46.5117 15.5039 -46.5117 46.5117zM15 79.0693
+c0 31.0078 15.5039 46.5117 46.5117 46.5117s46.5117 -15.5039 46.5117 -46.5117c0 -31.0068 -15.5039 -46.5107 -46.5117 -46.5107s-46.5117 15.5039 -46.5117 46.5107zM201.047 590.697v93.0234h558.139v-93.0234h-558.139zM201.047 404.651v93.0234h558.139v-93.0234
+h-558.139zM201.047 218.604v93.0234h558.139v-93.0234h-558.139zM201.047 32.5586v93.0225h558.139v-93.0225h-558.139z" />
+    <glyph glyph-name="glyph30" unicode="&#xe043;" horiz-adv-x="588" 
+d="M15 623.907v106.325h558.14v-106.325h-558.14zM201.047 411.279h-93.0234l185.768 212.628l186.325 -212.628h-93.0234v-425.232h-186.046v425.232z" />
+    <glyph glyph-name="glyph31" unicode="&#xe030;" horiz-adv-x="588" 
+d="M480.116 311.628h93.0234c0 -154.163 -125 -279.069 -279.07 -279.069c-154.069 0 -279.069 124.906 -279.069 279.069s125 279.069 279.069 279.069h55.2334l-27.4424 27.2559l65.7676 65.7676l139.813 -139.535l-139.813 -139.977l-65.7676 65.7676l27.0703 27.0703
+l-54.8613 0.62793c-102.65 0 -186.046 -83.4893 -186.046 -186.047c0 -102.559 83.3955 -186.047 186.046 -186.047c102.651 0 186.047 83.4883 186.047 186.047z" />
+    <glyph glyph-name="glyph32" unicode="&#xe035;" 
+d="M666.163 311.628h93.0225c0 -76.8604 -62.5811 -139.535 -139.534 -139.535h-418.791v-93.0234l-185.86 139.535l185.86 139.535v-93.0234h418.791c25.6279 0 46.5117 20.8838 46.5117 46.5117zM154.535 451.163c-25.6279 0 -46.5117 -20.8838 -46.5117 -46.5117
+h-93.0234c0 77.0234 62.5811 139.534 139.535 139.534h418.604v93.0234l186.046 -139.534l-186.046 -139.535v93.0234h-418.604z" />
+    <glyph glyph-name="glyph33" unicode="&#xe034;" 
+d="M666.163 358.14h93.0225v-46.5117c0 -76.8604 -62.5811 -139.535 -139.534 -139.535h-325.675l0.0927734 -93.0234l-186.046 139.535l186.046 139.535l-0.0927734 -93.0234h325.675c25.6279 0 46.5117 20.8838 46.5117 46.5117v46.5117zM108.023 404.651v-46.5117
+h-93.0234v46.5117c0 77.0234 62.5811 139.534 139.535 139.534h325.581v93.0234l185.86 -139.534l-185.86 -139.535v93.0234h-325.581c-25.6279 0 -46.5117 -20.8145 -46.5117 -46.5117z" />
+    <glyph glyph-name="glyph34" unicode="&#xe033;" 
+d="M478.837 218.604l139.722 186.047l140.627 -186.047h-93.0225v-46.5117c0 -76.9531 -62.582 -139.534 -139.535 -139.534h-279.069c-76.9541 0 -139.535 62.5811 -139.535 139.534v46.5117h93.0234v-46.5117c0 -25.6279 20.8838 -46.5117 46.5117 -46.5117h279.069
+c25.6279 0 46.5117 20.8838 46.5117 46.5117v46.5117h-94.3027zM295.349 497.675l-139.721 -186.047l-140.628 186.047h93.0234v46.5107c0 76.9541 62.5811 139.535 139.535 139.535h279.069c76.9531 0 139.535 -62.5811 139.535 -139.535v-46.5107h-93.0234v46.5107
+c0 25.6289 -20.8838 46.5117 -46.5117 46.5117h-279.069c-25.6279 0 -46.5117 -20.8828 -46.5117 -46.5117v-46.5107h94.3018z" />
+    <glyph glyph-name="glyph35" unicode="&#xe032;" 
+d="M619.651 358.14l139.534 -139.535h-93.0225c0 -76.9531 -62.582 -139.535 -139.535 -139.535h-279.069c-76.9541 0 -139.535 62.582 -139.535 139.535v46.5117h93.0234v-46.5117c0 -25.6279 20.8838 -46.5117 46.5117 -46.5117h279.069
+c25.6279 0 46.5117 20.8838 46.5117 46.5117h-93.0234zM526.628 637.209c76.9531 0 139.535 -62.5811 139.535 -139.534v-46.5117h-93.0234v46.5117c0 25.6279 -20.8838 46.5107 -46.5117 46.5107h-279.069c-25.6279 0 -46.5117 -20.8828 -46.5117 -46.5107v-1.44238
+h93.0225l-139.534 -138.093l-139.535 138.093h93.0234v1.44238c0 76.9531 62.5811 139.534 139.535 139.534h279.069z" />
+    <glyph glyph-name="glyph36" unicode="&#x275d;" 
+d="M759.186 79.0693h-279.069v279.07c0 153.884 125.187 279.069 279.069 279.069v-93.0234c-102.65 0 -186.046 -83.4883 -186.046 -186.046h186.046v-279.07zM294.069 79.0693h-279.069v279.07c0 153.884 125.187 279.069 279.069 279.069v-93.0234
+c-102.65 0 -186.046 -83.4883 -186.046 -186.046h186.046v-279.07z" />
+    <glyph glyph-name="glyph37" unicode="&#x2713;" horiz-adv-x="681" 
+d="M666.163 162.837l-130.279 -130.278l-195.303 195.395l-195.395 -195.395l-130.187 130.278l195.303 195.303l-195.303 195.302l130.187 130.279l195.395 -195.396l195.303 195.396l130.279 -130.279l-195.488 -195.302z" />
+    <glyph glyph-name="glyph38" unicode="&#xe04d;" 
+d="M15 730.232l558.14 -372.093l-558.14 -372.093v744.186zM573.14 79.0693v558.14h186.046v-558.14h-186.046z" />
+    <glyph glyph-name="glyph39" unicode="&#xe06f;" 
+d="M573.14 -13.9531v744.186h186.046v-744.186h-186.046zM294.069 -13.9531v558.139h186.047v-558.139h-186.047zM15 -13.9531v372.093h186.047v-372.093h-186.047z" />
+    <glyph glyph-name="glyph40" unicode="&#x2190;" 
+d="M386.721 -13.9531l-371.721 372.093l371.721 372.093l131.907 -131.93l-146.791 -146.978h387.349v-186.581h-387.349c72.8379 -72.8369 146.791 -146.791 146.791 -146.791z" />
+    <glyph glyph-name="glyph41" unicode="&#x2193;" 
+d="M759.186 357.768l-372.093 -371.721l-372.093 371.721l131.931 131.907l146.977 -146.978v387.535h186.581v-387.535c72.8369 72.8379 146.791 146.791 146.791 146.791z" />
+    <glyph glyph-name="glyph42" unicode="&#xe042;" horiz-adv-x="588" 
+d="M15 -13.9531v106.278h558.14v-106.278h-558.14zM387.093 305h93.0234l-185.768 -212.675l-186.325 212.675h93.0234v425.232h186.046v-425.232z" />
+    <glyph glyph-name="glyph43" unicode="&#x2302;" 
+d="M387.093 730.232l372.093 -372.093h-93.0225v-372.093h-558.14v372.093h-93.0234zM573.14 79.0693v333.582l-186.047 131.534l-186.046 -131.534v-333.582h139.534v139.535h93.0234v-139.535h139.535z" />
+    <glyph glyph-name="glyph44" unicode="&#xe001;" 
+d="M571.232 637.209h187.953v-93.0234h-187.953h-93.0234h-186.046h-93.0234h-184.14v93.0234h184.14v93.0234h93.0234v-93.0234h186.046v93.0234h93.0234v-93.0234zM108.023 172.093v93.0234h558.14v-93.0234h-558.14zM108.023 358.14v93.0234h558.14v-93.0234h-558.14z
+M573.14 -13.9531h-465.116v93.0225h558.14z" />
+    <glyph glyph-name="glyph45" unicode="&#xe012;" 
+d="M15 637.209h279.069v-279.069l-279.069 -279.07v558.14zM480.116 637.209h279.069v-279.069l-279.069 -279.07v558.14z" />
+    <glyph glyph-name="glyph46" unicode="&#xe076;" horiz-adv-x="588" 
+d="M340.675 730.232c128.093 0 232.465 -104.279 232.465 -232.558v-372.094c0 -76.9531 -62.6748 -139.534 -139.535 -139.534h-279.069c-76.8604 0 -139.535 62.5811 -139.535 139.534v325.582h465.116v46.5117c0 76.9531 -62.5811 139.534 -139.441 139.534
+c-77.0234 0 -139.535 -62.5811 -139.535 -139.534h-92.9307c0 128.278 104.187 232.558 232.466 232.558zM293.977 172.093c25.791 0 46.5117 20.8145 46.5117 46.5117c0 25.6982 -20.7207 46.5117 -46.5117 46.5117c-25.6279 0 -46.5117 -20.8135 -46.5117 -46.5117
+c0 -25.6973 20.8838 -46.5117 46.5117 -46.5117z" />
+    <glyph glyph-name="glyph47" unicode="&#xe04e;" 
+d="M652.535 158.465l106.65 106.651v-279.069h-279.069l106.651 106.65l-106.651 106.651l65.7676 65.7676zM294.069 730.232l-106.65 -106.651l105.65 -105.558l-65.7666 -65.7676l-105.651 105.559l-106.651 -106.651v279.069h279.069zM294.069 199.349l-106.65 -106.651
+l106.65 -106.65h-279.069v279.069l106.651 -106.651l106.651 106.651zM759.186 730.232v-279.069l-106.65 106.651l-105.651 -105.559l-65.7676 65.7676l105.651 105.558l-106.651 106.651h279.069z" />
+    <glyph glyph-name="glyph48" unicode="&#xe058;" 
+d="M294.069 360.14c0 62.0156 31.0078 93.0234 93.0234 93.0234s93.0234 -31.0078 93.0234 -93.0234s-31.0078 -93.0234 -93.0234 -93.0234s-93.0234 31.0078 -93.0234 93.0234zM679.069 587.604c53.4111 -67.583 80.1162 -143.404 80.1162 -227.465
+c0 -67.3809 -16.5986 -129.644 -49.7949 -186.787c-33.1973 -57.1436 -78.3672 -102.313 -135.511 -135.511c-57.1436 -33.1963 -119.406 -49.7949 -186.787 -49.7949s-129.644 16.5986 -186.787 49.7949c-57.1436 33.1973 -102.313 78.3672 -135.511 135.511
+c-33.1963 57.1436 -49.7949 119.406 -49.7949 186.787c0 82.9883 26.248 158.446 78.7441 226.372c8.68848 -6.54297 17.8047 -9.81445 27.3486 -9.81445c12.8955 0 23.8711 4.52832 32.9277 13.584c9.05566 9.05664 13.584 20.0322 13.584 32.9277
+c0 9.53809 -3.35645 18.7471 -10.0693 27.6279c53.9434 42.7354 115.261 68.5342 183.953 77.3955c1.05371 -11.9824 5.91895 -22.0635 14.5957 -30.2432c8.67578 -8.17871 19.0127 -12.2686 31.0088 -12.2686c11.958 0 22.249 4.03418 30.8721 12.1016
+c8.62207 8.06836 13.5332 18.0264 14.7324 29.875c67.4736 -8.38184 128.218 -33.3975 182.232 -75.0459c-7.50293 -9.13965 -11.2549 -18.9541 -11.2549 -29.4424c0 -12.8809 4.54395 -23.8516 13.6309 -32.9131c9.08789 -9.06543 20.0479 -13.5986 32.8799 -13.5986
+c10.3545 0 19.9824 3.63574 28.8838 10.9072zM387.093 174.093c51.3828 0 95.2363 18.1562 131.561 54.4678s54.4863 80.1709 54.4863 131.579c0 51.4746 -18.2324 95.3818 -54.6982 131.721l-131.349 131.349l-131.534 -131.534
+c-36.3408 -36.3408 -54.5117 -80.1865 -54.5117 -131.535c0 -51.3721 18.165 -95.2227 54.4941 -131.553c36.3291 -36.3291 80.1797 -54.4941 131.552 -54.4941z" />
+    <glyph glyph-name="glyph49" unicode="&#xe008;" 
+d="M387.093 730.232c205.488 0 372.093 -166.604 372.093 -372.093s-166.604 -372.093 -372.093 -372.093s-372.093 166.604 -372.093 372.093s166.604 372.093 372.093 372.093zM573.14 311.628v93.0234h-139.535v139.534h-93.0234v-139.534h-139.534v-93.0234h139.534
+v-139.535h93.0234v139.535h139.535z" />
+    <glyph glyph-name="glyph50" unicode="&#xe079;" 
+d="M387.093 637.209c-153.884 0 -279.069 -125.186 -279.069 -279.069s125.186 -279.07 279.069 -279.07s279.07 125.187 279.07 279.07s-125.187 279.069 -279.07 279.069zM387.093 730.232c205.488 0 372.093 -166.604 372.093 -372.093
+s-166.604 -372.093 -372.093 -372.093s-372.093 166.604 -372.093 372.093s166.604 372.093 372.093 372.093zM513.187 298.187l-65.7676 -65.7676l-91.21 91.209c-9.7207 8.62793 -16.1621 20.6973 -16.1621 34.6973v185.86h93.5576v-166.325z" />
+    <glyph glyph-name="glyph51" unicode="&#xe060;" 
+d="M665.628 544.186l93.5576 93.0234l-91.209 93.0234h91.209v-744.186h-744.186v744.186h93.3955l93.3721 -93.3955l-94.4648 -92.6514h93.209l95 93.3955l-92.6514 92.6514h131.535l93.3018 -93.3955l-94.3945 -92.6514h109.837l95.0234 93.3955l-92.7441 92.6514h129.441
+l91.3027 -93.3955l-92.3721 -92.6514h91.8369zM666.163 80.0693v93.0234h-558.14v-93.0234h558.14zM666.163 265.116v93.0234h-558.14v-93.0234h558.14z" />
+    <glyph glyph-name="glyph52" unicode="&#xe024;" 
+d="M387.093 730.232c205.488 0 372.093 -166.604 372.093 -372.093s-166.604 -372.093 -372.093 -372.093s-372.093 166.604 -372.093 372.093s166.604 372.093 372.093 372.093zM387.093 637.209c-121.093 0 -223.372 -78 -262 -186.046h524
+c-38.6279 108.046 -140.906 186.046 -262 186.046zM387.093 311.628c25.6982 0 46.5117 20.8135 46.5117 46.5117c0 25.6973 -20.8135 46.5117 -46.5117 46.5117c-25.6973 0 -46.5117 -20.8145 -46.5117 -46.5117c0 -25.6982 20.8145 -46.5117 46.5117 -46.5117z
+M108.023 358.14c0 -137.814 100.651 -251.814 232.186 -274.349c-2.16211 151.884 -105.093 274.349 -232.186 274.349zM433.977 83.791c131.535 22.5342 232.187 136.534 232.187 274.349c-127.094 0 -230.023 -122.465 -232.187 -274.349z" />
+    <glyph glyph-name="glyph53" unicode="&#x270e;" 
+d="M704.675 675.721c72.6738 -72.6738 72.6738 -190.396 0 -263.069l-426.605 -426.604h-263.069l0.720703 263.813l425.884 425.86c36.3262 36.3486 83.9307 54.5117 131.535 54.5117c47.6973 0 95.3018 -18.1631 131.535 -54.5117zM239.559 79.0693l399.349 399.35
+l-131.535 131.534l-398.721 -398.79l-0.0927734 -39.0703h92.4883v-93.0234h38.5117z" />
+    <glyph glyph-name="glyph54" unicode="&#xe067;" 
+d="M759.186 544.186c0 -69.9062 -39.0693 -130.093 -96.1162 -161.883c0.745117 -7.86133 2.37207 -15.3955 2.37207 -23.4424c0 -154.163 -124.906 -279.069 -279.069 -279.069c-57.5117 0 -110.931 17.4414 -155.349 47.2324l-216.023 -140.977l141.604 214.744
+c-30.9766 44.8838 -49.3252 99.3018 -49.3252 158.069c0 154.163 124.907 279.07 279.069 279.07c8.7207 0 17 -1.76758 25.5352 -2.58203c0 -0.0458984 -0.0927734 -0.0927734 -0.0927734 -0.139648c32 56.4658 91.9531 95.0234 161.349 95.0234
+c102.651 0 186.046 -83.3486 186.046 -186.047zM386.372 172.814c102.372 0 185.581 83.0459 185.953 185.418c-16.6279 0.0927734 -32.5342 3 -47.8838 7.18652l-73.0459 -73.0469c-36.3262 -36.3252 -95.209 -36.3252 -131.535 0
+c-36.3252 36.3252 -36.3252 95.209 0 131.535l73.7676 73.8135c-3.81348 14.9541 -6.53516 30.3027 -6.53516 46.4648c0 0.373047 0.0927734 0.72168 0.0927734 1.09375c0 -0.139648 -0.0927734 -0.279297 -0.0927734 -0.464844
+c-0.185547 0 -0.441406 0.0927734 -0.720703 0.0927734c-102.651 0 -186.047 -83.4424 -186.047 -186.047c0 -102.651 83.3955 -186.046 186.047 -186.046zM573.14 451.163c51.2324 0 93.0234 41.7441 93.0234 93.0225c0 51.2793 -41.791 93.0234 -93.0234 93.0234
+s-93.0234 -41.7441 -93.0234 -93.0234c0 -51.2783 41.791 -93.0225 93.0234 -93.0225z" />
+    <glyph glyph-name="glyph55" unicode="&#x26d4;" 
+d="M387.093 730.232c205.488 0 372.093 -166.604 372.093 -372.093s-166.604 -372.093 -372.093 -372.093s-372.093 166.604 -372.093 372.093s166.604 372.093 372.093 372.093zM387.093 637.209c-153.884 0 -279.069 -125.186 -279.069 -279.069
+c0 -60.1396 19.5352 -115.559 52.0459 -161.163l388.279 388.093c-45.6045 32.5117 -101.023 52.1396 -161.256 52.1396zM387.093 79.0693c153.884 0 279.07 125.187 279.07 279.07c0 60.1396 -19.5352 115.558 -52.0469 161.163l-388.279 -388.094
+c45.6045 -32.5117 101.023 -52.1396 161.256 -52.1396z" />
+    <glyph glyph-name="glyph56" unicode="&#xe011;" 
+d="M759.186 79.0693h-279.069v279.07l279.069 279.069v-558.14zM294.069 79.0693h-279.069v279.07l279.069 279.069v-558.14z" />
+    <glyph glyph-name="glyph57" unicode="&#xe071;" 
+d="M201.047 172.093c-102.744 0 -186.047 83.3027 -186.047 186.047s83.3027 186.046 186.047 186.046v-372.093zM294.069 544.186l186.047 93.0234v-558.14l-186.047 93.0234v372.093zM759.186 311.628h-186.046v93.0234h186.046v-93.0234z" />
+    <glyph glyph-name="glyph58" unicode="&#x2602;" 
+d="M387.093 730.232c205.21 0 372.093 -166.93 372.093 -372.093h-93.0225c0 19.209 -15.6279 34.8838 -34.8838 34.8838c-19.1631 0 -34.8838 -15.6748 -34.8838 -34.8838h-93.0234c0 19.209 -15.6279 34.8838 -34.8838 34.8838
+c-19.1631 0 -34.8838 -15.6748 -34.8838 -34.8838v-232.559c0 -76.9531 -62.5117 -139.534 -139.535 -139.534c-76.8604 0 -139.534 62.5811 -139.534 139.534h93.0234c0 -25.6279 20.8828 -46.5117 46.5107 -46.5117c25.6982 0 46.5117 20.8838 46.5117 46.5117v232.559
+c0 19.209 -15.6279 34.8838 -34.8838 34.8838c-19.1621 0 -34.8838 -15.6748 -34.8838 -34.8838h-93.0225c0 19.209 -15.6279 34.8838 -34.8838 34.8838c-19.1631 0 -34.8838 -15.6748 -34.8838 -34.8838h-93.0234c0 205.163 166.977 372.093 372.093 372.093z" />
+    <glyph glyph-name="glyph59" unicode="&#xe056;" 
+d="M15 637.209c0 31.0078 15.5039 46.5117 46.5117 46.5117s46.5117 -15.5039 46.5117 -46.5117s-15.5039 -46.5117 -46.5117 -46.5117s-46.5117 15.5039 -46.5117 46.5117zM201.047 590.697v93.0234h558.139v-93.0234h-558.139zM201.047 451.163
+c0 31.0078 15.5039 46.5117 46.5117 46.5117c31.0068 0 46.5107 -15.5039 46.5107 -46.5117s-15.5039 -46.5117 -46.5107 -46.5117c-31.0078 0 -46.5117 15.5039 -46.5117 46.5117zM387.093 404.651v93.0234h372.093v-93.0234h-372.093zM201.047 79.0693
+c0 31.0078 15.5039 46.5117 46.5117 46.5117c31.0068 0 46.5107 -15.5039 46.5107 -46.5117c0 -31.0068 -15.5039 -46.5107 -46.5107 -46.5107c-31.0078 0 -46.5117 15.5039 -46.5117 46.5107zM387.093 32.5586v93.0225h372.093v-93.0225h-372.093zM387.093 265.116
+c0 31.0078 15.5039 46.5117 46.5117 46.5117s46.5117 -15.5039 46.5117 -46.5117s-15.5039 -46.5117 -46.5117 -46.5117s-46.5117 15.5039 -46.5117 46.5117zM573.14 218.604v93.0234h186.046v-93.0234h-186.046z" />
+    <glyph glyph-name="glyph60" unicode="&#xe014;" 
+d="M15 358.14c0 205.535 166.604 372.093 372.186 372.093c205.396 0 372 -166.558 372 -372.093c0 -205.488 -166.604 -372.093 -372 -372.093c-205.581 0 -372.186 166.604 -372.186 372.093zM573.14 358.86l-185.954 185.372l-185.418 -185.372h138.813v-186.768h93.0234
+v186.768h139.535z" />
+    <glyph glyph-name="glyph61" unicode="&#xe02f;" 
+d="M669.791 643.768c57.6045 -57.6748 89.3945 -134.349 89.3945 -217.884c0 -79.6279 -31.79 -156.303 -89.3945 -214.116l-70.1396 -69.9531l-65.7676 65.7666l70.1396 70.0938c40.0693 40.1621 62.1396 93.3945 62.1396 148.209
+c0 58.6514 -22.0703 111.884 -62.1396 151.931c-80.1162 80.3018 -219.838 80.3018 -299.954 0.0927734l-126.813 -128.768h116.813v-91.1631h-279.069v279.232h93.0234v-123.697l130.163 130.256c115.209 115.372 316.325 115.279 431.604 0zM444.047 -13.9766
+l-65.7676 65.9531l65.7676 65.7676l65.7676 -65.7676z" />
+    <glyph glyph-name="glyph62" unicode="&#xe049;" horiz-adv-x="588" 
+d="M15 -13.9531v744.186h186.047v-744.186h-186.047zM387.093 -13.9531v744.186h186.047v-744.186h-186.047z" />
+    <glyph glyph-name="glyph63" unicode="&#x26a1;" 
+d="M759.186 730.232l-325.581 -372.093l139.535 -93.0234l-558.14 -279.069l325.581 279.069l-139.534 93.0234z" />
+    <glyph glyph-name="glyph64" unicode="&#xe053;" 
+d="M759.186 637.209h-744.186v93.0234h744.186v-93.0234zM480.116 451.163h-465.116v93.0225h465.116v-93.0225zM759.186 172.093h-744.186v93.0234h744.186v-93.0234zM573.14 -13.9531h-558.14v93.0225h558.14v-93.0225zM759.186 32.5586
+c0 -25.6982 -20.8828 -46.5117 -46.5107 -46.5117c-25.791 0 -46.6055 20.8135 -46.6055 46.5117c0 25.6973 20.8145 46.5107 46.6055 46.5107c25.6279 0 46.5107 -20.8135 46.5107 -46.5107z" />
+    <glyph glyph-name="glyph65" unicode="&#xe054;" 
+d="M759.186 637.209h-744.186v93.0234h744.186v-93.0234zM480.116 451.163h-465.116v93.0225h465.116v-93.0225zM759.186 263.116h-744.186v93.0234h744.186v-93.0234zM15 32.5586c0 31.0068 15.5039 46.5107 46.5117 46.5107s46.5117 -15.5039 46.5117 -46.5107
+c0 -31.0078 -15.5039 -46.5117 -46.5117 -46.5117s-46.5117 15.5039 -46.5117 46.5117zM294.069 32.5586c0 -12.8408 -4.5459 -23.8027 -13.6396 -32.8867c-9.0918 -9.08301 -20.0498 -13.625 -32.8711 -13.625c-12.8906 0 -23.8809 4.53809 -32.9707 13.6152
+s-13.6348 20.043 -13.6348 32.8965s4.54492 23.8184 13.6348 32.8955s20.0801 13.6152 32.9707 13.6152c12.8223 0 23.7793 -4.54199 32.8711 -13.625c9.09375 -9.08301 13.6396 -20.0449 13.6396 -32.8857zM387.093 32.5586c0 31.0068 15.5039 46.5107 46.5117 46.5107
+s46.5117 -15.5039 46.5117 -46.5107c0 -31.0078 -15.5039 -46.5117 -46.5117 -46.5117s-46.5117 15.5039 -46.5117 46.5117z" />
+    <glyph glyph-name="glyph66" unicode="&#xe023;" horiz-adv-x="402" 
+d="M340.581 730.232c25.6982 0 46.5117 -20.8135 46.5117 -46.5117v-511.628c0 -102.744 -83.3018 -186.046 -186.046 -186.046s-186.047 83.3018 -186.047 186.046v511.628c0 25.6982 20.8135 46.5117 46.5117 46.5117h279.069zM247.559 172.093
+c25.6973 0 46.5107 20.8145 46.5107 46.5117c0 25.6982 -20.8135 46.5117 -46.5107 46.5117c-25.6982 0 -46.5117 -20.8135 -46.5117 -46.5117c0 -25.6973 20.8135 -46.5117 46.5117 -46.5117zM294.069 358.14v279.069h-186.046v-279.069h46.7207
+c0.348633 25.6973 21.2559 46.5117 47.0469 46.5117c25.79 0 46.6738 -20.8145 47.0459 -46.5117h45.2324z" />
+    <glyph glyph-name="glyph67" unicode="&#xe010;" horiz-adv-x="402" 
+d="M201.768 520.931c0 15.0225 7.51172 22.5342 22.5352 22.5342c15.0225 0 22.5342 -7.51172 22.5342 -22.5342c0 -15.0234 -7.51172 -22.5352 -22.5342 -22.5352c-15.0234 0 -22.5352 7.51172 -22.5352 22.5352zM340.581 730.232
+c12.8486 0 23.8135 -4.54004 32.8926 -13.6191s13.6191 -20.043 13.6191 -32.8926v-511.628c0 -51.3721 -18.165 -95.2227 -54.4941 -131.552s-80.1797 -54.4941 -131.552 -54.4941s-95.2227 18.165 -131.553 54.4941c-36.3291 36.3291 -54.4941 80.1797 -54.4941 131.552
+v511.628c0 12.8496 4.54004 23.8135 13.6191 32.8926s20.043 13.6191 32.8926 13.6191h279.069zM132 265.116c6.2207 0 11.5322 2.2002 15.9336 6.60156s6.60156 9.71289 6.60156 15.9336s-2.2002 11.5312 -6.60156 15.9326s-9.71289 6.60156 -15.9336 6.60156
+s-11.5322 -2.2002 -15.9336 -6.60156s-6.60156 -9.71191 -6.60156 -15.9326s2.2002 -11.5322 6.60156 -15.9336s9.71289 -6.60156 15.9336 -6.60156zM247.559 172.093c12.8486 0 23.8125 4.54004 32.8916 13.6191c9.0791 9.08008 13.6191 20.0439 13.6191 32.8926
+s-4.54004 23.8135 -13.6191 32.8926s-20.043 13.6191 -32.8916 13.6191c-12.8496 0 -23.8135 -4.54004 -32.8926 -13.6191s-13.6191 -20.0439 -13.6191 -32.8926s4.54004 -23.8125 13.6191 -32.8926c9.0791 -9.0791 20.0439 -13.6191 32.8926 -13.6191zM294.069 358.14
+v279.069h-186.046v-279.069h46.7207c0.174805 12.8652 4.83984 23.834 13.9951 32.9053c9.15625 9.07129 20.1729 13.6064 33.0518 13.6064c12.875 0 23.8887 -4.53516 33.04 -13.6045c9.15039 -9.07031 13.8193 -20.0391 14.0059 -32.9072h45.2324z" />
+    <glyph glyph-name="glyph68" unicode="&#xe073;" 
+d="M759.186 451.163v-186.047h-93.0225v-46.5117c0 -25.6279 -20.8838 -46.5117 -46.5117 -46.5117h-558.14c-25.6279 0 -46.5117 20.8838 -46.5117 46.5117v279.07c0 25.5342 20.8838 46.5107 46.5117 46.5107h558.14c25.6279 0 46.5117 -20.9766 46.5117 -46.5107
+v-46.5117h93.0225z" />
+    <glyph glyph-name="glyph69" unicode="&#x2192;" 
+d="M387.465 730.232l371.721 -372.093l-371.721 -372.093l-131.906 131.93l146.883 146.884h-387.441v186.675h387.441l-146.79 146.79z" />
+    <glyph glyph-name="glyph70" unicode="&#xe06e;" horiz-adv-x="588" 
+d="M387.093 730.232c102.744 0 186.047 -83.3018 186.047 -186.047v-372.093c0 -102.744 -83.3027 -186.046 -186.047 -186.046h-186.046c-102.744 0 -186.047 83.3018 -186.047 186.046v372.093c0 102.745 83.3027 186.047 186.047 186.047h186.046zM294.069 31.1162
+c26.4893 0 47.9541 21.4424 47.9541 47.9531c0 26.5117 -21.4883 47.9541 -47.9541 47.9541c-26.4648 0 -47.9531 -21.4424 -47.9531 -47.9541c0 -26.5107 21.4648 -47.9531 47.9531 -47.9531zM480.116 172.093v372.093c0 51.2334 -41.791 93.0234 -93.0234 93.0234
+h-186.046c-51.2793 0 -93.0234 -41.79 -93.0234 -93.0234v-372.093h372.093z" />
+    <glyph glyph-name="glyph71" unicode="&#xe018;" 
+d="M759.186 358.14c0 -205.488 -166.604 -372.093 -372.093 -372.093s-372.093 166.604 -372.093 372.093s166.604 372.093 372.093 372.093s372.093 -166.604 372.093 -372.093zM294.069 218.604l93.0234 -93.0234l93.0234 93.0234h-46.5117v372.093h-93.0234v-372.093
+h-46.5117z" />
+    <glyph glyph-name="glyph72" unicode="&#x2699;" 
+d="M759.186 312.349l-111.209 -47.418c-3.09277 -8.62793 -6.2793 -17.0938 -10.1855 -25.4424l45.79 -111l-65.7666 -65.7676l-111.559 44.6982c-8.34863 -4 -17.0703 -7.44238 -25.8838 -10.7217l-46.0469 -110.65h-93.0225l-47.0469 110.093
+c-9.25586 3.18555 -18.2559 6.53516 -27.0703 10.7207l-109.837 -45.2324l-65.7676 65.7676l44.2324 110.279c-4.34863 9 -7.97656 18.1855 -11.3486 27.6279l-109.465 45.6045v93.0234l109.372 46.7666c3.37207 9.46582 6.90723 18.6279 11.2793 27.6279l-45.0703 109.559
+l65.7676 65.7676l110.559 -44.4189c8.81348 4.18652 17.7207 7.62793 26.9766 10.9072l45.9766 110.093h93.0234l47.1396 -110.465c8.90723 -3.18652 17.5352 -6.62793 25.9766 -10.7207l110.744 45.6045l65.791 -65.7676l-44.8838 -111.744
+c4 -8.2793 7.25586 -16.7207 10.3486 -25.4424l111.186 -46.3252v-93.0234zM386.372 218.604c77.0234 0 139.535 62.5117 139.535 139.535s-62.5117 139.535 -139.535 139.535s-139.535 -62.5117 -139.535 -139.535s62.5117 -139.535 139.535 -139.535z" />
+    <glyph glyph-name="glyph73" unicode="&#xe022;" horiz-adv-x="402" 
+d="M201.047 265.116c32.79 0 63.7666 6.7207 93.0225 17.1162v-296.186l-93.0225 93.0225l-93.0234 -93.0225v296.186c29.2559 -10.3955 60.2324 -17.1162 93.0234 -17.1162zM15 544.186c0 124.031 62.0156 186.047 186.047 186.047
+c124.03 0 186.046 -62.0156 186.046 -186.047c0 -124.03 -62.0156 -186.046 -186.046 -186.046c-124.031 0 -186.047 62.0156 -186.047 186.046z" />
+    <glyph glyph-name="glyph74" unicode="&#xe04c;" 
+d="M759.186 -13.9531l-558.139 372.093l558.139 372.093v-744.186zM15 79.0693v558.14h186.047v-558.14h-186.047z" />
+    <glyph glyph-name="glyph75" unicode="&#xe05a;" 
+d="M666.163 637.209h93.0225v-93.0234h-93.0225v-418.604c0 -77.0225 -62.5117 -139.534 -139.535 -139.534h-279.069c-77.0234 0 -139.535 62.5117 -139.535 139.534v418.604h-93.0234v93.0234h93.0234h93.0234c0 51.4189 41.6045 93.0234 93.0225 93.0234h186.047
+c51.4189 0 93.0234 -41.6045 93.0234 -93.0234h93.0234zM294.069 172.093c25.6982 0 46.5117 20.8145 46.5117 46.5117c0 25.6982 -20.8135 46.5117 -46.5117 46.5117c-25.6973 0 -46.5107 -20.8135 -46.5107 -46.5117c0 -25.6973 20.8135 -46.5117 46.5107 -46.5117z
+M294.069 358.14c25.6982 0 46.5117 20.8135 46.5117 46.5117c0 25.6973 -20.8135 46.5117 -46.5117 46.5117c-25.6973 0 -46.5107 -20.8145 -46.5107 -46.5117c0 -25.6982 20.8135 -46.5117 46.5107 -46.5117zM480.116 172.093c25.6973 0 46.5117 20.8145 46.5117 46.5117
+c0 25.6982 -20.8145 46.5117 -46.5117 46.5117s-46.5117 -20.8135 -46.5117 -46.5117c0 -25.6973 20.8145 -46.5117 46.5117 -46.5117zM480.116 358.14c25.6973 0 46.5117 20.8135 46.5117 46.5117c0 25.6973 -20.8145 46.5117 -46.5117 46.5117
+s-46.5117 -20.8145 -46.5117 -46.5117c0 -25.6982 20.8145 -46.5117 46.5117 -46.5117z" />
+    <glyph glyph-name="glyph76" unicode="&#xe027;" 
+d="M201.047 172.093v186.047l93.0225 93.0234l93.0234 -93.0234l186.047 186.046l186.046 -186.046v-186.047h-558.139zM108.023 79.0693h651.162l-93.0225 -93.0225h-558.14h-93.0234v93.0225v558.14l93.0234 93.0234v-651.163z" />
+    <glyph glyph-name="glyph77" unicode="&#xe003;" 
+d="M711.256 172.093h-370.581c-139.442 0 -139.442 0 -232.466 -93.0234c-92.9531 93.0234 -241.302 558.14 278.907 558.14c186.047 0 372.093 -46.5117 372.093 -279.069c0 -93.0234 -47.9531 -186.047 -47.9531 -186.047z" />
+    <glyph glyph-name="glyph78" unicode="&#xe064;" 
+d="M387.093 730.232c205.488 0 372.093 -166.604 372.093 -372.093s-166.604 -372.093 -372.093 -372.093s-372.093 166.604 -372.093 372.093s166.604 372.093 372.093 372.093zM387.814 266.651c51.0693 0 92.3018 41.2559 92.3018 92.209
+c0 51.0703 -41.2324 92.3027 -92.3018 92.3027c-50.8838 0 -92.21 -41.2559 -92.21 -92.3027c0 -50.9766 41.3262 -92.209 92.21 -92.209z" />
+    <glyph glyph-name="glyph79" unicode="&#x275e;" 
+d="M15 637.209h279.069v-279.069c0 -153.884 -125.186 -279.07 -279.069 -279.07v93.0234c102.651 0 186.047 83.4883 186.047 186.047h-186.047v279.069zM480.116 637.209h279.069v-279.069c0 -153.884 -125.186 -279.07 -279.069 -279.07v93.0234
+c102.651 0 186.047 83.4883 186.047 186.047h-186.047v279.069z" />
+    <glyph glyph-name="glyph80" unicode="&#xe01b;" 
+d="M387 234.14h0.0927734c0 -137 -111.093 -248.093 -248.093 -248.093c-45.0469 0 -87.209 12 -123.628 32.8828l-0.37207 0.46582c74.0469 42.8604 123.907 122.907 124 214.651l-0.0927734 0.0927734c0 68.4883 55.5117 124 124 124s124.093 -55.5117 124.093 -124z
+M731.93 702.977c36.3496 -36.3252 36.3496 -95.209 0 -131.535l-265.162 -265.162c-21.791 61.3955 -70.1162 109.744 -131.535 131.628l265.163 265.069c36.3252 36.3262 95.209 36.3262 131.534 0z" />
+    <glyph glyph-name="glyph81" unicode="&#x2601;" 
+d="M573.14 497.675c102.558 0 186.046 -83.4424 186.046 -186.047c0 -102.651 -83.4883 -186.047 -186.046 -186.047h-372.093c-102.559 0 -186.047 83.3955 -186.047 186.047c0 102.604 83.4883 186.047 186.047 186.047c8.25586 0 16.4414 -1.2334 24.6279 -2.32617
+c31.9766 56.6045 91.9297 95.3486 161.418 95.3486c68.0469 0 128 -38.2783 160.419 -95.6045c8.46484 1.16309 16.8135 2.58203 25.6279 2.58203zM573.14 218.604c51.3252 0 93.0234 41.7441 93.0234 93.0234s-41.6982 93.0234 -93.0234 93.0234
+c-51.3262 0 -93.0234 -41.7441 -93.0234 -93.0234h-93.0234c0 60.5811 29.5352 113.953 74.3955 147.931c-17.0693 23.209 -44.3252 38.1162 -74.3955 38.1162c-51.3252 0 -93.0234 -41.7441 -93.0234 -93.0234c0 -11.21 1.90723 -22.1631 5.81445 -32.4883
+l-87.1162 -32.6045c-7.62793 20.3486 -11.4424 41.8828 -11.6279 63.8135c-47.9766 -1.39551 -93.1162 -40.7441 -93.1162 -91.7441c0 -51.2793 41.6973 -93.0234 93.0234 -93.0234h372.093z" />
+    <glyph glyph-name="glyph82" unicode="&#xe025;" 
+d="M387.093 637.209c205.488 0 372.093 -275.441 372.093 -275.441s-166.604 -282.698 -372.093 -282.698s-372.093 282.698 -372.093 282.698s166.604 275.441 372.093 275.441zM387.093 172.093c102.744 0 186.047 83.3027 186.047 186.047
+s-83.3027 186.046 -186.047 186.046s-186.046 -83.3018 -186.046 -186.046s83.3018 -186.047 186.046 -186.047zM294.069 357.768c0 62.0156 31.0078 93.0234 93.0234 93.0234s93.0234 -31.0078 93.0234 -93.0234s-31.0078 -93.0234 -93.0234 -93.0234
+s-93.0234 31.0078 -93.0234 93.0234z" />
+    <glyph glyph-name="glyph83" unicode="&#xe048;" 
+d="M387.093 730.232c205.488 0 372.093 -166.604 372.093 -372.093s-166.604 -372.093 -372.093 -372.093s-372.093 166.604 -372.093 372.093s166.604 372.093 372.093 372.093zM247.559 172.093l372.278 186.047l-372.278 186.046v-372.093z" />
+    <glyph glyph-name="glyph84" unicode="&#xe041;" 
+d="M759.186 172.093h-558.325v-93.0234l-185.86 139.535l185.86 139.535v-93.0234h558.325v-93.0234zM15 451.163v93.0225h558.14v93.0234l186.046 -139.534l-186.046 -139.535v93.0234h-558.14z" />
+    <glyph glyph-name="glyph85" unicode="&#xe006;" 
+d="M731.93 702.977c36.3496 -36.3252 36.3496 -95.209 0 -131.535l-351.744 -351.837c-8.16211 30.6982 -23.4414 59.9541 -47.418 83.9307c-23.6045 23.6279 -52.6748 39.418 -84.1162 47.6045l351.744 351.837c36.3252 36.3262 95.209 36.3262 131.534 0zM267 106.232
+l-252 -120.186l120.465 251.721c36.3262 36.3252 95.21 36.3252 131.535 0c36.3252 -36.3262 36.3252 -95.209 0 -131.535z" />
+    <glyph glyph-name="glyph86" unicode="&#xe070;" 
+d="M387.093 265.116c0 62.0156 31.0078 93.0234 93.0234 93.0234s93.0234 -31.0078 93.0234 -93.0234s-31.0078 -93.0234 -93.0234 -93.0234s-93.0234 31.0078 -93.0234 93.0234zM666.163 544.186c25.7041 0 47.6338 -9.08105 65.7891 -27.2432
+c18.1553 -18.1611 27.2334 -40.0879 27.2334 -65.7793v-465.116h-744.186v465.116c0 25.6855 9.08203 47.6113 27.2471 65.7754c18.165 18.165 40.0898 27.2471 65.7764 27.2471h186.14l79.3486 153.838c3.04004 9.46191 8.61621 17.1953 16.7285 23.2012
+c8.1123 6.00488 17.2959 9.00781 27.5508 9.00781h127.721c10.1338 0 19.2324 -2.93359 27.2939 -8.80078c8.0625 -5.86719 13.6855 -13.4531 16.8691 -22.7568zM154.535 358.14c12.8486 0 23.8125 4.54004 32.8926 13.6191c9.0791 9.0791 13.6191 20.0439 13.6191 32.8926
+s-4.54004 23.8125 -13.6191 32.8926c-9.08008 9.0791 -20.0439 13.6191 -32.8926 13.6191s-23.8135 -4.54004 -32.8926 -13.6191c-9.0791 -9.08008 -13.6191 -20.0439 -13.6191 -32.8926s4.54004 -23.8135 13.6191 -32.8926s20.043 -13.6191 32.8926 -13.6191z
+M480.116 79.0693c51.3721 0 95.2227 18.165 131.553 54.4941c36.3291 36.3301 54.4941 80.1807 54.4941 131.553s-18.165 95.2227 -54.4941 131.553c-36.3301 36.3291 -80.1807 54.4941 -131.553 54.4941s-95.2227 -18.165 -131.553 -54.4941
+c-36.3291 -36.3301 -54.4941 -80.1807 -54.4941 -131.553s18.165 -95.2227 54.4941 -131.553c36.3301 -36.3291 80.1807 -54.4941 131.553 -54.4941z" />
+    <glyph glyph-name="glyph87" unicode="&#xe03a;" 
+d="M759.186 358.14c0 -205.488 -166.604 -372.093 -372.093 -372.093s-372.093 166.604 -372.093 372.093s166.604 372.093 372.093 372.093s372.093 -166.604 372.093 -372.093zM247.559 311.628h279.069v-46.5117l93.0234 93.0234l-93.0234 93.0234v-46.5117h-279.069
+v46.5117l-93.0234 -93.0234l93.0234 -93.0234v46.5117z" />
+    <glyph glyph-name="glyph88" unicode="&#x2935;" 
+d="M666.163 311.628v-46.5117h93.0225l-186.046 -186.047l-186.047 186.047h93.0234v46.5117c0 128.441 -104.116 232.558 -232.558 232.558c-128.442 0 -232.559 -104.116 -232.559 -232.558c0 179.768 145.814 325.581 325.581 325.581
+c179.768 0 325.582 -145.813 325.582 -325.581z" />
+    <glyph glyph-name="glyph89" unicode="&#xe039;" 
+d="M247.559 358.14l46.5107 -46.5117h-139.534v-93.0234l-139.535 139.535l139.535 139.535v-93.0234h139.534zM525.186 358.14l-45.0693 46.5117h139.535v93.0234l139.534 -139.535l-139.534 -139.535v93.0234h-139.535z" />
+    <glyph glyph-name="glyph90" unicode="&#xe026;" 
+d="M507.372 405.116l-188 318.303c22.0234 4.0459 44.5117 6.81348 67.7207 6.81348c97.9307 0 186.232 -38.5576 252.628 -100.372zM478.977 265.116l193.628 328.953c53.3262 -64.3721 86.5811 -145.86 86.5811 -235.93c0 -32.3486 -5.44141 -63.1396 -13.1855 -93.0234
+h-267.023zM398.721 497.675h-356.093c41.0469 101.256 125.675 178.906 230.325 212.837zM378.559 218.604h353.022c-40.6973 -100.372 -124.186 -177.512 -227.65 -212.023zM296.512 451.163l-194.163 -329.954c-53.7441 64.6055 -87.3486 146.442 -87.3486 236.931
+c0 32.3486 5.39551 63.1396 13.1631 93.0234h268.349zM268.907 312.535l188.907 -319.396c-22.9307 -4.37207 -46.5117 -7.09277 -70.7217 -7.09277c-97.4648 0 -185.418 38.3252 -251.768 99.5576z" />
+    <glyph glyph-name="glyph91" unicode="&#xe031;" horiz-adv-x="588" 
+d="M480.116 311.628h93.0234c0 -154.069 -125 -279.069 -279.07 -279.069c-154.069 0 -279.069 125 -279.069 279.069s125 279.069 279.069 279.069h93.5586v93.0234l139.813 -139.535l-139.813 -140v92.4893l-93.5586 1c-102.65 0 -186.046 -83.3955 -186.046 -186.047
+s83.3955 -186.047 186.046 -186.047c102.651 0 186.047 83.3955 186.047 186.047z" />
+    <glyph glyph-name="glyph92" unicode="&#xe074;" 
+d="M278.628 271.209c6.81348 -7.62793 13.9072 -14.7207 21.3486 -21.4414c-43.0693 -43.0703 -102.651 -102.675 -132.186 -132.279c-10.0938 10.1865 -11.4424 11.4424 -21.4424 21.4424c29.6279 29.5342 89.1396 89.209 132.279 132.278zM483.209 730.232
+c152.163 0 275.977 -123.721 275.954 -275.907c0 -152.069 -123.814 -275.79 -275.978 -275.79c-36.3252 0 -70.8604 7.44141 -102.65 20.3486l-212.744 -212.837l-152.791 152.884l212.651 212.674c-12.8145 31.791 -20.2559 66.3262 -20.2559 102.744
+c0 152.163 123.744 275.884 275.813 275.884zM483.209 271.465c100.931 0 183.047 82.1396 183.047 182.86c0 100.838 -82.1162 182.954 -183.047 182.954c-100.744 0 -182.86 -82.1162 -182.86 -182.954c0 -100.744 82.1162 -182.86 182.86 -182.86z" />
+    <glyph glyph-name="glyph93" unicode="&#xe06c;" 
+d="M619.651 637.209h139.534v-651.162h-744.186v651.162h139.535v-47.6045c0 -51.4189 41.6045 -93.0234 93.0234 -93.0234c51.418 0 93.0225 41.6045 93.0225 93.0234v47.6045h93.0234v-47.6045c0 -51.4189 41.6045 -93.0234 93.0234 -93.0234
+s93.0234 41.6045 93.0234 93.0234v47.6045zM247.559 79.0693v93.0234h-93.0234v-93.0234h93.0234zM247.559 265.116v93.0234h-93.0234v-93.0234h93.0234zM433.604 79.0693v93.0234h-93.0234v-93.0234h93.0234zM433.604 264.931v93.0225h-93.0234v-93.0225h93.0234z
+M526.628 79.0693l93.0234 93.0234h-93.0234v-93.0234zM619.651 265.116v93.0234h-93.0234v-93.0234h93.0234zM201.047 590.697v93.0234c0 25.6982 20.8135 46.5117 46.5117 46.5117c25.6973 0 46.5107 -20.8135 46.5107 -46.5117v-93.0234
+c0 -25.6973 -20.8135 -46.5117 -46.5107 -46.5117c-25.6982 0 -46.5117 20.8145 -46.5117 46.5117zM480.116 590.697v93.0234c0 25.6982 20.8145 46.5117 46.5117 46.5117s46.5117 -20.8135 46.5117 -46.5117v-93.0234c0 -25.6973 -20.8145 -46.5117 -46.5117 -46.5117
+s-46.5117 20.8145 -46.5117 46.5117z" />
+    <glyph glyph-name="glyph94" unicode="&#xe046;" horiz-adv-x="681" 
+d="M480.116 730.232h93.0234v-90.1162c0 -62.1396 -24.1631 -120.465 -68.1396 -164.419l-263.163 -261.628c-23.8838 -24 -37.3486 -55.0459 -39.79 -88.4883h92.0225l-139.534 -139.534l-139.535 139.534h94.1162c2.53516 58.2334 25.4424 112.744 67.0469 154.256
+l263.162 261.722c26.2559 26.2559 40.791 61.3018 40.791 98.5576v90.1162zM572.047 125.581h94.1162l-139.535 -139.534l-139.535 139.534h92.0234c-2.46484 33.4424 -15.9072 64.6045 -39.8838 88.4883l-52.1396 51.8613l65.9541 65.6045l51.8604 -51.5117
+c41.6045 -41.6982 64.6045 -96.209 67.1396 -154.442z" />
+    <glyph glyph-name="glyph95" unicode="&#xe06b;" 
+d="M15 -13.9531v465.116h744.186v-465.116h-744.186zM433.604 544.186v186.047h232.559l93.0225 -186.047h-325.581zM340.581 730.232v-186.047h-325.581l93.0234 186.047h232.558z" />
+    <glyph glyph-name="glyph96" unicode="&#xe068;" horiz-adv-x="495" 
+d="M411.988 662.093c90.8604 -90.79 90.8604 -238.093 -0.0234375 -328.86c0 0 -164.512 -161.163 -164.512 -347.209c0 186.046 -164.325 347.209 -164.325 347.209c-90.8369 90.7676 -90.8369 238.07 0 328.86c90.8604 90.8379 238.023 90.8379 328.86 0zM247.477 404.512
+c51.5117 0 93.1162 41.6045 93.1162 93.0234c0 51.418 -41.6045 93.0234 -93.1162 93.0234c-51.3252 0 -92.9297 -41.6055 -92.9297 -93.0234c0 -51.4189 41.6045 -93.0234 92.9297 -93.0234z" />
+    <glyph glyph-name="glyph97" unicode="&#xe00a;" horiz-adv-x="681" 
+d="M573.14 -13.9531v744.186h93.0234v-744.186h-93.0234zM387.093 -13.9531v558.139h93.0234v-558.139h-93.0234zM201.047 -13.9531v372.093h93.0225v-372.093h-93.0225zM15 -13.9531v186.046h93.0234v-186.046h-93.0234z" />
+    <glyph glyph-name="glyph98" unicode="&#xe072;" 
+d="M201.047 172.093c-102.744 0 -186.047 83.3027 -186.047 186.047s83.3027 186.046 186.047 186.046v-372.093zM294.069 544.186l186.047 93.0234v-558.14l-186.047 93.0234v372.093zM573.232 172.186v93.0234c7.7207 0 15.5352 1 23.3486 2.90723
+c40.791 10.3486 69.582 47.4189 69.582 90.0234s-28.791 79.5811 -70.0469 90.0234c-7.34863 1.81348 -15.1631 2.81348 -22.8838 2.81348v93.0234c15.2559 0 30.5117 -1.90723 45.5117 -5.53516c82.7676 -21.0693 140.441 -95.1162 140.441 -180.325
+c0 -85.3027 -57.6738 -159.419 -140.162 -180.325c-15.4424 -3.72168 -30.7207 -5.62891 -45.791 -5.62891z" />
+    <glyph glyph-name="glyph99" unicode="&#x2714;" 
+d="M387.093 730.232c205.488 0 372.093 -166.604 372.093 -372.093s-166.604 -372.093 -372.093 -372.093s-372.093 166.604 -372.093 372.093s166.604 372.093 372.093 372.093zM571.14 239.86l-118.279 118.279l118.279 118.279l-65.7676 65.7666l-118.279 -118.278
+l-118.279 118.278l-65.7666 -65.7666l118.278 -118.279l-118.278 -118.279l65.7666 -65.7676l118.279 118.279l118.279 -118.279z" />
+    <glyph glyph-name="glyph100" unicode="&#xe077;" 
+d="M745.529 716.604c18.1621 -18.1631 18.1621 -47.6045 -0.0234375 -65.7676l-94.9307 -94.7441c59.791 -90.209 49.9766 -213.116 -29.6045 -292.604l-139.256 -139.535c-45.4189 -45.418 -106.86 -68.1387 -164.419 -68.1387
+c-44.6973 0 -89.4883 12.8135 -128.278 38.6045l-94.6514 -94.7441c-9.09277 -9.09375 -20.9766 -13.6279 -32.8838 -13.6279s-23.791 4.53418 -32.8604 13.6279c-18.1631 18.1621 -18.1631 47.6045 0 65.7666l94.6514 94.7441
+c-59.791 90.21 -49.9775 213.117 29.6045 292.605l139.534 139.534c43.4658 45.4189 104.907 68.1396 164.419 68.1396c44.6982 0 89.209 -12.8135 128 -38.6045l94.6514 94.7441c18.1631 18.1631 47.8838 18.1631 66.0469 0zM596.087 427.907
+c0 21.1621 -5.06934 41.3252 -14 59.7676l-70.6748 -70.8604c-9.06934 -9.09375 -20.9766 -13.6279 -32.79 -13.6279c-11.9072 0 -23.791 4.53418 -32.8838 13.6279c-18.1631 18.1621 -18.1631 47.6045 0 65.7666l70.7676 70.8604c-18.5117 8.81445 -38.6045 14 -59.6748 14
+c-37.2559 0 -74.2559 -14.5342 -100.604 -40.8828l-137.581 -139.535c-26.3496 -26.3486 -40.8838 -61.3955 -40.8838 -98.6514c0 -21.1631 5.09277 -41.3252 14 -59.7676l69.3945 69.3955c18.1631 18.1631 47.6055 18.1631 63.8145 0
+c18.1631 -18.1631 18.1631 -47.6045 0 -65.7676l-67.4414 -69.3955c18.5342 -8.81348 38.6973 -14 59.7666 -14c35.3027 0 72.21 14.5352 98.6514 40.8838l139.256 139.535c26.3486 26.3486 40.8838 61.3955 40.8838 98.6514z" />
+    <glyph glyph-name="glyph101" unicode="&#xe03b;" horiz-adv-x="309" 
+d="M201.047 265.116v-139.535h93.0225l-139.534 -139.534l-139.535 139.534h93.0234v139.535h93.0234zM108.023 451.163v139.534h-93.0234l139.535 139.535l139.534 -139.535h-93.0225v-139.534h-93.0234z" />
+    <glyph glyph-name="glyph102" unicode="&#xe01e;" 
+d="M704.488 412l-13.6045 -13.7207l67.0459 -66.9072l-65.7666 -65.5811l-66.9541 66.9062l-346.396 -346.65h-172.418l-91.3955 92.8369v168.604l347.721 347.931l-69.1855 69.1855l65.5342 65.6279l69.1162 -69.2324l13.8145 13.7207
+c36.1396 36.2559 83.6973 54.4189 131.303 54.4189c47.3486 0 94.9297 -18.1631 131.186 -54.4189c72.6748 -72.4883 72.6748 -190.186 0 -262.721zM255 93.791l304.512 304.697l-131.14 131.14l-300.372 -300.559c32.3486 -1.09277 64.4883 -13.1855 89.1162 -37.8838
+c26.791 -26.8135 38.9766 -62.1387 37.8838 -97.3945z" />
+    <glyph glyph-name="glyph103" unicode="&#xe036;" horiz-adv-x="681" 
+d="M570.419 588.697c61.7666 -61.5811 95.7441 -143.441 95.7441 -230.558c0 -179.512 -146.07 -325.581 -325.582 -325.581v-46.5117l-93.0225 93.0225l93.0225 93.0234v-46.5117c128.279 0 232.559 104.279 232.559 232.559c0 62.2324 -24.3486 120.721 -68.3027 164.697z
+M433.604 637.209l-93.0234 -93.0234v46.5117c-128.278 0 -232.558 -104.278 -232.558 -232.558c0 -62.2324 24.3486 -120.721 68.3018 -164.698l-65.5811 -65.8604c-61.7676 61.582 -95.7441 143.442 -95.7441 230.559c0 179.512 146.069 325.581 325.581 325.581v46.5117z
+" />
+    <glyph glyph-name="glyph104" unicode="&#xe02c;" 
+d="M15 79.0693c0 62.0312 31.0156 93.0469 93.0469 93.0469c62.0303 0 93.0459 -31.0156 93.0459 -93.0469c0 -62.0303 -31.0156 -93.0459 -93.0459 -93.0459c-62.0312 0 -93.0469 31.0156 -93.0469 93.0459zM155.069 265.023v93.1162
+c64.1133 0 118.909 -22.7549 164.388 -68.2646s68.2178 -100.336 68.2178 -164.479h-93.0703c0 38.5186 -13.6357 71.4189 -40.9062 98.7031c-27.2705 27.2832 -60.1475 40.9248 -98.6289 40.9248zM154.441 451.163v93.0225c75.7012 0 145.709 -18.7051 210.023 -56.1143
+c64.3154 -37.4102 115.173 -88.2793 152.574 -152.608c37.4004 -64.3291 56.1006 -134.352 56.1006 -210.067h-93.0703c0 58.8857 -14.5469 113.351 -43.6416 163.393c-29.0938 50.042 -68.6504 89.6152 -118.671 118.719
+c-50.0195 29.1045 -104.458 43.6562 -163.315 43.6562zM154.441 637.209v93.0234c81.8926 0 160.163 -15.9893 234.812 -47.9668c74.6484 -31.9775 138.979 -74.9736 192.988 -128.988s97.0029 -118.354 128.979 -193.017
+c31.9766 -74.6641 47.9648 -152.952 47.9648 -234.865h-93.0225c0 69.3096 -13.5303 135.554 -40.5908 198.733c-27.0605 63.1807 -63.4434 117.625 -109.146 163.333c-45.7041 45.708 -100.14 82.0928 -163.305 109.155c-63.165 27.0605 -129.393 40.5918 -198.681 40.5918
+z" />
+    <glyph glyph-name="glyph105" unicode="&#x2139;" horiz-adv-x="402" 
+d="M61.5117 358.14h-46.5117v93.0234h279.069l0.186523 -325.582c0 -25.6973 20.8838 -46.5117 46.5117 -46.5117h46.3252v-93.0225h-372.093v93.0225h46.5117c25.6973 0 46.5117 20.8145 46.5117 46.5117v186.047c0 25.6973 -20.8145 46.5117 -46.5117 46.5117z
+M108.023 637.209c0 62.0156 31.0078 93.0234 93.0234 93.0234c62.0146 0 93.0225 -31.0078 93.0225 -93.0234s-31.0078 -93.0234 -93.0225 -93.0234c-62.0156 0 -93.0234 31.0078 -93.0234 93.0234z" />
+    <glyph glyph-name="glyph106" unicode="&#xe02a;" 
+d="M434.14 405.232v134.628c65.9531 -17.0234 117.558 -68.6738 134.628 -134.628h-134.628zM434.14 311.047h134.628c-17.0703 -65.9072 -68.6748 -117.605 -134.628 -134.582v134.582zM339.953 405.232h-134.628c16.9775 65.9531 68.6748 117.604 134.628 134.628
+v-134.628zM339.953 311.047v-134.582c-65.9531 16.9766 -117.65 68.6748 -134.628 134.582h134.628zM434.14 635.977v94.2559c170.069 -21.4883 303.512 -154.977 325.046 -325h-94.3018c-20 118 -112.744 210.768 -230.744 230.744zM109.209 405.232h-94.209
+c21.4414 170.023 154.977 303.512 324.953 325v-94.2559c-118.022 -19.9766 -210.767 -112.721 -230.744 -230.744zM339.953 80.3486v-94.3018c-169.977 21.4414 -303.512 154.977 -324.953 325h94.209c20 -118.047 112.744 -210.722 230.744 -230.698zM664.884 311.047
+h94.3018c-21.5342 -170.023 -154.977 -303.559 -325.046 -325v94.3018c118 19.9766 210.768 112.651 230.744 230.698z" />
+    <glyph glyph-name="glyph107" unicode="&#xe057;" 
+d="M759.186 79.0693l-93.0225 -93.0225l-325.582 325.674l-139.534 -139.535l-186.047 558.047l558.14 -186.047l-139.535 -139.534z" />
+    <glyph glyph-name="glyph108" unicode="&#x26bf;" 
+d="M526.628 730.232c128.441 0 232.558 -104.116 232.558 -232.558c0 -128.442 -104.116 -232.559 -232.558 -232.559c-14.5352 0 -28.5352 1.7207 -42.2324 4.2793l-4.2793 -4.2793v-93.0234h-93.0234v-93.0234h-93.0234v-93.0225h-279.069v186.046l283.349 283.349
+c-2.55762 13.6982 -4.2793 27.6982 -4.2793 42.2334c0 128.441 104.116 232.558 232.559 232.558zM526.813 451.163c25.6982 0 46.5117 20.8135 46.5117 46.5117c0 25.6973 -20.8135 46.5107 -46.5117 46.5107c-25.6973 0 -46.5107 -20.8135 -46.5107 -46.5107
+c0 -25.6982 20.8135 -46.5117 46.5107 -46.5117z" />
+    <glyph glyph-name="glyph109" unicode="&#x2796;" 
+d="M15 265.116v186.047h744.186v-186.047h-744.186z" />
+    <glyph glyph-name="glyph110" unicode="&#xe00b;" 
+d="M293.791 450.441v93.7441h185.86v-93.7441h-185.86zM665.535 543.093l92.9297 -93.0234h-92.9297v-464.022h-557.604c-51.3262 0 -92.9307 41.6045 -92.9307 93.0225v558.14c0 51.4189 41.6045 93.0234 92.9307 93.0234h557.604v-93.3955h93.6504zM572.604 79.0693
+h-0.0234375v558.14h-371.721v-558.14h371.744z" />
+    <glyph glyph-name="glyph111" unicode="&#xe061;" 
+d="M712.675 311.628c25.6973 0 46.5107 -20.8135 46.5107 -46.5117v-186.047c0 -25.6973 -20.8135 -46.5107 -46.5107 -46.5107h-139.535v279.069v93.0234c0 102.651 -83.3955 186.046 -186.047 186.046s-186.046 -83.3945 -186.046 -186.046v-93.0234v-279.069h-139.535
+c-25.6982 0 -46.5117 20.8135 -46.5117 46.5107v186.047c0 25.6982 20.8135 46.5117 46.5117 46.5117h46.5117v93.0234c0 154.069 125 279.069 279.069 279.069c154.07 0 279.07 -125 279.07 -279.069v-93.0234h46.5117z" />
+    <glyph glyph-name="numbersign" unicode="#" horiz-adv-x="681" 
+d="M666.163 451.163h-128l-23.1631 -186.047h151.163v-93.0234h-162.791l-23.3027 -186.046h-93.0225l23.2559 186.046h-186l-23.2559 -186.046h-93.0234l23.2559 186.046h-116.279v93.0234h127.907l23.209 186.047h-151.116v93.0225h162.744l23.2559 186.047h93.0234
+l-23.2559 -186.047h186l23.2559 186.047h93.0234l-23.2559 -186.047h116.372v-93.0225zM421.931 265.116l23.209 186.047h-186l-23.209 -186.047h186z" />
+    <glyph glyph-name="glyph113" unicode="&#xe013;" 
+d="M387.093 -13.9531c-205.488 0 -372.093 166.604 -372.093 372.046c0 205.535 166.604 372.14 372.093 372.14s372.093 -166.604 372.093 -372.14c0 -205.441 -166.604 -372.046 -372.093 -372.046zM386.372 544.186l-185.325 -186.093l185.325 -185.278v138.813h186.768
+v92.9766h-186.768v139.581z" />
+    <glyph glyph-name="glyph114" unicode="&#xe017;" 
+d="M387.093 -13.9531c-205.488 0 -372.093 166.604 -372.093 372.093s166.604 372.093 372.093 372.093s372.093 -166.604 372.093 -372.093s-166.604 -372.093 -372.093 -372.093zM247.559 451.163l-93.0234 -93.0234l93.0234 -93.0234v46.5117h372.093v93.0234h-372.093
+v46.5117z" />
+    <glyph glyph-name="glyph115" unicode="&#xe050;" 
+d="M586.768 92.6973l-106.651 -106.65v279.069h279.069l-106.65 -106.651l106.65 -106.65l-65.7666 -65.7676zM15 451.163l106.651 106.651l-105.559 105.65l65.7676 65.7676l105.559 -105.651l106.65 106.651v-279.069h-279.069zM15 51.8145l106.651 106.65
+l-106.651 106.651h279.069v-279.069l-106.65 106.65l-106.651 -106.65zM480.116 451.163v279.069l106.651 -106.651l105.744 105.651l65.7676 -65.7676l-105.744 -105.65l106.65 -106.651h-279.069z" />
+    <glyph glyph-name="glyph116" unicode="&#xe02e;" 
+d="M666.163 172.093c51.418 0 93.0225 -41.6973 93.0225 -93.0234c0 -51.3252 -41.6045 -93.0225 -93.0225 -93.0225c-51.4189 0 -93.0234 41.6973 -93.0234 93.0225c0 11.7217 2.7207 22.7217 6.7207 33.0703l-140.907 100.558
+c-25.2324 -25.0693 -60.0459 -40.6045 -98.3721 -40.6045c-77.0225 0 -139.534 62.5117 -139.534 139.535c0 7.25586 1.09277 14.2559 2.18555 21.2559l-111.372 37.1631c-8.18555 -7.18652 -18.6279 -11.9072 -30.3486 -11.9072
+c-25.6982 0 -46.5117 20.8135 -46.5117 46.5117c0 25.6973 20.8135 46.5117 46.5117 46.5117c22.2559 0 39.9766 -15.9072 44.5117 -36.791l112 -37.4189c23.5352 43.9541 69.3252 74.21 122.558 74.21c30.1631 0 57.7676 -9.72168 80.6748 -26.0703l165.884 165.977
+c-8.09277 13.791 -14 28.9775 -14 46.1396c0 51.4189 41.6045 93.0234 93.0234 93.0234c51.418 0 93.0225 -41.6045 93.0225 -93.0234c0 -51.418 -41.6045 -93.0234 -93.0225 -93.0234c-17.1631 0 -32.3496 5.90723 -46.1396 14l-165.884 -165.977
+c16.1631 -22.7207 25.9766 -50.5117 25.9766 -80.5811c0 -21.8838 -5.44141 -42.2324 -14.4414 -60.7676l141.813 -101.209c16.0703 13.3486 36.0469 22.4414 58.6748 22.4414z" />
+    <glyph glyph-name="glyph117" unicode="&#xe04f;" 
+d="M699.047 111.953l60.1387 60.1396v-186.046h-186.046l60.1396 60.1387l-153.163 153.163l65.7676 65.7676zM75.1396 604.325l-60.1396 -60.1396v186.047h186.047l-60.1396 -60.1396l153.162 -153.162l-65.7666 -65.7676zM633.279 670.093l-60.1396 60.1396h186.046
+v-186.047l-60.1387 60.1396l-153.163 -153.162l-65.7676 65.7676zM294.069 199.349l-153.162 -153.163l60.1396 -60.1387h-186.047v186.046l60.1396 -60.1396l153.163 153.163z" />
+    <glyph glyph-name="glyph118" unicode="&#xe004;" horiz-adv-x="681" 
+d="M340.581 730.232c179.768 0 325.582 -145.813 325.582 -325.581s-145.814 -325.582 -325.582 -325.582c-26.5342 0 -51.9531 4.00098 -76.6738 10.0938l-155.884 -103.116l1 190.046c-58.0469 58.8604 -94.0234 139.442 -94.0234 228.559
+c0 179.768 145.813 325.581 325.581 325.581z" />
+    <glyph glyph-name="glyph119" unicode="&#x263e;" 
+d="M587.86 214.325c62.6748 0 120.628 18 171.325 47.1631c-45.7666 -158.628 -190.488 -275.441 -363.906 -275.441c-210.047 0 -380.279 170.325 -380.279 380.349c0 173.419 116.744 318.046 275.441 363.813c-29.2559 -50.5811 -47.2324 -108.558 -47.2324 -171.232
+c0 -190.302 154.232 -344.651 344.651 -344.651z" />
+    <glyph glyph-name="at" unicode="@" 
+d="M709.047 172.093h-135.907c-30.3486 0 -56.1396 15.6279 -73.0469 38.1631c-31.418 -24.1631 -70.3018 -39.1631 -113 -39.1631c-102.744 0 -186.046 83.3027 -186.046 186.047s83.3018 186.046 186.046 186.046h186.047v-278.069h75.3018l0.744141 -1.09277
+c10.6279 29.4414 16.9775 61.0459 16.9775 94.1162c0 153.884 -125.187 279.069 -279.07 279.069s-279.069 -125.186 -279.069 -279.069s125.186 -279.07 279.069 -279.07c76.9541 0 146.722 31.2559 197.21 81.8613l65.8604 -65.8613
+c-67.3027 -67.418 -160.326 -109.022 -263.07 -109.022c-205.488 0 -372.093 166.604 -372.093 372.093s166.604 372.093 372.093 372.093s372.093 -166.604 372.093 -372.093c0 -67.9541 -18.2549 -131.279 -50.1387 -186.047zM480.116 404.465v45.6982h-93.0234
+c-51.2324 0 -93.0234 -41.6982 -93.0234 -93.0234s41.791 -93.0234 93.0234 -93.0234s93.0234 41.6982 93.0234 93.0234v47.3252z" />
+    <glyph glyph-name="glyph121" unicode="&#xe05e;" 
+d="M573.047 451.163c93.1162 0 186.139 -49.4189 186.139 -186.047c0 -133.721 -93.0225 -186.047 -186.139 -186.047h-92.9307c-51.3252 0 -93.0234 -41.6045 -93.0234 -93.0225c-102.744 0 -186.14 83.3018 -186.14 186.046v93.0234
+c0 37.0703 8.46582 67.4424 21.6279 93.0234h-21.6279c-92.9297 0 -185.953 52.3252 -185.953 186.046c0 136.629 93.0234 186.047 185.953 186.047h186.14c93.0234 0 185.954 -57.6045 185.954 -186.047v-93.0225zM200.953 451.163h93.1162
+c60.6748 0 114.651 -29.2559 148.628 -74.4424c22.791 16.9766 37.4189 44.0469 37.4189 74.4424v93.0225c0 80.9307 -58.2324 93.0234 -93.0234 93.0234h-186.14c-92.9297 0 -92.9297 -65.1396 -92.9297 -93.0234c0 -22.9766 0 -93.0225 92.9297 -93.0225zM573.047 172.093
+c93.1162 0 93.1162 69.9541 93.1162 93.0234c0 27.8369 0 93.0234 -93.1162 93.0234l-25.6982 0.0458984l0.0927734 0.046875c-32.2559 -55.418 -91.5811 -93.1162 -160.349 -93.1162c0 35.8838 -20.9766 66.1396 -50.79 81.6748
+c-21.0703 -13.2793 -42.2334 -39.1162 -42.2334 -81.6748v-93.0234c0 -30.3486 14.6279 -57.418 37.2559 -74.3955c33.9541 45.1396 88.0234 74.3955 148.791 74.3955h92.9307zM566.604 404.791c-0.62793 -2.13965 -1.37207 -4.23242 -2 -6.30273
+c0.62793 2.07031 1.44238 4.16309 2 6.30273z" />
+    <glyph glyph-name="glyph122" unicode="&#xe03d;" 
+d="M387.093 730.232c205.488 0 372.093 -166.604 372.093 -372.093s-166.604 -372.093 -372.093 -372.093s-372.093 166.604 -372.093 372.093s166.604 372.093 372.093 372.093zM433.604 218.604v279.07h46.5117l-93.0234 93.0225l-93.0234 -93.0225h46.5117v-279.07
+h-46.5117l93.0234 -93.0234l93.0234 93.0234h-46.5117z" />
+    <glyph glyph-name="glyph123" unicode="&#xe03c;" horiz-adv-x="309" 
+d="M154.535 218.604l46.5117 46.5117v-139.535h93.0225l-139.534 -139.534l-139.535 139.534h93.0234v139.535zM154.535 496.232l-46.5117 -45.0693v139.534h-93.0234l139.535 139.535l139.534 -139.535h-93.0225v-139.534z" />
+    <glyph glyph-name="glyph124" unicode="&#x2717;" 
+d="M292.697 58.6748l-277.697 277.697l132.441 132.465l145.256 -145.256l334.047 334.023l132.441 -132.441z" />
+    <glyph glyph-name="glyph125" unicode="&#xe05f;" horiz-adv-x="588" 
+d="M294.069 358.14c-51.418 0 -93.0225 41.6514 -93.0225 93.0234v186.046c0 51.3721 41.6045 93.0234 93.0225 93.0234c51.4189 0 93.0234 -41.6514 93.0234 -93.0234v-186.046c0 -51.3721 -41.6045 -93.0234 -93.0234 -93.0234zM387.093 79.0693
+c51.4189 0 93.0234 -41.6045 93.0234 -93.0225h-372.093c0 51.418 41.6045 93.0225 93.0234 93.0225h46.5117v97.7451c-131.722 22.2549 -232.559 136.395 -232.559 274.349v45.0693c0 25.6973 20.8135 46.5117 46.5117 46.5117
+c25.6973 0 46.5117 -20.8145 46.5117 -46.5117v-45.0693c0 -102.604 83.3955 -186.047 186.046 -186.047c102.651 0 186.047 83.4424 186.047 186.047v45.0693c0 25.6973 20.8145 46.5117 46.5117 46.5117s46.5117 -20.8145 46.5117 -46.5117v-45.0693
+c0 -137.954 -100.837 -252.094 -232.559 -274.349v-97.7451h46.5117z" />
+    <glyph glyph-name="glyph126" unicode="&#xe069;" horiz-adv-x="681" 
+d="M108.023 730.232h558.14v-744.186h-558.14c-51.3721 0 -93.0234 41.6045 -93.0234 93.0225v558.14c0 51.4189 41.6514 93.0234 93.0234 93.0234zM573.14 79.0693v558.14h-92.6748v-187.674l-93.7441 93.7441l-93.0234 -93.0234v186.953h-92.6504v-558.14h372.093z" />
+    <glyph glyph-name="glyph127" unicode="&#xe03f;" 
+d="M387.093 218.604l46.5117 46.5117v-139.535h93.0234l-139.535 -139.534l-139.534 139.534h93.0225v139.535zM387.093 496.232l-46.5117 -45.0693v139.534h-93.0225l139.534 139.535l139.535 -139.535h-93.0234v-139.534zM247.559 358.14l46.5107 -46.5117h-139.534
+v-93.0234l-139.535 139.535l139.535 139.535v-93.0234h139.534zM525.186 358.14l-45.0693 46.5117h139.535v93.0234l139.534 -139.535l-139.534 -139.535v93.0234h-139.535z" />
+    <glyph glyph-name="glyph128" unicode="&#xe040;" 
+d="M387.093 730.232c205.488 0 372.093 -166.604 372.093 -372.093s-166.604 -372.093 -372.093 -372.093s-372.093 166.604 -372.093 372.093s166.604 372.093 372.093 372.093zM526.628 265.116l93.0234 93.0234l-93.0234 93.0234v-46.5117h-93.0234v93.0234h46.5117
+l-93.0234 93.0225l-93.0234 -93.0225h46.5117v-93.0234h-93.0225v46.5117l-93.0234 -93.0234l93.0234 -93.0234v46.5117h93.0225v-93.0234h-46.5117l93.0234 -93.0234l93.0234 93.0234h-46.5117v93.0234h93.0234v-46.5117z" />
+    <glyph glyph-name="glyph129" unicode="&#xe066;" 
+d="M517.535 730.232l241.65 -239.558v-504.628l-186.139 154.256l-186.14 -154.256l-186.396 154.441l-185.512 -154.441v744.186h502.535zM433.604 404.651h232.559l-232.559 232.558v-232.558z" />
+    <glyph glyph-name="glyph130" unicode="&#x2795;" 
+d="M759.186 451.163v-186.047h-279.069v-279.069h-186.047v279.069h-279.069v186.047h279.069v279.069h186.047v-279.069h279.069z" />
+    <glyph glyph-name="glyph131" unicode="&#xe078;" 
+d="M750 599c5.46484 -17.4414 9.18555 -35.6045 9.18555 -54.8145c0 -102.697 -83.3018 -186.046 -186.046 -186.046c-28.6045 0 -55.5117 7.0459 -79.6748 18.5811l-372.628 -372.512c-11.0928 -11.2559 -26.6279 -18.1621 -43.79 -18.1621
+c-34.3496 0 -62.0469 27.79 -62.0469 61.9531c0 17.2559 7 32.6973 18.1631 43.9766l372.558 372.442c-11.6279 24.3018 -18.6279 51.1162 -18.6279 79.7666c0 102.698 83.3027 186.047 186.047 186.047c18.6279 0 36.3252 -3.58105 53.3252 -8.7207l-115.372 -115.326
+v-123.999h122.093z" />
+    <glyph glyph-name="glyph132" unicode="&#xe047;" horiz-adv-x="588" 
+d="M15 730.232l558.14 -372.093l-558.14 -372.093v744.186z" />
+    <glyph glyph-name="glyph133" unicode="&#x2605;" 
+d="M529.813 273.837l90.0234 -287.79l-232.372 178.604l-232.93 -178.604l90.1162 288.512l-229.651 176.604h279.069l93.0234 279.069l93.0234 -279.069h279.069z" />
+    <glyph glyph-name="glyph134" unicode="&#xe000;" horiz-adv-x="588" 
+d="M340.581 730.232l232.559 -232.512v-511.674h-558.14v744.186h325.581zM292.163 451.163h186.046l-186.046 186.046v-186.046z" />
+    <glyph glyph-name="glyph135" unicode="&#xe028;" 
+d="M334.553 279.116l-232.744 -232.558c-115.744 133.813 -115.744 331.302 0 465.116zM478.133 633.535c159.35 -23.3955 281.07 -156.396 281.07 -320.535c0 -180.697 -146.441 -326.953 -329.232 -326.953c-70.7207 0 -136.488 25.6279 -190.629 66.0459l238.791 240.838
+v340.604zM387.064 357.768l-266.908 266.953c73.9531 60.4424 165.535 99.2324 266.908 105.512v-372.465z" />
+    <glyph glyph-name="glyph136" unicode="&#x26c6;" 
+d="M573.151 590.697c102.558 0 186.046 -83.4414 186.046 -186.046s-83.4883 -186.047 -186.046 -186.047h-372.093c-102.559 0 -186.047 83.4424 -186.047 186.047s83.4883 186.046 186.047 186.046c8.25586 0 16.4414 -1.23242 24.6279 -2.3252
+c31.9766 56.6045 91.9297 95.3486 161.418 95.3486c68.0469 0 128 -38.2793 160.419 -95.6045c8.46484 1.16309 16.8135 2.58105 25.6279 2.58105zM573.151 311.628c51.3252 0 93.0234 41.7441 93.0234 93.0234s-41.6982 93.0234 -93.0234 93.0234
+c-51.3262 0 -93.0234 -41.7441 -93.0234 -93.0234h-93.0234c0 60.5811 29.5352 113.953 74.3955 147.93c-17.0693 23.21 -44.3252 38.1162 -74.3955 38.1162c-51.3252 0 -93.0234 -41.7441 -93.0234 -93.0225c0 -11.21 1.90723 -22.1631 5.81445 -32.4893l-87.1162 -32.6045
+c-7.62793 20.3496 -11.4424 41.8838 -11.6279 63.8145c-47.9766 -1.39551 -93.1162 -40.7441 -93.1162 -91.7441c0 -51.2793 41.6973 -93.0234 93.0234 -93.0234h372.093zM91.3145 45.6279c-17.4424 -17.4424 -45.791 -17.4424 -63.2334 0
+c-17.4414 17.4414 -17.4414 45.791 0 63.2324c17.4424 17.4424 126.466 63.2324 126.466 63.2324s-45.791 -109.023 -63.2324 -126.465zM277.453 45.6279c-17.4414 -17.4424 -45.6973 -17.4424 -63.2324 0c-17.4414 17.4414 -17.4414 45.791 0 63.2324
+c17.5352 17.4424 126.466 63.2324 126.466 63.2324s-45.6982 -109.023 -63.2334 -126.465zM463.407 45.6279c-17.4424 -17.4424 -45.791 -17.4424 -63.2324 0c-17.4424 17.4414 -17.4424 45.791 0 63.2324c17.4414 17.4424 126.465 63.2324 126.465 63.2324
+s-45.791 -109.023 -63.2324 -126.465z" />
+    <glyph glyph-name="glyph137" unicode="&#xe065;" 
+d="M759.186 32.5586c0 -25.791 -20.8135 -46.5117 -46.5107 -46.5117h-651.163c-25.6982 0 -46.5117 20.7207 -46.5117 46.5117v418.604h744.186v-418.604zM294.069 637.163h465.116v-92.9775h-744.186v186.047h279.069v-93.0693z" />
+    <glyph glyph-name="glyph138" unicode="&#xe059;" 
+d="M759.186 730.232v-558.14h-186.046v-186.046h-558.14v558.139h186.047v186.047h558.139zM480.116 79.0693v93.0234h-279.069v186.047h-93.0234v-279.07h372.093zM666.163 265.116v279.069h-372.094v-279.069h372.094z" />
+    <glyph glyph-name="glyph139" unicode="&#xe062;" horiz-adv-x="588" 
+d="M294.069 358.14c154.07 0 279.07 -125 279.07 -279.07c0 -51.418 -41.6045 -93.0225 -93.0234 -93.0225h-372.093c-51.4189 0 -93.0234 41.6045 -93.0234 93.0225c0 154.07 125 279.07 279.069 279.07zM154.535 590.697c0 93.0234 46.5117 139.535 139.534 139.535
+c93.0234 0 139.535 -46.5117 139.535 -139.535c0 -93.0225 -46.5117 -139.534 -139.535 -139.534c-93.0225 0 -139.534 46.5117 -139.534 139.534z" />
+    <glyph glyph-name="glyph140" unicode="&#xe05b;" 
+d="M759.186 451.163v-186.047h-93.0225v-46.5117c0 -25.6279 -20.8838 -46.5117 -46.5117 -46.5117h-558.14c-25.6279 0 -46.5117 20.8838 -46.5117 46.5117v279.07c0 25.5811 20.8838 46.5107 46.5117 46.5107h558.14c25.6279 0 46.5117 -20.9297 46.5117 -46.5107
+v-46.5117h93.0225zM573.14 265.116v186.047h-186.047v-186.047h186.047z" />
+    <glyph glyph-name="glyph141" unicode="&#xe00c;" 
+d="M719.86 521.559c24.4189 -49.5117 39.3252 -104.512 39.3252 -163.419c0 -23.4424 -2.7207 -46.1396 -6.90625 -68.5117h-264.349zM504.651 375.814v333.604c78.1162 -26.1631 145.349 -75.9775 190.768 -142.931zM404.488 240.581h333.884
+c-26.1631 -78.209 -76.0234 -145.534 -143.069 -190.86zM455.535 457.116l-233.094 233.093c49.8145 24.8145 105.233 40.0234 164.651 40.0234c23.4424 0 46.1865 -2.76758 68.4424 -6.90723v-266.209zM318.651 256.931l231.697 -231.628
+c-49.5117 -24.3496 -104.396 -39.2559 -163.256 -39.2559c-23.4414 0 -46.1855 2.7207 -68.4414 6.88379v264zM269.488 340.163v-333.326c-78.0234 26.1631 -145.209 75.9541 -190.535 142.814zM21.9072 426.581v0.0234375h264.581l-232.069 -232.069
+c-24.4189 49.5117 -39.4189 104.558 -39.4189 163.604c0 23.4414 2.76758 46.1855 6.90723 68.4414zM177.697 665.559l189.814 -189.814h-331.721c26.0459 77.6748 75.5117 144.488 141.906 189.814z" />
+    <glyph glyph-name="glyph142" unicode="&#xe04b;" 
+d="M15 265.116l372.093 465.116l372.093 -465.116h-744.186zM15 -13.9531v186.046h743.186v-186.046h-743.186z" />
+    <glyph glyph-name="glyph143" unicode="&#xe016;" 
+d="M759.186 358.187c0 -205.535 -166.604 -372.14 -372.093 -372.14s-372.093 166.604 -372.093 372.14c0 205.441 166.604 372.046 372.093 372.046s372.093 -166.604 372.093 -372.046zM201.047 357.465l186.046 -185.372l185.326 185.372h-138.814v186.721h-93.0234
+v-186.721h-139.534z" />
+    <glyph glyph-name="paragraph" unicode="&#xb6;" horiz-adv-x="588" 
+d="M573.14 730.232v-93.0234h-93.0234v-651.162h-93.0234v651.162h-93.0234v-651.162h-93.0225v372.093c-102.744 0 -186.047 83.3018 -186.047 186.046c0 102.745 83.3027 186.047 186.047 186.047h93.0225h93.0234h93.0234h93.0234z" />
+    <glyph glyph-name="glyph145" unicode="&#xe01a;" 
+d="M15 358.14c0 205.488 166.604 372.093 372.093 372.093s372.093 -166.604 372.093 -372.093s-166.604 -372.093 -372.093 -372.093s-372.093 166.604 -372.093 372.093zM480.116 497.675l-93.0234 93.0225l-93.0234 -93.0225h46.5117v-372.094h93.0234v372.094h46.5117z
+" />
+    <glyph glyph-name="glyph146" unicode="&#xe015;" 
+d="M387.093 730.232c205.488 0 372.093 -166.604 372.093 -372.093s-166.604 -372.093 -372.093 -372.093s-372.093 166.604 -372.093 372.093s166.604 372.093 372.093 372.093zM387.814 172.093l185.325 186.047l-185.325 185.325v-138.813h-186.768v-93.0234h186.768
+v-139.535z" />
+    <glyph glyph-name="glyph147" unicode="&#xe019;" 
+d="M387.093 730.232c205.488 0 372.093 -166.604 372.093 -372.093s-166.604 -372.093 -372.093 -372.093s-372.093 166.604 -372.093 372.093s166.604 372.093 372.093 372.093zM526.628 265.116l93.0234 93.0234l-93.0234 93.0234v-46.5117h-372.093v-93.0234h372.093
+v-46.5117z" />
+    <glyph glyph-name="glyph148" unicode="&#xe02d;" 
+d="M759.186 -13.9531h-106.278c0 351.744 -286.163 637.86 -637.907 637.86v106.325c410.256 0 744.186 -333.837 744.186 -744.186zM546.512 -13.9531h-106.279c0 234.465 -190.86 425.232 -425.232 425.232v106.325c293.069 0 531.512 -238.488 531.512 -531.558z
+M333.953 -13.9531h-106.372c0 117.278 -95.3018 212.581 -212.581 212.581v106.372c175.86 0 318.953 -143.093 318.953 -318.953zM121.279 -13.9531h-106.279v106.278c58.7676 0 106.279 -47.6045 106.279 -106.278z" />
+    <glyph glyph-name="glyph149" unicode="&#xe037;" 
+d="M124.093 94.8838c-70.3955 70.3018 -109.093 163.697 -109.093 263.256c0 205.116 166.884 372.093 372.093 372.093l46.5117 -46.5117l-46.5117 -46.5117c-153.884 0 -279.069 -125.186 -279.069 -279.069c0 -74.6748 29.0693 -144.814 81.8604 -197.488
+l-57.1396 -7.44238zM387.093 -13.9531l-46.5117 46.3252l46.5117 46.6973c153.884 0 279.07 125.187 279.07 279.07c0 74.4883 -29.0703 144.628 -81.9307 197.396l57.2324 7.53418l8.53516 58.2334c70.3955 -70.3027 109.186 -163.791 109.186 -263.163
+c0 -205.116 -166.977 -372.093 -372.093 -372.093z" />
+    <glyph glyph-name="glyph150" unicode="&#x2600;" 
+d="M387.093 544.186c102.651 0 186.047 -83.3018 186.047 -186.046s-83.3955 -186.047 -186.047 -186.047c-102.837 0 -186.046 83.3027 -186.046 186.047s83.209 186.046 186.046 186.046zM433.604 683.721c0 -25.6973 -20.8838 -46.5117 -46.5117 -46.5117
+c-25.79 0 -46.5117 20.8145 -46.5117 46.5117c0 25.6982 20.7217 46.5117 46.5117 46.5117c25.6279 0 46.5117 -20.8135 46.5117 -46.5117zM108.023 590.697c0 31.0078 15.5039 46.5117 46.5117 46.5117s46.5117 -15.5039 46.5117 -46.5117
+s-15.5039 -46.5117 -46.5117 -46.5117s-46.5117 15.5039 -46.5117 46.5117zM61.5117 404.697c25.6279 0 46.5117 -20.8135 46.5117 -46.5107c0 -25.7451 -20.8838 -46.5117 -46.5117 -46.5117c-25.791 0 -46.5117 20.7666 -46.5117 46.5117
+c0 25.6973 20.7207 46.5107 46.5117 46.5107zM108.023 125.581c0 31.0078 15.5039 46.5117 46.5117 46.5117s46.5117 -15.5039 46.5117 -46.5117s-15.5039 -46.5117 -46.5117 -46.5117s-46.5117 15.5039 -46.5117 46.5117zM340.581 32.5586
+c0 25.79 20.7217 46.5107 46.5117 46.5107c25.791 0 46.5117 -20.7207 46.5117 -46.5107c0 -25.6982 -20.7207 -46.5117 -46.5117 -46.5117c-25.79 0 -46.5117 20.8135 -46.5117 46.5117zM586.953 92.6973c-18.1621 18.1631 -18.1621 47.6055 0 65.7676
+c18.1631 18.1631 47.6055 18.1631 65.7676 0c18.3486 -18.1621 18.3486 -47.6045 0 -65.7676c-18.1621 -18.1621 -47.6045 -18.1621 -65.7676 0zM712.581 311.675c-25.5342 0 -46.418 20.79 -46.418 46.4648c0 25.791 20.8838 46.5117 46.6973 46.5117
+c25.5352 0 46.5117 -20.7676 46.2324 -46.4648c0.279297 -25.7451 -20.7207 -46.5586 -46.5117 -46.5117zM652.721 557.768c-18.1621 -18.1162 -47.6045 -18.1162 -65.7676 0c-18.1621 18.2559 -18.1621 47.6045 0 65.8135
+c18.1631 18.1162 47.6055 18.1631 65.7676 -0.0458984c18.3486 -18.1631 18.3486 -47.6514 0 -65.7676z" />
+  </font>
+</defs></svg>
diff --git a/src/main/resources/bootstrap/font/iconic_fill.ttf b/src/main/resources/bootstrap/font/iconic_fill.ttf
new file mode 100644
index 0000000..8334254
--- /dev/null
+++ b/src/main/resources/bootstrap/font/iconic_fill.ttf
Binary files differ
diff --git a/src/main/resources/bootstrap/font/iconic_fill.woff b/src/main/resources/bootstrap/font/iconic_fill.woff
new file mode 100644
index 0000000..9596d8e
--- /dev/null
+++ b/src/main/resources/bootstrap/font/iconic_fill.woff
Binary files differ
diff --git a/src/main/resources/bootstrap/font/iconic_stroke.afm b/src/main/resources/bootstrap/font/iconic_stroke.afm
new file mode 100644
index 0000000..bf77283
--- /dev/null
+++ b/src/main/resources/bootstrap/font/iconic_stroke.afm
@@ -0,0 +1,170 @@
+StartFontMetrics 2.0
+Comment Generated by FontForge 20110222
+Comment Creation Date: Sun Apr  1 19:42:24 2012
+FontName IconicStroke
+FullName Iconic Stroke
+FamilyName Iconic
+Weight Medium
+Notice (Icons by PJ Onori, font creation script by Yann)
+ItalicAngle 0
+IsFixedPitch false
+UnderlinePosition -100
+UnderlineThickness 50
+Version 001.000
+EncodingScheme ISOLatin1Encoding
+FontBBox 14 -14 760 731
+Descender -2147483648
+StartCharMetrics 151
+C 35 ; WX 681 ; N numbersign ; B 15 -14 667 731 ;
+C 63 ; WX 402 ; N question ; B 15 -14 388 731 ;
+C 64 ; WX 774 ; N at ; B 15 -14 760 731 ;
+C 182 ; WX 588 ; N paragraph ; B 15 -14 574 731 ;
+C -1 ; WX 495 ; N glyph0 ; B 15 -14 481 731 ;
+C -1 ; WX 774 ; N glyph1 ; B 15 -14 760 731 ;
+C -1 ; WX 495 ; N glyph2 ; B 15 -14 481 731 ;
+C -1 ; WX 774 ; N glyph3 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph4 ; B 15 -14 760 731 ;
+C -1 ; WX 681 ; N glyph5 ; B 15 -14 667 730 ;
+C -1 ; WX 774 ; N glyph6 ; B 15 -14 760 731 ;
+C -1 ; WX 309 ; N glyph7 ; B 15 -14 295 731 ;
+C -1 ; WX 774 ; N glyph8 ; B 15 32 760 684 ;
+C -1 ; WX 774 ; N glyph9 ; B 15 32 760 684 ;
+C -1 ; WX 588 ; N glyph10 ; B 15 -14 574 731 ;
+C -1 ; WX 774 ; N glyph11 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph12 ; B 15 -14 760 731 ;
+C -1 ; WX 728 ; N glyph13 ; B 14 -14 713 731 ;
+C -1 ; WX 774 ; N glyph14 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph15 ; B 15 -14 760 731 ;
+C -1 ; WX 681 ; N glyph16 ; B 15 -14 667 731 ;
+C -1 ; WX 774 ; N glyph17 ; B 15 79 760 638 ;
+C -1 ; WX 774 ; N glyph18 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph19 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph20 ; B 15 172 760 545 ;
+C -1 ; WX 774 ; N glyph21 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph22 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph23 ; B 15 218 760 498 ;
+C -1 ; WX 774 ; N glyph24 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph25 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph26 ; B 15 172 760 545 ;
+C -1 ; WX 402 ; N glyph27 ; B 15 -14 388 731 ;
+C -1 ; WX 588 ; N glyph28 ; B 15 -14 574 731 ;
+C -1 ; WX 588 ; N glyph29 ; B 15 -14 574 731 ;
+C -1 ; WX 774 ; N glyph31 ; B 15 32 760 684 ;
+C -1 ; WX 588 ; N glyph32 ; B 15 -14 574 731 ;
+C -1 ; WX 588 ; N glyph33 ; B 15 32 574 684 ;
+C -1 ; WX 774 ; N glyph34 ; B 15 79 760 638 ;
+C -1 ; WX 774 ; N glyph35 ; B 15 79 760 638 ;
+C -1 ; WX 774 ; N glyph36 ; B 15 32 760 684 ;
+C -1 ; WX 774 ; N glyph37 ; B 15 79 760 638 ;
+C -1 ; WX 774 ; N glyph38 ; B 15 79 760 638 ;
+C -1 ; WX 681 ; N glyph39 ; B 15 32 667 684 ;
+C -1 ; WX 774 ; N glyph40 ; B 15 -14 760 731 ;
+C -1 ; WX 588 ; N glyph41 ; B 15 -14 574 731 ;
+C -1 ; WX 774 ; N glyph42 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph43 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph44 ; B 15 -14 760 731 ;
+C -1 ; WX 588 ; N glyph45 ; B 15 -14 574 731 ;
+C -1 ; WX 774 ; N glyph46 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph47 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph48 ; B 15 79 760 638 ;
+C -1 ; WX 774 ; N glyph49 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph50 ; B 15 -12 760 729 ;
+C -1 ; WX 774 ; N glyph51 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph52 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph53 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph54 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph55 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph56 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph57 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph58 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph59 ; B 15 79 760 638 ;
+C -1 ; WX 774 ; N glyph60 ; B 15 79 760 638 ;
+C -1 ; WX 774 ; N glyph61 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph62 ; B 15 32 760 684 ;
+C -1 ; WX 774 ; N glyph63 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph64 ; B 15 32 760 684 ;
+C -1 ; WX 774 ; N glyph65 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph66 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph67 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph68 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph69 ; B 15 -14 760 731 ;
+C -1 ; WX 402 ; N glyph70 ; B 15 -14 388 731 ;
+C -1 ; WX 402 ; N glyph71 ; B 15 -14 388 731 ;
+C -1 ; WX 774 ; N glyph72 ; B 15 172 760 545 ;
+C -1 ; WX 774 ; N glyph73 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph74 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph75 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph76 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph77 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph78 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph79 ; B 14 79 760 638 ;
+C -1 ; WX 774 ; N glyph80 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph81 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph82 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph83 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph84 ; B 15 79 760 638 ;
+C -1 ; WX 774 ; N glyph85 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph86 ; B 15 125 760 591 ;
+C -1 ; WX 774 ; N glyph87 ; B 15 79 760 638 ;
+C -1 ; WX 774 ; N glyph88 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph89 ; B 15 79 760 638 ;
+C -1 ; WX 774 ; N glyph90 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph91 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph92 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph93 ; B 15 79 760 638 ;
+C -1 ; WX 774 ; N glyph94 ; B 15 218 760 498 ;
+C -1 ; WX 774 ; N glyph95 ; B 15 -14 760 731 ;
+C -1 ; WX 588 ; N glyph96 ; B 15 32 574 684 ;
+C -1 ; WX 774 ; N glyph97 ; B 15 -14 760 731 ;
+C -1 ; WX 588 ; N glyph98 ; B 15 -14 574 731 ;
+C -1 ; WX 681 ; N glyph99 ; B 15 -14 667 731 ;
+C -1 ; WX 774 ; N glyph100 ; B 15 -14 760 731 ;
+C -1 ; WX 681 ; N glyph101 ; B 15 -14 667 731 ;
+C -1 ; WX 774 ; N glyph102 ; B 15 32 760 684 ;
+C -1 ; WX 774 ; N glyph103 ; B 15 79 760 638 ;
+C -1 ; WX 774 ; N glyph104 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph105 ; B 14 -14 760 731 ;
+C -1 ; WX 774 ; N glyph106 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph107 ; B 15 -14 759 731 ;
+C -1 ; WX 681 ; N glyph108 ; B 15 -14 667 731 ;
+C -1 ; WX 774 ; N glyph109 ; B 15 -14 760 731 ;
+C -1 ; WX 402 ; N glyph110 ; B 15 -14 388 731 ;
+C -1 ; WX 774 ; N glyph111 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph112 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph113 ; B 15 265 760 452 ;
+C -1 ; WX 774 ; N glyph114 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph115 ; B 15 32 760 684 ;
+C -1 ; WX 774 ; N glyph117 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph118 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph119 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph120 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph121 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph123 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph124 ; B 15 -14 760 731 ;
+C -1 ; WX 309 ; N glyph125 ; B 15 -14 295 731 ;
+C -1 ; WX 774 ; N glyph126 ; B 15 58 760 658 ;
+C -1 ; WX 588 ; N glyph127 ; B 15 -14 574 731 ;
+C -1 ; WX 774 ; N glyph128 ; B 15 -14 760 731 ;
+C -1 ; WX 681 ; N glyph129 ; B 15 -14 667 731 ;
+C -1 ; WX 774 ; N glyph130 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph131 ; B 15 -14 760 731 ;
+C -1 ; WX 402 ; N glyph132 ; B 15 -14 388 731 ;
+C -1 ; WX 774 ; N glyph133 ; B 15 -14 760 731 ;
+C -1 ; WX 588 ; N glyph134 ; B 15 -14 574 731 ;
+C -1 ; WX 774 ; N glyph135 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph136 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph137 ; B 15 32 760 684 ;
+C -1 ; WX 774 ; N glyph138 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph139 ; B 15 -14 760 731 ;
+C -1 ; WX 588 ; N glyph140 ; B 15 -14 574 731 ;
+C -1 ; WX 774 ; N glyph141 ; B 15 172 760 545 ;
+C -1 ; WX 774 ; N glyph142 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph143 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph144 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph146 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph147 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph148 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph149 ; B 15 -14 760 731 ;
+C -1 ; WX 774 ; N glyph150 ; B 15 -14 760 731 ;
+EndCharMetrics
+EndFontMetrics
diff --git a/src/main/resources/bootstrap/font/iconic_stroke.css b/src/main/resources/bootstrap/font/iconic_stroke.css
new file mode 100644
index 0000000..f311e41
--- /dev/null
+++ b/src/main/resources/bootstrap/font/iconic_stroke.css
@@ -0,0 +1 @@
+@font-face { font-family: 'IconicStroke'; src: url('iconic_stroke.eot'); src: url('iconic_stroke.eot?#iefix') format('embedded-opentype'), url('iconic_stroke.ttf') format('truetype'), url('iconic_stroke.svg#iconic') format('svg'); font-weight: normal; font-style: normal; }.iconic { display:inline-block; font-family: 'IconicStroke'; }.lightbulb:before {content:'\e063';}.equalizer:before {content:'\e052';}.map_pin_stroke:before {content:'\e068';}.brush_alt:before {content:'\e01c';}.move:before {content:'\e03e';}.paperclip:before {content:'\e08a';}.pen_alt_stroke:before {content:'\e005';}.move_vertical:before {content:'\e03b';}.book_alt2:before {content:'\e06a';}.layers:before {content:'\e01f';}.pause:before {content:'\e049';}.layers_alt:before {content:'\e020';}.cloud_upload:before {content:'\e045';}.chart_alt:before {content:'\e029';}.fullscreen_exit_alt:before {content:'\e051';}.cloud_download:before {content:'\e044';}.comment_alt2_stroke:before {content:'\e004';}.mail:before {content:'\2709';}.check_alt:before {content:'\2714';}.document_stroke:before {content:'\e066';}.battery_charging:before {content:'\e05d';}.stop:before {content:'\e04a';}.arrow_up:before {content:'\2191';}.move_horizontal:before {content:'\e038';}.compass:before {content:'\e021';}.minus_alt:before {content:'\e009';}.battery_empty:before {content:'\e05c';}.map_pin_alt:before {content:'\e002';}.unlock_stroke:before {content:'\e076';}.lock_stroke:before {content:'\e075';}.question_mark:before {content:'\003f';}.list:before {content:'\e055';}.upload:before {content:'\e043';}.reload:before {content:'\e030';}.loop_alt4:before {content:'\e035';}.loop_alt3:before {content:'\e034';}.loop_alt2:before {content:'\e033';}.loop_alt1:before {content:'\e032';}.left_quote:before {content:'\275d';}.x:before {content:'\2717';}.last:before {content:'\e04d';}.document_alt_stroke:before {content:'\e000';}.bars:before {content:'\e06f';}.arrow_left:before {content:'\2190';}.arrow_down:before {content:'\2193';}.download:before {content:'\e042';}.home:before {content:'\2302';}.calendar:before {content:'\e001';}.right_quote_alt:before {content:'\e012';}.fullscreen:before {content:'\e04e';}.dial:before {content:'\e058';}.plus_alt:before {content:'\e008';}.clock:before {content:'\e079';}.movie:before {content:'\e060';}.steering_wheel:before {content:'\e024';}.pen:before {content:'\270e';}.tag_stroke:before {content:'\e02b';}.pin:before {content:'\e067';}.denied:before {content:'\26d4';}.left_quote_alt:before {content:'\e011';}.volume_mute:before {content:'\e071';}.arrow_up_alt2:before {content:'\e018';}.list_nested:before {content:'\e056';}.arrow_up_alt1:before {content:'\e014';}.comment_stroke:before {content:'\e06d';}.undo:before {content:'\e02f';}.umbrella:before {content:'\2602';}.bolt:before {content:'\26a1';}.article:before {content:'\e053';}.read_more:before {content:'\e054';}.beaker:before {content:'\e023';}.beaker_alt:before {content:'\e010';}.battery_full:before {content:'\e073';}.arrow_right:before {content:'\2192';}.new_window:before {content:'\e059';}.plus:before {content:'\2795';}.cog:before {content:'\2699';}.key_stroke:before {content:'\26bf';}.first:before {content:'\e04c';}.comment_alt1_stroke:before {content:'\e003';}.trash_stroke:before {content:'\e05a';}.image:before {content:'\e027';}.chat_alt_stroke:before {content:'\e007';}.cd:before {content:'\e064';}.right_quote:before {content:'\275e';}.brush:before {content:'\e01b';}.cloud:before {content:'\2601';}.eye:before {content:'\e025';}.play_alt:before {content:'\e048';}.transfer:before {content:'\e041';}.pen_alt2:before {content:'\e006';}.camera:before {content:'\e070';}.move_horizontal_alt2:before {content:'\e03a';}.curved_arrow:before {content:'\2935';}.move_horizontal_alt1:before {content:'\e039';}.aperture:before {content:'\e026';}.reload_alt:before {content:'\e031';}.magnifying_glass:before {content:'\e074';}.iphone:before {content:'\e06e';}.fork:before {content:'\e046';}.box:before {content:'\e06b';}.bars_alt:before {content:'\e00a';}.heart_stroke:before {content:'\2764';}.volume:before {content:'\e072';}.x_alt:before {content:'\2718';}.link:before {content:'\e077';}.moon_stroke:before {content:'\263e';}.eyedropper:before {content:'\e01e';}.spin:before {content:'\e036';}.rss:before {content:'\e02c';}.info:before {content:'\2139';}.target:before {content:'\e02a';}.cursor:before {content:'\e057';}.minus:before {content:'\2796';}.book_alt:before {content:'\e00b';}.headphones:before {content:'\e061';}.hash:before {content:'\0023';}.arrow_left_alt1:before {content:'\e013';}.arrow_left_alt2:before {content:'\e017';}.fullscreen_exit:before {content:'\e050';}.share:before {content:'\e02e';}.fullscreen_alt:before {content:'\e04f';}.at:before {content:'\0040';}.chat:before {content:'\e05e';}.move_vertical_alt2:before {content:'\e03d';}.move_vertical_alt1:before {content:'\e03c';}.check:before {content:'\2713';}.mic:before {content:'\e05f';}.calendar_alt_stroke:before {content:'\e06c';}.book:before {content:'\e069';}.move_alt1:before {content:'\e03f';}.move_alt2:before {content:'\e040';}.award_stroke:before {content:'\e022';}.wrench:before {content:'\e078';}.play:before {content:'\e047';}.star:before {content:'\2605';}.chart:before {content:'\e028';}.rain:before {content:'\26c6';}.folder_stroke:before {content:'\e065';}.sun_stroke:before {content:'\2600';}.user:before {content:'\e062';}.battery_half:before {content:'\e05b';}.aperture_alt:before {content:'\e00c';}.eject:before {content:'\e04b';}.arrow_down_alt1:before {content:'\e016';}.pilcrow:before {content:'\00b6';}.arrow_down_alt2:before {content:'\e01a';}.arrow_right_alt1:before {content:'\e015';}.arrow_right_alt2:before {content:'\e019';}.rss_alt:before {content:'\e02d';}.spin_alt:before {content:'\e037';}
\ No newline at end of file
diff --git a/src/main/resources/bootstrap/font/iconic_stroke.eot b/src/main/resources/bootstrap/font/iconic_stroke.eot
new file mode 100644
index 0000000..eb04fd3
--- /dev/null
+++ b/src/main/resources/bootstrap/font/iconic_stroke.eot
Binary files differ
diff --git a/src/main/resources/bootstrap/font/iconic_stroke.otf b/src/main/resources/bootstrap/font/iconic_stroke.otf
new file mode 100644
index 0000000..0d132a9
--- /dev/null
+++ b/src/main/resources/bootstrap/font/iconic_stroke.otf
Binary files differ
diff --git a/src/main/resources/bootstrap/font/iconic_stroke.svg b/src/main/resources/bootstrap/font/iconic_stroke.svg
new file mode 100644
index 0000000..e46ede7
--- /dev/null
+++ b/src/main/resources/bootstrap/font/iconic_stroke.svg
@@ -0,0 +1,553 @@
+<?xml version="1.0" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
+<svg>
+<metadata>
+Created by FontForge 20110222 at Sun Apr  1 19:42:24 2012
+ By PJ Onori,,,
+Icons by PJ Onori, font creation script by Yann
+</metadata>
+<defs>
+<font id="IconicStroke" horiz-adv-x="774" >
+  <font-face 
+    font-family="Iconic"
+    font-weight="500"
+    font-stretch="normal"
+    units-per-em="1000"
+    panose-1="2 0 6 3 0 0 0 0 0 0"
+    ascent="800"
+    descent="-200"
+    bbox="14.9997 -13.9766 759.23 730.262"
+    underline-thickness="50"
+    underline-position="-100"
+    unicode-range="U+0023-E08A"
+  />
+    <missing-glyph />
+    <glyph glyph-name="glyph0" unicode="&#xe063;" horiz-adv-x="495" 
+d="M247.559 637.023c-77 0 -139.582 -62.7676 -139.582 -139.907c0 -44.0469 20.1631 -84.5576 55.2324 -111c52.4883 -39.6973 73.7676 -83.5811 81.0234 -120.465h6.65137c7.30176 36.8838 28.5117 80.7676 81.0234 120.465c35.1162 26.4189 55.1855 66.9307 55.1855 111
+c0 77.1396 -62.5342 139.907 -139.534 139.907zM247.559 730.232c128.441 0 232.558 -104.372 232.558 -233.093c0 -76.3252 -35.8838 -143 -92.3018 -185.512c-25.8838 -19.6279 -47.2793 -44.8838 -47.2793 -79.7676v-59.4189h-185.907v59.4189
+c0 34.8838 -21.3955 60.1396 -47.3252 79.7676c-56.3721 42.5117 -92.3027 109.187 -92.3027 185.512c0 128.721 104.116 233.093 232.559 233.093zM154.628 -13.9531v93.209h185.907v-93.209h-185.907z" />
+    <glyph glyph-name="glyph1" unicode="&#xe052;" 
+d="M480.116 265.116v-93.0234h-46.5117v-139.534c0 -25.6982 -20.7207 -46.5117 -46.5117 -46.5117c-25.6279 0 -46.5117 20.8135 -46.5117 46.5117v139.534h-46.5117v93.0234h46.5117v418.604c0 25.6982 20.8838 46.5117 46.5117 46.5117
+c25.791 0 46.5117 -20.8135 46.5117 -46.5117v-418.604h46.5117zM759.186 544.186v-93.0225h-46.5107v-418.604c0 -25.6982 -20.7217 -46.5117 -46.5117 -46.5117c-25.6279 0 -46.5117 20.8135 -46.5117 46.5117v418.604h-46.5117v93.0225h46.5117v139.535
+c0 25.6982 20.8838 46.5117 46.5117 46.5117c25.79 0 46.5117 -20.8135 46.5117 -46.5117v-139.535h46.5107zM201.047 451.163v-93.0234h-46.5117v-325.581c0 -25.6982 -20.7217 -46.5117 -46.5117 -46.5117c-25.6279 0 -46.5117 20.8135 -46.5117 46.5117v325.581h-46.5117
+v93.0234h46.5117v232.558c0 25.6982 20.8838 46.5117 46.5117 46.5117c25.79 0 46.5117 -20.8135 46.5117 -46.5117v-232.558h46.5117z" />
+    <glyph glyph-name="glyph2" unicode="&#xe068;" horiz-adv-x="495" 
+d="M247.453 637.209c-39.1162 0 -72.209 -14.5342 -98.5576 -40.8838c-26.3486 -26.3486 -40.8838 -61.3945 -40.8838 -98.6504s14.5352 -72.3027 40.9072 -98.6055c3.62793 -3.53418 51.5811 -51.2324 98.5576 -125.906c47.3262 74.79 95.4648 122.674 99.3721 126.534
+c25.6982 25.6748 40.2324 60.7217 40.2324 97.9775s-14.5342 72.3018 -40.8838 98.6045c-26.3486 26.3955 -61.418 40.9297 -98.7441 40.9297zM247.453 730.232c59.6045 0 119.094 -22.7207 164.512 -68.1396c90.8369 -90.8135 90.8369 -238.069 0 -328.86
+c0 0 -164.512 -161.163 -164.512 -347.209c0 186.046 -164.325 347.209 -164.325 347.209c-90.8369 90.8145 -90.8369 238.07 0 328.86c45.4189 45.4189 104.907 68.1396 164.325 68.1396zM294.058 497.675c0 -25.6982 -20.8135 -46.5117 -46.6045 -46.5117
+c-27.5576 0 -46.418 20.8135 -46.418 46.5117c0 25.6973 18.8604 46.5107 46.418 46.5107c25.791 0 46.6045 -20.8135 46.6045 -46.5107z" />
+    <glyph glyph-name="glyph3" unicode="&#xe01c;" 
+d="M294.069 172.093c0 -102.744 -83.3018 -186.046 -186.046 -186.046c-33.791 0 -65.3955 9 -92.6514 24.6973l-0.37207 0.279297c55.6045 32.1631 93.0234 92.1162 93.0234 160.884v0.185547c0 51.4189 41.6045 93.0234 93.0234 93.0234
+c51.3252 0 93.0225 -41.6045 93.0225 -93.0234zM731.93 702.977c36.3496 -36.3252 36.3496 -95.209 0 -131.535l-351.744 -351.837c-17.0693 64.2324 -67.3945 114.465 -131.534 131.628l351.744 351.744c36.3252 36.3262 95.209 36.3262 131.534 0z" />
+    <glyph glyph-name="glyph4" unicode="&#xe03e;" 
+d="M433.604 265.116v-139.535h93.0234l-139.535 -139.534l-139.534 139.534h93.0225v139.535h93.0234zM340.581 451.163v139.534h-93.0225l139.534 139.535l139.535 -139.535h-93.0234v-139.534h-93.0234zM294.069 311.628h-139.534v-93.0234l-139.535 139.535
+l139.535 139.535v-93.0234h139.534v-93.0234zM480.116 404.651h139.535v93.0234l139.534 -139.535l-139.534 -139.535v93.0234h-139.535v93.0234z" />
+    <glyph glyph-name="glyph5" unicode="&#xe08a;" horiz-adv-x="681" 
+d="M270.813 -13.0469c-68.3486 0 -132.581 26.6982 -180.86 75.0469c-48.3252 48.3252 -74.9531 112.465 -74.9531 180.86c0.046875 68.3252 26.6279 132.535 74.9531 180.814l258.978 253.372c69.6279 69.6279 192.488 69.9062 262.721 -0.37207
+c72.4883 -72.582 72.4883 -190.582 0 -263.07l-233.023 -227.325c-44.8838 -44.8838 -118.651 -44.9766 -163.977 0.37207c-45.3262 45.418 -45.3262 119.14 0 164.465l90.209 90.209l65.7676 -65.7666l-90.209 -90.21
+c-5.9541 -5.95312 -6.81445 -12.8604 -6.81445 -16.4414c0 -3.58203 0.860352 -10.4883 6.81445 -16.4883c11.9062 -11.8145 20.9766 -11.8145 32.8838 0l232.93 227.372c35.8838 35.8369 35.8838 94.8369 -0.37207 131.14c-35.1631 35.1621 -96.3955 35.1621 -131.535 0
+l-258.977 -253.372c-30.3721 -30.3955 -47.3252 -71.2334 -47.3252 -114.698c0 -43.5117 16.9531 -84.3955 47.6973 -115.093c61.5117 -61.582 168.698 -61.582 230.187 0l116.279 116.279l65.7666 -65.7676l-116.278 -116.279
+c-48.3262 -48.3252 -112.535 -75.0469 -180.861 -75.0469z" />
+    <glyph glyph-name="glyph6" unicode="&#xe005;" 
+d="M704.675 675.721c72.6738 -72.6738 72.6738 -190.396 0 -263.069l-426.605 -426.604h-263.069l0.720703 263.813l425.884 425.86c36.3262 36.3486 83.9307 54.5117 131.535 54.5117s95.209 -18.1631 131.535 -54.5117zM239.559 79.0693l398.534 398.442l-131.534 131.535
+l-394.279 -394.256c36.5342 31.8135 91.4883 31.3486 126.279 -3.44238c36.3252 -36.3486 36.3252 -95.9531 0 -132.279h1z" />
+    <glyph glyph-name="glyph7" unicode="&#xe03b;" horiz-adv-x="309" 
+d="M201.047 265.116v-139.535h93.0225l-139.534 -139.534l-139.535 139.534h93.0234v139.535h93.0234zM108.023 451.163v139.534h-93.0234l139.535 139.535l139.534 -139.535h-93.0225v-139.534h-93.0234z" />
+    <glyph glyph-name="glyph8" unicode="&#xe06a;" 
+d="M387.093 590.697c0 0 93.0234 93.0234 372.093 93.0234v-558.14c-281.977 0 -372.093 -93.0225 -372.093 -93.0225s-90.1162 93.0225 -372.093 93.0225v558.14c279.069 0 372.093 -93.0234 372.093 -93.0234zM108.023 590.697v-374.627
+c118.232 -10.001 190.768 -37.3496 232.558 -59.4189v374.628c-41.79 22.1621 -114.325 49.5117 -232.558 59.418zM666.163 216.07v374.627c-118.279 -9.90625 -190.814 -37.2559 -232.559 -59.418v-374.628c41.7441 22.0928 114.279 49.418 232.559 59.4189z" />
+    <glyph glyph-name="glyph9" unicode="&#xe01f;" 
+d="M15 218.604l372.093 -93.0234l372.093 93.0234v-93.0234l-372.093 -93.0225l-372.093 93.0225v93.0234zM15 404.651l372.093 -93.0234l372.093 93.0234v-93.0234l-372.093 -93.0234l-372.093 93.0234v93.0234zM15 590.697l372.093 93.0234l372.093 -93.0234v-93.0225
+l-372.093 -93.0234l-372.093 93.0234v93.0225z" />
+    <glyph glyph-name="glyph10" unicode="&#xe049;" horiz-adv-x="588" 
+d="M15 -13.9531v744.186h186.047v-744.186h-186.047zM387.093 -13.9531v744.186h186.047v-744.186h-186.047z" />
+    <glyph glyph-name="glyph11" unicode="&#xe020;" 
+d="M15 172.093l372.093 -93.0234l372.093 93.0234v-93.0234l-372.093 -93.0225l-372.093 93.0225v93.0234zM15 358.14l372.093 -93.0234l372.093 93.0234v-93.0234l-372.093 -93.0234l-372.093 93.0234v93.0234zM15 544.186l372.093 -93.0225l372.093 93.0225v-93.0225
+l-372.093 -93.0234l-372.093 93.0234v93.0225zM15 730.232l372.093 -93.0234l372.093 93.0234v-93.0234l-372.093 -93.0234l-372.093 93.0234v93.0234z" />
+    <glyph glyph-name="glyph12" unicode="&#xe045;" 
+d="M573.14 637.209c102.651 0 186.046 -83.3945 186.046 -186.046s-83.3945 -186.047 -186.046 -186.047h-372.093c-102.605 0 -186.047 83.3955 -186.047 186.047s83.4414 186.046 186.047 186.046c8.30176 0 16.4414 -1.27832 24.6279 -2.37207
+c31.9297 56.6982 91.9297 95.3955 161.418 95.3955c68.0469 0 128 -38.3252 160.419 -95.5576c8.46484 1.09277 16.9072 2.53418 25.6279 2.53418zM573.14 358.14c51.2324 0 93.0234 41.791 93.0234 93.0234s-41.791 93.0225 -93.0234 93.0225
+s-93.0234 -41.79 -93.0234 -93.0225h-93.0234c0 60.5811 29.5352 113.906 74.4424 147.884c-17.1631 23.2559 -44.3262 38.1621 -74.4424 38.1621c-51.2783 0 -93.0234 -41.79 -93.0234 -93.0234c0 -11.2549 1.9541 -22.1621 5.81445 -32.5342l-87.1162 -32.5117
+c-7.62793 20.3486 -11.4424 41.791 -11.6279 63.7676c-47.9307 -1.44238 -93.1162 -40.6982 -93.1162 -91.7441c0 -51.2324 41.7441 -93.0234 93.0234 -93.0234h372.093zM433.651 125.581v-139.534h-93.0234v139.534h-93.0234l139.581 139.535l139.442 -139.535h-92.9766z
+" />
+    <glyph glyph-name="glyph13" unicode="&#xe029;" horiz-adv-x="728" 
+d="M353.006 278.559l-246 -246c-122.675 141.534 -122.675 350.465 0 492zM389.797 311.628l-301.86 299.953c83.0225 67.9541 185.86 111.559 301.86 118.651v-418.604zM435.587 636.837c157.466 -24.1621 277.116 -157.512 277.116 -322.116
+c0 -181.512 -145.256 -328.674 -327.069 -328.674c-74.0469 0 -139.279 25.0693 -193.419 64.8604l243.372 241.465v344.465z" />
+    <glyph glyph-name="glyph14" unicode="&#xe051;" 
+d="M540.256 139.209l-60.1396 -60.1396v186.047h186.047l-60.1396 -60.1396l153.162 -153.162l-65.7666 -65.7676zM167.256 512.303l-152.256 152.162l65.7676 65.7676l152.256 -152.163l61.0459 59.1396l-0.90625 -185.046l-185.14 -1zM15 51.8145l153.163 153.162
+l-60.1396 60.1396h186.046v-186.047l-60.1387 60.1396l-153.163 -153.162zM666.884 451.163h-186.768v186.721l60.4189 -60.3486l152.697 152.697l65.9531 -66l-152.604 -152.697z" />
+    <glyph glyph-name="glyph15" unicode="&#xe044;" 
+d="M573.14 637.209c102.651 0 186.046 -83.4883 186.046 -186.046c0 -102.559 -83.3945 -186.047 -186.046 -186.047h-139.535v-139.535h93.0234l-139.581 -139.534l-139.488 139.534h93.0225v139.535h-139.534c-102.605 0 -186.047 83.4883 -186.047 186.047
+c0 102.558 83.4414 186.046 186.047 186.046c8.30176 0 16.4414 -1.18555 24.6279 -2.27832c31.9297 56.5107 91.9297 95.3018 161.418 95.3018c68.0469 0 128 -38.2559 160.419 -95.6514c8.46484 1.25586 16.9072 2.62793 25.6279 2.62793zM573.14 358.14
+c51.2324 0 93.0234 41.6973 93.0234 93.0234c0 51.3252 -41.791 93.0225 -93.0234 93.0225s-93.0234 -41.6973 -93.0234 -93.0225h-93.0234c0 60.5811 29.5352 114 74.4424 147.977c-17.1631 23.1631 -44.3262 38.0693 -74.4424 38.0693
+c-51.2783 0 -93.0234 -41.6973 -93.0234 -93.0234c0 -11.1621 1.9541 -22.1621 5.81445 -32.4414l-87.1162 -32.6973c-7.62793 20.3486 -11.4424 41.9766 -11.6279 63.8604c-47.9307 -1.34863 -93.1162 -40.791 -93.1162 -91.7441
+c0 -51.3262 41.7441 -93.0234 93.0234 -93.0234h372.093z" />
+    <glyph glyph-name="glyph16" unicode="&#xe004;" horiz-adv-x="681" 
+d="M340.581 637.209c-128.278 0 -232.558 -104.278 -232.558 -232.558c0 -61.3262 23.8838 -119.279 67.2324 -163.232l26.9766 -27.3496l-0.185547 -38.418l-0.09375 -15.9072l10.6279 7l33.9775 22.5117l39.6045 -9.81445
+c20.1621 -4.90625 37.9766 -7.34863 54.418 -7.34863c128.279 0 232.559 104.279 232.559 232.559s-104.279 232.558 -232.559 232.558zM340.581 730.232c179.768 0 325.582 -145.813 325.582 -325.581s-145.814 -325.582 -325.582 -325.582
+c-26.5342 0 -51.9531 4.00098 -76.6738 10.0938l-155.884 -103.116l1 190.046c-58.0469 58.8604 -94.0234 139.442 -94.0234 228.559c0 179.768 145.813 325.581 325.581 325.581z" />
+    <glyph glyph-name="glyph17" unicode="&#x2709;" 
+d="M388.907 370.768l-373.907 175.604v90.8369h744.186v-91.1162zM389.093 267.931l370.093 175.232v-364.094h-744.186v364.651z" />
+    <glyph glyph-name="glyph18" unicode="&#x2714;" 
+d="M387.093 730.232c205.488 0 372.093 -166.604 372.093 -372.093s-166.604 -372.093 -372.093 -372.093s-372.093 166.604 -372.093 372.093s166.604 372.093 372.093 372.093zM329.419 186.441l289.395 289.419l-65.7666 65.7676l-223.651 -223.651l-105.419 105.465
+l-65.7676 -65.7666z" />
+    <glyph glyph-name="glyph19" unicode="&#xe066;" 
+d="M527 730.232l232.186 -232.558v-511.256l-184.139 153.884l-186.326 -154.256l-186.488 154.418l-187.232 -152.977v742.744h512zM666.628 173.535v186.046h-279.232v279.07h-279.256v-465.116l95.0234 74.8604l185.581 -153.698l185.953 153.884zM480.488 452.604
+h186.14c-37.2559 37.2559 -148.535 148.442 -186.14 186.047v-186.047z" />
+    <glyph glyph-name="glyph20" unicode="&#xe05d;" 
+d="M666.163 451.163h93.0225v-186.047h-93.0225v-46.5117c0 -25.6279 -20.8838 -46.5117 -46.5117 -46.5117h-558.14c-25.6279 0 -46.5117 20.8838 -46.5117 46.5117v92.0234h148.535c19.1621 -54.0469 70.3252 -93.0234 131 -93.0234v47.5117h94.5576v46.5117h-94.5576
+v93.0234h92.6504v46.5117h-92.6504v45.5117c-60.6982 0 -111.838 -38.9775 -131 -93.0234h-148.535v94.0234c0 25.5811 20.8838 46.5107 46.5117 46.5107h558.14c25.6279 0 46.5117 -20.9297 46.5117 -46.5107v-46.5117z" />
+    <glyph glyph-name="glyph21" unicode="&#xe04a;" 
+d="M15 -13.9531v744.186h744.186v-744.186h-744.186z" />
+    <glyph glyph-name="glyph22" unicode="&#x2191;" 
+d="M15 358.512l372.14 371.721l372.046 -371.721l-131.93 -131.907l-146.977 146.977v-387.534h-186.535v387.534c-72.8369 -72.8369 -146.837 -146.79 -146.837 -146.79z" />
+    <glyph glyph-name="glyph23" unicode="&#xe038;" 
+d="M294.069 311.628h-139.534v-93.0234l-139.535 139.535l139.535 139.535v-93.0234h139.534v-93.0234zM480.116 404.651h139.535v93.0234l139.534 -139.535l-139.534 -139.535v93.0234h-139.535v93.0234z" />
+    <glyph glyph-name="glyph24" unicode="&#xe021;" 
+d="M387.093 637.209c-153.884 0 -279.069 -125.186 -279.069 -279.069s125.186 -279.07 279.069 -279.07s279.07 125.187 279.07 279.07s-125.187 279.069 -279.07 279.069zM387.093 730.232c205.488 0 372.093 -166.604 372.093 -372.093
+s-166.604 -372.093 -372.093 -372.093s-372.093 166.604 -372.093 372.093s166.604 372.093 372.093 372.093zM436.512 407.465c-17.9072 17.9072 -42.8838 22.8604 -65.7676 17.21l202.396 119.511l-119.907 -205.162c6.7207 23.5576 1.81445 49.8604 -16.7207 68.4414z
+M337.86 308.814c18.6279 -18.6289 44.9531 -23.5352 68.5811 -16.7217l-205.395 -120l119.558 202.675c-5.62793 -22.9307 -0.720703 -48.0234 17.2559 -65.9531z" />
+    <glyph glyph-name="glyph25" unicode="&#xe009;" 
+d="M387.093 730.232c205.488 0 372.093 -166.604 372.093 -372.093s-166.604 -372.093 -372.093 -372.093s-372.093 166.604 -372.093 372.093s166.604 372.093 372.093 372.093zM573.14 311.628v93.0234h-372.093v-93.0234h372.093z" />
+    <glyph glyph-name="glyph26" unicode="&#xe05c;" 
+d="M759.186 451.163v-186.047h-93.0225v-46.5117c0 -25.5352 -20.8838 -46.5117 -46.5117 -46.5117h-558.14c-25.6279 0 -46.5117 20.9766 -46.5117 46.5117v279.07c0 25.5342 20.8838 46.5107 46.5117 46.5107h558.14c25.6279 0 46.5117 -20.9766 46.5117 -46.5107
+v-46.5117h93.0225zM573.14 265.116v186.047h-465.116v-186.047h465.116z" />
+    <glyph glyph-name="glyph27" unicode="&#xe002;" horiz-adv-x="402" 
+d="M201.047 730.232c102.744 0 186.046 -83.3018 186.046 -186.047c0 -102.744 -186.046 -558.139 -186.046 -558.139s-186.047 455.395 -186.047 558.139c0 102.745 83.3027 186.047 186.047 186.047zM201.047 451.163c51.3721 0 93.0225 41.6514 93.0225 93.0225
+c0 51.373 -41.6504 93.0234 -93.0225 93.0234s-93.0234 -41.6504 -93.0234 -93.0234c0 -51.3711 41.6514 -93.0225 93.0234 -93.0225z" />
+    <glyph glyph-name="glyph28" unicode="&#xe076;" horiz-adv-x="588" 
+d="M340.675 730.232c128.186 0 232.465 -104.372 232.465 -232.558v-372.094c0 -77.0225 -62.5811 -139.534 -139.535 -139.534h-279.116c-76.9072 0 -139.488 62.5117 -139.488 139.534v325.582h465.116v46.5117c0 76.8604 -62.4883 139.534 -139.441 139.534
+c-76.9541 0 -139.488 -62.6738 -139.488 -139.534h-93.0234c0 128.186 104.278 232.558 232.512 232.558zM433.604 79.0693c25.6279 0 46.5117 20.8145 46.5117 46.5117v232.559h-372.14v-232.559c0 -25.6973 20.8838 -46.5117 46.5117 -46.5117h279.116zM340.581 218.604
+c0 -25.79 -20.8135 -46.5117 -46.5117 -46.5117c-25.6973 0 -46.5107 20.7217 -46.5107 46.5117c0 25.6279 20.8135 46.5117 46.5107 46.5117c25.6982 0 46.5117 -20.8838 46.5117 -46.5117z" />
+    <glyph glyph-name="glyph29" unicode="&#xe075;" horiz-adv-x="588" 
+d="M340.675 730.232c128.186 0 232.465 -104.279 232.465 -232.558v-372.094c0 -76.8604 -62.5117 -139.534 -139.535 -139.534h-279.116c-76.8135 0 -139.488 62.6738 -139.488 139.534v325.582h93.1631v46.5117c0 128.278 104.372 232.558 232.512 232.558z
+M201.186 497.675v-46.5117h278.931v46.5117c0 77.0225 -62.5117 139.534 -139.441 139.534c-76.8613 0 -139.489 -62.5117 -139.489 -139.534zM433.604 79.0693c25.791 0 46.5117 20.8838 46.5117 46.5117v232.559h-372.14v-232.559
+c0 -25.6279 20.8838 -46.5117 46.5117 -46.5117h279.116zM340.675 218.604c0 -25.6279 -20.8838 -46.5117 -46.5117 -46.5117c-25.791 0 -46.5117 20.8838 -46.5117 46.5117c0 25.791 20.7207 46.5117 46.5117 46.5117c25.6045 0 46.5117 -20.7207 46.5117 -46.5117z" />
+    <glyph glyph-name="question" unicode="?" horiz-adv-x="402" 
+d="M238.256 32.5586c0 -25.6279 -20.8145 -46.5117 -46.5117 -46.5117s-46.5117 20.8838 -46.5117 46.5117c0 25.79 20.8145 46.5107 46.5117 46.5107s46.5117 -20.7207 46.5117 -46.5107zM193.512 126.116c-31.0703 0 -60.2324 12.1631 -82.209 34.1631
+c-21.9775 25.6973 -34.0703 54.8604 -34.0703 85.9297c0 31.0703 12.0928 60.2324 34.0703 78.3955l155.534 155.535c17.5586 17.6279 27.2324 40.9766 27.2324 65.7676c0 24.8838 -9.67383 48.2324 -27.2559 65.7676c-36.2549 36.2559 -95.2549 36.3252 -131.488 0
+c-17.6279 -17.5352 -27.3018 -40.8604 -27.3018 -65.7676h-93.0234c0 49.6973 19.3955 96.4648 54.6045 131.628c70.2324 70.3018 192.675 70.2324 263 -0.09375c35.1396 -35.0459 54.4883 -81.7441 54.4883 -131.534c0 -49.6982 -19.3486 -96.3955 -54.5117 -131.535
+l-155.534 -151.697c-9.04688 -9.09375 -9.04688 -23.791 0 -32.8838c9.09277 -9.09375 23.79 -9.09375 32.8838 0c5.90625 6 6.81348 12.9062 6.81348 16.5342h93.0234c0 -31.0693 -12.0928 -60.3252 -34.0703 -86.0225c-21.9531 -22 -51.1162 -34.1865 -82.1855 -34.1865z
+" />
+    <glyph glyph-name="glyph31" unicode="&#xe055;" 
+d="M15 637.209c0 31.0078 15.5039 46.5117 46.5117 46.5117s46.5117 -15.5039 46.5117 -46.5117s-15.5039 -46.5117 -46.5117 -46.5117s-46.5117 15.5039 -46.5117 46.5117zM15 451.163c0 31.0078 15.5039 46.5117 46.5117 46.5117s46.5117 -15.5039 46.5117 -46.5117
+s-15.5039 -46.5117 -46.5117 -46.5117s-46.5117 15.5039 -46.5117 46.5117zM15 265.116c0 31.0078 15.5039 46.5117 46.5117 46.5117s46.5117 -15.5039 46.5117 -46.5117s-15.5039 -46.5117 -46.5117 -46.5117s-46.5117 15.5039 -46.5117 46.5117zM15 79.0693
+c0 31.0078 15.5039 46.5117 46.5117 46.5117s46.5117 -15.5039 46.5117 -46.5117c0 -31.0068 -15.5039 -46.5107 -46.5117 -46.5107s-46.5117 15.5039 -46.5117 46.5107zM201.047 590.697v93.0234h558.139v-93.0234h-558.139zM201.047 404.651v93.0234h558.139v-93.0234
+h-558.139zM201.047 218.604v93.0234h558.139v-93.0234h-558.139zM201.047 32.5586v93.0225h558.139v-93.0225h-558.139z" />
+    <glyph glyph-name="glyph32" unicode="&#xe043;" horiz-adv-x="588" 
+d="M15 623.907v106.325h558.14v-106.325h-558.14zM201.047 411.279h-93.0234l185.768 212.628l186.325 -212.628h-93.0234v-425.232h-186.046v425.232z" />
+    <glyph glyph-name="glyph33" unicode="&#xe030;" horiz-adv-x="588" 
+d="M480.116 311.628h93.0234c0 -154.163 -125 -279.069 -279.07 -279.069c-154.069 0 -279.069 124.906 -279.069 279.069s125 279.069 279.069 279.069h55.2334l-27.4424 27.2559l65.7676 65.7676l139.813 -139.535l-139.813 -139.977l-65.7676 65.7676l27.0703 27.0703
+l-54.8613 0.62793c-102.65 0 -186.046 -83.4893 -186.046 -186.047c0 -102.559 83.3955 -186.047 186.046 -186.047c102.651 0 186.047 83.4883 186.047 186.047z" />
+    <glyph glyph-name="glyph34" unicode="&#xe035;" 
+d="M666.163 311.628h93.0225c0 -76.8604 -62.5811 -139.535 -139.534 -139.535h-418.791v-93.0234l-185.86 139.535l185.86 139.535v-93.0234h418.791c25.6279 0 46.5117 20.8838 46.5117 46.5117zM154.535 451.163c-25.6279 0 -46.5117 -20.8838 -46.5117 -46.5117
+h-93.0234c0 77.0234 62.5811 139.534 139.535 139.534h418.604v93.0234l186.046 -139.534l-186.046 -139.535v93.0234h-418.604z" />
+    <glyph glyph-name="glyph35" unicode="&#xe034;" 
+d="M666.163 358.14h93.0225v-46.5117c0 -76.8604 -62.5811 -139.535 -139.534 -139.535h-325.675l0.0927734 -93.0234l-186.046 139.535l186.046 139.535l-0.0927734 -93.0234h325.675c25.6279 0 46.5117 20.8838 46.5117 46.5117v46.5117zM108.023 404.651v-46.5117
+h-93.0234v46.5117c0 77.0234 62.5811 139.534 139.535 139.534h325.581v93.0234l185.86 -139.534l-185.86 -139.535v93.0234h-325.581c-25.6279 0 -46.5117 -20.8145 -46.5117 -46.5117z" />
+    <glyph glyph-name="glyph36" unicode="&#xe033;" 
+d="M478.837 218.604l139.722 186.047l140.627 -186.047h-93.0225v-46.5117c0 -76.9531 -62.582 -139.534 -139.535 -139.534h-279.069c-76.9541 0 -139.535 62.5811 -139.535 139.534v46.5117h93.0234v-46.5117c0 -25.6279 20.8838 -46.5117 46.5117 -46.5117h279.069
+c25.6279 0 46.5117 20.8838 46.5117 46.5117v46.5117h-94.3027zM295.349 497.675l-139.721 -186.047l-140.628 186.047h93.0234v46.5107c0 76.9541 62.5811 139.535 139.535 139.535h279.069c76.9531 0 139.535 -62.5811 139.535 -139.535v-46.5107h-93.0234v46.5107
+c0 25.6289 -20.8838 46.5117 -46.5117 46.5117h-279.069c-25.6279 0 -46.5117 -20.8828 -46.5117 -46.5117v-46.5107h94.3018z" />
+    <glyph glyph-name="glyph37" unicode="&#xe032;" 
+d="M619.651 358.14l139.534 -139.535h-93.0225c0 -76.9531 -62.582 -139.535 -139.535 -139.535h-279.069c-76.9541 0 -139.535 62.582 -139.535 139.535v46.5117h93.0234v-46.5117c0 -25.6279 20.8838 -46.5117 46.5117 -46.5117h279.069
+c25.6279 0 46.5117 20.8838 46.5117 46.5117h-93.0234zM526.628 637.209c76.9531 0 139.535 -62.5811 139.535 -139.534v-46.5117h-93.0234v46.5117c0 25.6279 -20.8838 46.5107 -46.5117 46.5107h-279.069c-25.6279 0 -46.5117 -20.8828 -46.5117 -46.5107v-1.44238
+h93.0225l-139.534 -138.093l-139.535 138.093h93.0234v1.44238c0 76.9531 62.5811 139.534 139.535 139.534h279.069z" />
+    <glyph glyph-name="glyph38" unicode="&#x275d;" 
+d="M759.186 79.0693h-279.069v279.07c0 153.884 125.187 279.069 279.069 279.069v-93.0234c-102.65 0 -186.046 -83.4883 -186.046 -186.046h186.046v-279.07zM294.069 79.0693h-279.069v279.07c0 153.884 125.187 279.069 279.069 279.069v-93.0234
+c-102.65 0 -186.046 -83.4883 -186.046 -186.046h186.046v-279.07z" />
+    <glyph glyph-name="glyph39" unicode="&#x2717;" horiz-adv-x="681" 
+d="M666.163 162.837l-130.279 -130.278l-195.303 195.395l-195.395 -195.395l-130.187 130.278l195.303 195.303l-195.303 195.302l130.187 130.279l195.395 -195.396l195.303 195.396l130.279 -130.279l-195.488 -195.302z" />
+    <glyph glyph-name="glyph40" unicode="&#xe04d;" 
+d="M15 730.232l558.14 -372.093l-558.14 -372.093v744.186zM573.14 79.0693v558.14h186.046v-558.14h-186.046z" />
+    <glyph glyph-name="glyph41" unicode="&#xe000;" horiz-adv-x="588" 
+d="M340.581 730.232l232.559 -232.512v-511.674h-558.14v744.186h325.581zM108.023 79.0693h372.093v372.094h-186.047v186.046h-186.046v-558.14z" />
+    <glyph glyph-name="glyph42" unicode="&#xe06f;" 
+d="M573.14 -13.9531v744.186h186.046v-744.186h-186.046zM294.069 -13.9531v558.139h186.047v-558.139h-186.047zM15 -13.9531v372.093h186.047v-372.093h-186.047z" />
+    <glyph glyph-name="glyph43" unicode="&#x2190;" 
+d="M386.721 -13.9531l-371.721 372.093l371.721 372.093l131.907 -131.93l-146.791 -146.978h387.349v-186.581h-387.349c72.8379 -72.8369 146.791 -146.791 146.791 -146.791z" />
+    <glyph glyph-name="glyph44" unicode="&#x2193;" 
+d="M759.186 357.768l-372.093 -371.721l-372.093 371.721l131.931 131.907l146.977 -146.978v387.535h186.581v-387.535c72.8369 72.8379 146.791 146.791 146.791 146.791z" />
+    <glyph glyph-name="glyph45" unicode="&#xe042;" horiz-adv-x="588" 
+d="M15 -13.9531v106.278h558.14v-106.278h-558.14zM387.093 305h93.0234l-185.768 -212.675l-186.325 212.675h93.0234v425.232h186.046v-425.232z" />
+    <glyph glyph-name="glyph46" unicode="&#x2302;" 
+d="M387.093 730.232l372.093 -372.093h-93.0225v-372.093h-558.14v372.093h-93.0234zM573.14 79.0693v333.582l-186.047 131.534l-186.046 -131.534v-333.582h139.534v139.535h93.0234v-139.535h139.535z" />
+    <glyph glyph-name="glyph47" unicode="&#xe001;" 
+d="M571.232 637.209h187.953v-93.0234h-187.953h-93.0234h-186.046h-93.0234h-184.14v93.0234h184.14v93.0234h93.0234v-93.0234h186.046v93.0234h93.0234v-93.0234zM108.023 172.093v93.0234h558.14v-93.0234h-558.14zM108.023 358.14v93.0234h558.14v-93.0234h-558.14z
+M573.14 -13.9531h-465.116v93.0225h558.14z" />
+    <glyph glyph-name="glyph48" unicode="&#xe012;" 
+d="M15 637.209h279.069v-279.069l-279.069 -279.07v558.14zM480.116 637.209h279.069v-279.069l-279.069 -279.07v558.14z" />
+    <glyph glyph-name="glyph49" unicode="&#xe04e;" 
+d="M652.535 158.465l106.65 106.651v-279.069h-279.069l106.651 106.65l-106.651 106.651l65.7676 65.7676zM294.069 730.232l-106.65 -106.651l105.65 -105.558l-65.7666 -65.7676l-105.651 105.559l-106.651 -106.651v279.069h279.069zM294.069 199.349l-106.65 -106.651
+l106.65 -106.65h-279.069v279.069l106.651 -106.651l106.651 106.651zM759.186 730.232v-279.069l-106.65 106.651l-105.651 -105.559l-65.7676 65.7676l105.651 105.558l-106.651 106.651h279.069z" />
+    <glyph glyph-name="glyph50" unicode="&#xe058;" 
+d="M294.069 360.14c0 62.0156 31.0078 93.0234 93.0234 93.0234s93.0234 -31.0078 93.0234 -93.0234s-31.0078 -93.0234 -93.0234 -93.0234s-93.0234 31.0078 -93.0234 93.0234zM679.069 587.604c53.4111 -67.583 80.1162 -143.404 80.1162 -227.465
+c0 -67.3809 -16.5986 -129.644 -49.7949 -186.787c-33.1973 -57.1436 -78.3672 -102.313 -135.511 -135.511c-57.1436 -33.1963 -119.406 -49.7949 -186.787 -49.7949s-129.644 16.5986 -186.787 49.7949c-57.1436 33.1973 -102.313 78.3672 -135.511 135.511
+c-33.1963 57.1436 -49.7949 119.406 -49.7949 186.787c0 82.9883 26.248 158.446 78.7441 226.372c8.68848 -6.54297 17.8047 -9.81445 27.3486 -9.81445c12.8955 0 23.8711 4.52832 32.9277 13.584c9.05566 9.05664 13.584 20.0322 13.584 32.9277
+c0 9.53809 -3.35645 18.7471 -10.0693 27.6279c53.9434 42.7354 115.261 68.5342 183.953 77.3955c1.05371 -11.9824 5.91895 -22.0635 14.5957 -30.2432c8.67578 -8.17871 19.0127 -12.2686 31.0088 -12.2686c11.958 0 22.249 4.03418 30.8721 12.1016
+c8.62207 8.06836 13.5332 18.0264 14.7324 29.875c67.4736 -8.38184 128.218 -33.3975 182.232 -75.0459c-7.50293 -9.13965 -11.2549 -18.9541 -11.2549 -29.4424c0 -12.8809 4.54395 -23.8516 13.6309 -32.9131c9.08789 -9.06543 20.0479 -13.5986 32.8799 -13.5986
+c10.3545 0 19.9824 3.63574 28.8838 10.9072zM387.093 174.093c51.3828 0 95.2363 18.1562 131.561 54.4678s54.4863 80.1709 54.4863 131.579c0 51.4746 -18.2324 95.3818 -54.6982 131.721l-131.349 131.349l-131.534 -131.534
+c-36.3408 -36.3408 -54.5117 -80.1865 -54.5117 -131.535c0 -51.3721 18.165 -95.2227 54.4941 -131.553c36.3291 -36.3291 80.1797 -54.4941 131.552 -54.4941z" />
+    <glyph glyph-name="glyph51" unicode="&#xe008;" 
+d="M387.093 730.232c205.488 0 372.093 -166.604 372.093 -372.093s-166.604 -372.093 -372.093 -372.093s-372.093 166.604 -372.093 372.093s166.604 372.093 372.093 372.093zM573.14 311.628v93.0234h-139.535v139.534h-93.0234v-139.534h-139.534v-93.0234h139.534
+v-139.535h93.0234v139.535h139.535z" />
+    <glyph glyph-name="glyph52" unicode="&#xe079;" 
+d="M387.093 637.209c-153.884 0 -279.069 -125.186 -279.069 -279.069s125.186 -279.07 279.069 -279.07s279.07 125.187 279.07 279.07s-125.187 279.069 -279.07 279.069zM387.093 730.232c205.488 0 372.093 -166.604 372.093 -372.093
+s-166.604 -372.093 -372.093 -372.093s-372.093 166.604 -372.093 372.093s166.604 372.093 372.093 372.093zM513.187 298.187l-65.7676 -65.7676l-91.21 91.209c-9.7207 8.62793 -16.1621 20.6973 -16.1621 34.6973v185.86h93.5576v-166.325z" />
+    <glyph glyph-name="glyph53" unicode="&#xe060;" 
+d="M665.628 544.186l93.5576 93.0234l-91.209 93.0234h91.209v-744.186h-744.186v744.186h93.3955l93.3721 -93.3955l-94.4648 -92.6514h93.209l95 93.3955l-92.6514 92.6514h131.535l93.3018 -93.3955l-94.3945 -92.6514h109.837l95.0234 93.3955l-92.7441 92.6514h129.441
+l91.3027 -93.3955l-92.3721 -92.6514h91.8369zM666.163 80.0693v93.0234h-558.14v-93.0234h558.14zM666.163 265.116v93.0234h-558.14v-93.0234h558.14z" />
+    <glyph glyph-name="glyph54" unicode="&#xe024;" 
+d="M387.093 730.232c205.488 0 372.093 -166.604 372.093 -372.093s-166.604 -372.093 -372.093 -372.093s-372.093 166.604 -372.093 372.093s166.604 372.093 372.093 372.093zM387.093 637.209c-121.093 0 -223.372 -78 -262 -186.046h524
+c-38.6279 108.046 -140.906 186.046 -262 186.046zM387.093 311.628c25.6982 0 46.5117 20.8135 46.5117 46.5117c0 25.6973 -20.8135 46.5117 -46.5117 46.5117c-25.6973 0 -46.5117 -20.8145 -46.5117 -46.5117c0 -25.6982 20.8145 -46.5117 46.5117 -46.5117z
+M108.023 358.14c0 -137.814 100.651 -251.814 232.186 -274.349c-2.16211 151.884 -105.093 274.349 -232.186 274.349zM433.977 83.791c131.535 22.5342 232.187 136.534 232.187 274.349c-127.094 0 -230.023 -122.465 -232.187 -274.349z" />
+    <glyph glyph-name="glyph55" unicode="&#x270e;" 
+d="M704.675 675.721c72.6738 -72.6738 72.6738 -190.396 0 -263.069l-426.605 -426.604h-263.069l0.720703 263.813l425.884 425.86c36.3262 36.3486 83.9307 54.5117 131.535 54.5117c47.6973 0 95.3018 -18.1631 131.535 -54.5117zM239.559 79.0693l399.349 399.35
+l-131.535 131.534l-398.721 -398.79l-0.0927734 -39.0703h92.4883v-93.0234h38.5117z" />
+    <glyph glyph-name="glyph56" unicode="&#xe02b;" 
+d="M287.069 529.931c0 -39.6055 -32.1621 -71.7676 -71.6738 -71.7676c-39.6045 0 -71.7676 32.1621 -71.7676 71.7676c0 39.5107 32.1631 71.5811 71.7676 71.5811c39.5117 0 71.6738 -32.0703 71.6738 -71.5811zM15 730.232h308.675l435.511 -435.581h-308.674v-308.604
+l-435.512 435.512v308.674zM289.349 647.396c-24.5352 0 -138.535 0 -191.512 -0.0234375v-191.581c18 -17.791 183.232 -183.14 269.838 -269.604v191.325h191.488c-86.582 86.5576 -251.838 251.977 -269.814 269.884z" />
+    <glyph glyph-name="glyph57" unicode="&#xe067;" 
+d="M759.186 544.186c0 -69.9062 -39.0693 -130.093 -96.1162 -161.883c0.745117 -7.86133 2.37207 -15.3955 2.37207 -23.4424c0 -154.163 -124.906 -279.069 -279.069 -279.069c-57.5117 0 -110.931 17.4414 -155.349 47.2324l-216.023 -140.977l141.604 214.744
+c-30.9766 44.8838 -49.3252 99.3018 -49.3252 158.069c0 154.163 124.907 279.07 279.069 279.07c8.7207 0 17 -1.76758 25.5352 -2.58203c0 -0.0458984 -0.0927734 -0.0927734 -0.0927734 -0.139648c32 56.4658 91.9531 95.0234 161.349 95.0234
+c102.651 0 186.046 -83.3486 186.046 -186.047zM386.372 172.814c102.372 0 185.581 83.0459 185.953 185.418c-16.6279 0.0927734 -32.5342 3 -47.8838 7.18652l-73.0459 -73.0469c-36.3262 -36.3252 -95.209 -36.3252 -131.535 0
+c-36.3252 36.3252 -36.3252 95.209 0 131.535l73.7676 73.8135c-3.81348 14.9541 -6.53516 30.3027 -6.53516 46.4648c0 0.373047 0.0927734 0.72168 0.0927734 1.09375c0 -0.139648 -0.0927734 -0.279297 -0.0927734 -0.464844
+c-0.185547 0 -0.441406 0.0927734 -0.720703 0.0927734c-102.651 0 -186.047 -83.4424 -186.047 -186.047c0 -102.651 83.3955 -186.046 186.047 -186.046zM573.14 451.163c51.2324 0 93.0234 41.7441 93.0234 93.0225c0 51.2793 -41.791 93.0234 -93.0234 93.0234
+s-93.0234 -41.7441 -93.0234 -93.0234c0 -51.2783 41.791 -93.0225 93.0234 -93.0225z" />
+    <glyph glyph-name="glyph58" unicode="&#x26d4;" 
+d="M387.093 730.232c205.488 0 372.093 -166.604 372.093 -372.093s-166.604 -372.093 -372.093 -372.093s-372.093 166.604 -372.093 372.093s166.604 372.093 372.093 372.093zM387.093 637.209c-153.884 0 -279.069 -125.186 -279.069 -279.069
+c0 -60.1396 19.5352 -115.559 52.0459 -161.163l388.279 388.093c-45.6045 32.5117 -101.023 52.1396 -161.256 52.1396zM387.093 79.0693c153.884 0 279.07 125.187 279.07 279.07c0 60.1396 -19.5352 115.558 -52.0469 161.163l-388.279 -388.094
+c45.6045 -32.5117 101.023 -52.1396 161.256 -52.1396z" />
+    <glyph glyph-name="glyph59" unicode="&#xe011;" 
+d="M759.186 79.0693h-279.069v279.07l279.069 279.069v-558.14zM294.069 79.0693h-279.069v279.07l279.069 279.069v-558.14z" />
+    <glyph glyph-name="glyph60" unicode="&#xe071;" 
+d="M201.047 172.093c-102.744 0 -186.047 83.3027 -186.047 186.047s83.3027 186.046 186.047 186.046v-372.093zM294.069 544.186l186.047 93.0234v-558.14l-186.047 93.0234v372.093zM759.186 311.628h-186.046v93.0234h186.046v-93.0234z" />
+    <glyph glyph-name="glyph61" unicode="&#xe018;" 
+d="M759.186 358.14c0 -205.488 -166.604 -372.093 -372.093 -372.093s-372.093 166.604 -372.093 372.093s166.604 372.093 372.093 372.093s372.093 -166.604 372.093 -372.093zM294.069 218.604l93.0234 -93.0234l93.0234 93.0234h-46.5117v372.093h-93.0234v-372.093
+h-46.5117z" />
+    <glyph glyph-name="glyph62" unicode="&#xe056;" 
+d="M15 637.209c0 31.0078 15.5039 46.5117 46.5117 46.5117s46.5117 -15.5039 46.5117 -46.5117s-15.5039 -46.5117 -46.5117 -46.5117s-46.5117 15.5039 -46.5117 46.5117zM201.047 590.697v93.0234h558.139v-93.0234h-558.139zM201.047 451.163
+c0 31.0078 15.5039 46.5117 46.5117 46.5117c31.0068 0 46.5107 -15.5039 46.5107 -46.5117s-15.5039 -46.5117 -46.5107 -46.5117c-31.0078 0 -46.5117 15.5039 -46.5117 46.5117zM387.093 404.651v93.0234h372.093v-93.0234h-372.093zM201.047 79.0693
+c0 31.0078 15.5039 46.5117 46.5117 46.5117c31.0068 0 46.5107 -15.5039 46.5107 -46.5117c0 -31.0068 -15.5039 -46.5107 -46.5107 -46.5107c-31.0078 0 -46.5117 15.5039 -46.5117 46.5107zM387.093 32.5586v93.0225h372.093v-93.0225h-372.093zM387.093 265.116
+c0 31.0078 15.5039 46.5117 46.5117 46.5117s46.5117 -15.5039 46.5117 -46.5117s-15.5039 -46.5117 -46.5117 -46.5117s-46.5117 15.5039 -46.5117 46.5117zM573.14 218.604v93.0234h186.046v-93.0234h-186.046z" />
+    <glyph glyph-name="glyph63" unicode="&#xe014;" 
+d="M15 358.14c0 205.535 166.604 372.093 372.186 372.093c205.396 0 372 -166.558 372 -372.093c0 -205.488 -166.604 -372.093 -372 -372.093c-205.581 0 -372.186 166.604 -372.186 372.093zM573.14 358.86l-185.954 185.372l-185.418 -185.372h138.813v-186.768h93.0234
+v186.768h139.535z" />
+    <glyph glyph-name="glyph64" unicode="&#xe06d;" 
+d="M480.116 590.697h-232.558c-76.8613 0 -139.535 -62.5811 -139.535 -139.534v-232.559c0 -30.3486 14.7207 -57.4189 37.3252 -74.4883c33.9766 45.2324 88.0469 74.4883 148.721 74.4883h186.047c102.651 0 186.047 83.3955 186.047 186.047
+s-83.3955 186.046 -186.047 186.046zM480.116 683.721c154.163 0 279.069 -125 279.069 -279.069c0 -154.07 -124.906 -279.07 -279.069 -279.07h-186.047c-51.3252 0 -93.0225 -41.6045 -93.0225 -93.0225c-102.651 0 -186.047 83.3018 -186.047 186.046v232.559
+c0 128.441 104.116 232.558 232.559 232.558h232.558z" />
+    <glyph glyph-name="glyph65" unicode="&#xe02f;" 
+d="M669.791 643.768c57.6045 -57.6748 89.3945 -134.349 89.3945 -217.884c0 -79.6279 -31.79 -156.303 -89.3945 -214.116l-70.1396 -69.9531l-65.7676 65.7666l70.1396 70.0938c40.0693 40.1621 62.1396 93.3945 62.1396 148.209
+c0 58.6514 -22.0703 111.884 -62.1396 151.931c-80.1162 80.3018 -219.838 80.3018 -299.954 0.0927734l-126.813 -128.768h116.813v-91.1631h-279.069v279.232h93.0234v-123.697l130.163 130.256c115.209 115.372 316.325 115.279 431.604 0zM444.047 -13.9766
+l-65.7676 65.9531l65.7676 65.7676l65.7676 -65.7676z" />
+    <glyph glyph-name="glyph66" unicode="&#x2602;" 
+d="M387.093 730.232c205.21 0 372.093 -166.93 372.093 -372.093h-93.0225c0 19.209 -15.6279 34.8838 -34.8838 34.8838c-19.1631 0 -34.8838 -15.6748 -34.8838 -34.8838h-93.0234c0 19.209 -15.6279 34.8838 -34.8838 34.8838
+c-19.1631 0 -34.8838 -15.6748 -34.8838 -34.8838v-232.559c0 -76.9531 -62.5117 -139.534 -139.535 -139.534c-76.8604 0 -139.534 62.5811 -139.534 139.534h93.0234c0 -25.6279 20.8828 -46.5117 46.5107 -46.5117c25.6982 0 46.5117 20.8838 46.5117 46.5117v232.559
+c0 19.209 -15.6279 34.8838 -34.8838 34.8838c-19.1621 0 -34.8838 -15.6748 -34.8838 -34.8838h-93.0225c0 19.209 -15.6279 34.8838 -34.8838 34.8838c-19.1631 0 -34.8838 -15.6748 -34.8838 -34.8838h-93.0234c0 205.163 166.977 372.093 372.093 372.093z" />
+    <glyph glyph-name="glyph67" unicode="&#x26a1;" 
+d="M759.186 730.232l-325.581 -372.093l139.535 -93.0234l-558.14 -279.069l325.581 279.069l-139.534 93.0234z" />
+    <glyph glyph-name="glyph68" unicode="&#xe053;" 
+d="M759.186 637.209h-744.186v93.0234h744.186v-93.0234zM480.116 451.163h-465.116v93.0225h465.116v-93.0225zM759.186 172.093h-744.186v93.0234h744.186v-93.0234zM573.14 -13.9531h-558.14v93.0225h558.14v-93.0225zM759.186 32.5586
+c0 -25.6982 -20.8828 -46.5117 -46.5107 -46.5117c-25.791 0 -46.6055 20.8135 -46.6055 46.5117c0 25.6973 20.8145 46.5107 46.6055 46.5107c25.6279 0 46.5107 -20.8135 46.5107 -46.5107z" />
+    <glyph glyph-name="glyph69" unicode="&#xe054;" 
+d="M759.186 637.209h-744.186v93.0234h744.186v-93.0234zM480.116 451.163h-465.116v93.0225h465.116v-93.0225zM759.186 263.116h-744.186v93.0234h744.186v-93.0234zM15 32.5586c0 31.0068 15.5039 46.5107 46.5117 46.5107s46.5117 -15.5039 46.5117 -46.5107
+c0 -31.0078 -15.5039 -46.5117 -46.5117 -46.5117s-46.5117 15.5039 -46.5117 46.5117zM294.069 32.5586c0 -12.8408 -4.5459 -23.8027 -13.6396 -32.8867c-9.0918 -9.08301 -20.0498 -13.625 -32.8711 -13.625c-12.8906 0 -23.8809 4.53809 -32.9707 13.6152
+s-13.6348 20.043 -13.6348 32.8965s4.54492 23.8184 13.6348 32.8955s20.0801 13.6152 32.9707 13.6152c12.8223 0 23.7793 -4.54199 32.8711 -13.625c9.09375 -9.08301 13.6396 -20.0449 13.6396 -32.8857zM387.093 32.5586c0 31.0068 15.5039 46.5107 46.5117 46.5107
+s46.5117 -15.5039 46.5117 -46.5107c0 -31.0078 -15.5039 -46.5117 -46.5117 -46.5117s-46.5117 15.5039 -46.5117 46.5117z" />
+    <glyph glyph-name="glyph70" unicode="&#xe023;" horiz-adv-x="402" 
+d="M340.581 730.232c25.6982 0 46.5117 -20.8135 46.5117 -46.5117v-511.628c0 -102.744 -83.3018 -186.046 -186.046 -186.046s-186.047 83.3018 -186.047 186.046v511.628c0 25.6982 20.8135 46.5117 46.5117 46.5117h279.069zM247.559 172.093
+c25.6973 0 46.5107 20.8145 46.5107 46.5117c0 25.6982 -20.8135 46.5117 -46.5107 46.5117c-25.6982 0 -46.5117 -20.8135 -46.5117 -46.5117c0 -25.6973 20.8135 -46.5117 46.5117 -46.5117zM294.069 358.14v279.069h-186.046v-279.069h46.7207
+c0.348633 25.6973 21.2559 46.5117 47.0469 46.5117c25.79 0 46.6738 -20.8145 47.0459 -46.5117h45.2324z" />
+    <glyph glyph-name="glyph71" unicode="&#xe010;" horiz-adv-x="402" 
+d="M201.768 520.931c0 15.0225 7.51172 22.5342 22.5352 22.5342c15.0225 0 22.5342 -7.51172 22.5342 -22.5342c0 -15.0234 -7.51172 -22.5352 -22.5342 -22.5352c-15.0234 0 -22.5352 7.51172 -22.5352 22.5352zM340.581 730.232
+c12.8486 0 23.8135 -4.54004 32.8926 -13.6191s13.6191 -20.043 13.6191 -32.8926v-511.628c0 -51.3721 -18.165 -95.2227 -54.4941 -131.552s-80.1797 -54.4941 -131.552 -54.4941s-95.2227 18.165 -131.553 54.4941c-36.3291 36.3291 -54.4941 80.1797 -54.4941 131.552
+v511.628c0 12.8496 4.54004 23.8135 13.6191 32.8926s20.043 13.6191 32.8926 13.6191h279.069zM132 265.116c6.2207 0 11.5322 2.2002 15.9336 6.60156s6.60156 9.71289 6.60156 15.9336s-2.2002 11.5312 -6.60156 15.9326s-9.71289 6.60156 -15.9336 6.60156
+s-11.5322 -2.2002 -15.9336 -6.60156s-6.60156 -9.71191 -6.60156 -15.9326s2.2002 -11.5322 6.60156 -15.9336s9.71289 -6.60156 15.9336 -6.60156zM247.559 172.093c12.8486 0 23.8125 4.54004 32.8916 13.6191c9.0791 9.08008 13.6191 20.0439 13.6191 32.8926
+s-4.54004 23.8135 -13.6191 32.8926s-20.043 13.6191 -32.8916 13.6191c-12.8496 0 -23.8135 -4.54004 -32.8926 -13.6191s-13.6191 -20.0439 -13.6191 -32.8926s4.54004 -23.8125 13.6191 -32.8926c9.0791 -9.0791 20.0439 -13.6191 32.8926 -13.6191zM294.069 358.14
+v279.069h-186.046v-279.069h46.7207c0.174805 12.8652 4.83984 23.834 13.9951 32.9053c9.15625 9.07129 20.1729 13.6064 33.0518 13.6064c12.875 0 23.8887 -4.53516 33.04 -13.6045c9.15039 -9.07031 13.8193 -20.0391 14.0059 -32.9072h45.2324z" />
+    <glyph glyph-name="glyph72" unicode="&#xe073;" 
+d="M759.186 451.163v-186.047h-93.0225v-46.5117c0 -25.6279 -20.8838 -46.5117 -46.5117 -46.5117h-558.14c-25.6279 0 -46.5117 20.8838 -46.5117 46.5117v279.07c0 25.5342 20.8838 46.5107 46.5117 46.5107h558.14c25.6279 0 46.5117 -20.9766 46.5117 -46.5107
+v-46.5117h93.0225z" />
+    <glyph glyph-name="glyph73" unicode="&#x2192;" 
+d="M387.465 730.232l371.721 -372.093l-371.721 -372.093l-131.906 131.93l146.883 146.884h-387.441v186.675h387.441l-146.79 146.79z" />
+    <glyph glyph-name="glyph74" unicode="&#xe059;" 
+d="M759.186 730.232v-558.14h-186.046v-186.046h-558.14v558.139h186.047v186.047h558.139zM480.116 79.0693v93.0234h-279.069v186.047h-93.0234v-279.07h372.093zM666.163 265.116v279.069h-372.094v-279.069h372.094z" />
+    <glyph glyph-name="glyph75" unicode="&#x2795;" 
+d="M759.186 451.163v-186.047h-279.069v-279.069h-186.047v279.069h-279.069v186.047h279.069v279.069h186.047v-279.069h279.069z" />
+    <glyph glyph-name="glyph76" unicode="&#x2699;" 
+d="M759.186 312.349l-111.209 -47.418c-3.09277 -8.62793 -6.2793 -17.0938 -10.1855 -25.4424l45.79 -111l-65.7666 -65.7676l-111.559 44.6982c-8.34863 -4 -17.0703 -7.44238 -25.8838 -10.7217l-46.0469 -110.65h-93.0225l-47.0469 110.093
+c-9.25586 3.18555 -18.2559 6.53516 -27.0703 10.7207l-109.837 -45.2324l-65.7676 65.7676l44.2324 110.279c-4.34863 9 -7.97656 18.1855 -11.3486 27.6279l-109.465 45.6045v93.0234l109.372 46.7666c3.37207 9.46582 6.90723 18.6279 11.2793 27.6279l-45.0703 109.559
+l65.7676 65.7676l110.559 -44.4189c8.81348 4.18652 17.7207 7.62793 26.9766 10.9072l45.9766 110.093h93.0234l47.1396 -110.465c8.90723 -3.18652 17.5352 -6.62793 25.9766 -10.7207l110.744 45.6045l65.791 -65.7676l-44.8838 -111.744
+c4 -8.2793 7.25586 -16.7207 10.3486 -25.4424l111.186 -46.3252v-93.0234zM386.372 218.604c77.0234 0 139.535 62.5117 139.535 139.535s-62.5117 139.535 -139.535 139.535s-139.535 -62.5117 -139.535 -139.535s62.5117 -139.535 139.535 -139.535z" />
+    <glyph glyph-name="glyph77" unicode="&#x26bf;" 
+d="M526.628 637.209c-76.9531 0 -139.535 -62.5811 -139.535 -139.534c0 -7.25586 0.860352 -15.2559 2.72168 -25.2559l8.90625 -48.1396l-34.6045 -34.6045l-256.093 -256.094v-54.5117h93.0234v93.0234h93.0225v93.0234h93.0234v38.5117l27.2559 27.2559l4.2793 4.2793
+l34.6045 34.6045l48.1396 -8.90723c10 -1.81348 18 -2.7207 25.2559 -2.7207c76.9531 0 139.535 62.5811 139.535 139.535c0 76.9531 -62.582 139.534 -139.535 139.534zM526.628 730.232c128.441 0 232.558 -104.116 232.558 -232.558
+c0 -128.442 -104.116 -232.559 -232.558 -232.559c-14.5352 0 -28.5352 1.7207 -42.2324 4.2793l-4.2793 -4.2793v-93.0234h-93.0234v-93.0234h-93.0234v-93.0225h-279.069v186.046l283.349 283.349c-2.55762 13.6982 -4.2793 27.6982 -4.2793 42.2334
+c0 128.441 104.116 232.558 232.559 232.558zM480.303 497.675c0 31.0068 15.5039 46.5107 46.5107 46.5107c31.0078 0 46.5117 -15.5039 46.5117 -46.5107c0 -31.0078 -15.5039 -46.5117 -46.5117 -46.5117c-31.0068 0 -46.5107 15.5039 -46.5107 46.5117z" />
+    <glyph glyph-name="glyph78" unicode="&#xe04c;" 
+d="M759.186 -13.9531l-558.139 372.093l558.139 372.093v-744.186zM15 79.0693v558.14h186.047v-558.14h-186.047z" />
+    <glyph glyph-name="glyph79" unicode="&#xe003;" 
+d="M387.138 544.186c-144.116 0 -233.14 -38.5107 -264.581 -114.371c-27 -64.9541 -13 -145.814 8.81348 -205.582c52.6045 36.3486 99.9307 40.8838 209.303 40.8838h308.093c9.09277 27.791 17.4414 61.7676 17.4414 93.0234
+c0 90.0234 -21.6279 186.046 -279.069 186.046zM387.138 637.209c186.046 0 372.093 -46.5117 372.093 -279.069c0 -93.0234 -47.9531 -186.047 -47.9531 -186.047h-370.604c-139.442 0 -139.442 0 -232.466 -93.0234c-92.9531 93.0234 -241.302 558.14 278.931 558.14z" />
+    <glyph glyph-name="glyph80" unicode="&#xe05a;" 
+d="M759.186 637.209v-93.0234h-93.0225v-418.604c0 -77.0225 -62.5117 -139.534 -139.535 -139.534h-279.069c-77.0234 0 -139.535 62.5117 -139.535 139.534v418.604h-93.0234v93.0234h93.0234h93.0234c0 51.4189 41.6045 93.0234 93.0225 93.0234h186.047
+c51.4189 0 93.0234 -41.6045 93.0234 -93.0234h93.0234h93.0225zM573.14 125.581v418.604h-372.093v-418.604c0 -25.6279 20.8838 -46.5117 46.5117 -46.5117h279.069c25.6279 0 46.5117 20.8838 46.5117 46.5117zM247.559 218.604
+c0 31.0078 15.5039 46.5117 46.5107 46.5117c31.0078 0 46.5117 -15.5039 46.5117 -46.5117s-15.5039 -46.5117 -46.5117 -46.5117c-31.0068 0 -46.5107 15.5039 -46.5107 46.5117zM247.559 404.651c0 31.0078 15.5039 46.5117 46.5107 46.5117
+c31.0078 0 46.5117 -15.5039 46.5117 -46.5117s-15.5039 -46.5117 -46.5117 -46.5117c-31.0068 0 -46.5107 15.5039 -46.5107 46.5117zM433.604 218.604c0 31.0078 15.5039 46.5117 46.5117 46.5117s46.5117 -15.5039 46.5117 -46.5117s-15.5039 -46.5117 -46.5117 -46.5117
+s-46.5117 15.5039 -46.5117 46.5117zM433.604 404.651c0 31.0078 15.5039 46.5117 46.5117 46.5117s46.5117 -15.5039 46.5117 -46.5117s-15.5039 -46.5117 -46.5117 -46.5117s-46.5117 15.5039 -46.5117 46.5117z" />
+    <glyph glyph-name="glyph81" unicode="&#xe027;" 
+d="M201.047 172.093v186.047l93.0225 93.0234l93.0234 -93.0234l186.047 186.046l186.046 -186.046v-186.047h-558.139zM108.023 79.0693h651.162l-93.0225 -93.0225h-558.14h-93.0234v93.0225v558.14l93.0234 93.0234v-651.163z" />
+    <glyph glyph-name="glyph82" unicode="&#xe007;" 
+d="M619.116 446.209c75.3955 -14.5811 140.069 -67.3018 140.069 -181.093c0 -133.721 -93.0225 -186.047 -185.953 -186.047h-93.1162c-51.4189 0 -93.0234 -41.6045 -93.0234 -93.0225c-102.744 0 -185.953 83.3018 -185.953 186.046v93.0234
+c0 16.7207 1.62793 32.1162 4.62793 46.5117h88.3018c2.90723 0 5.62793 -1.18652 8.34961 -1.6748c-5.07031 -11.9531 -8.34961 -26.4414 -8.34961 -44.8369v-93.0234c0 -30.3486 14.7217 -57.418 37.2559 -74.4883c33.9541 45.2324 88.21 74.4883 148.791 74.4883h93.1162
+c92.9307 0 92.9307 69.9541 92.9307 93.0234c0 24.8838 -0.535156 79.4883 -67.582 90.6514c12.7217 27.7676 19.9072 58.1855 20.5352 90.4414zM387.093 637.209h-185.953c-93.1162 0 -93.1162 -65.2324 -93.1162 -93.0234c0 -23.0225 0 -93.0225 93.1162 -93.0225h92.9297
+c60.6748 0 114.838 -29.3027 148.791 -74.4424c22.5352 16.9766 37.2559 44 37.2559 74.4424v93.0225c0 80.9307 -58.3252 93.0234 -93.0234 93.0234zM387.093 730.232c93.0234 0 186.14 -57.6514 186.14 -186.047v-93.0225c0 -102.744 -83.3955 -186.047 -186.14 -186.047
+c0 51.3252 -41.6045 93.0234 -93.0234 93.0234h-92.9297c-93.1162 0 -186.14 52.3252 -186.14 186.046c0 136.629 93.0234 186.047 186.14 186.047h185.953z" />
+    <glyph glyph-name="glyph83" unicode="&#xe064;" 
+d="M387.093 730.232c205.488 0 372.093 -166.604 372.093 -372.093s-166.604 -372.093 -372.093 -372.093s-372.093 166.604 -372.093 372.093s166.604 372.093 372.093 372.093zM387.814 266.651c51.0693 0 92.3018 41.2559 92.3018 92.209
+c0 51.0703 -41.2324 92.3027 -92.3018 92.3027c-50.8838 0 -92.21 -41.2559 -92.21 -92.3027c0 -50.9766 41.3262 -92.209 92.21 -92.209z" />
+    <glyph glyph-name="glyph84" unicode="&#x275e;" 
+d="M15 637.209h279.069v-279.069c0 -153.884 -125.186 -279.07 -279.069 -279.07v93.0234c102.651 0 186.047 83.4883 186.047 186.047h-186.047v279.069zM480.116 637.209h279.069v-279.069c0 -153.884 -125.186 -279.07 -279.069 -279.07v93.0234
+c102.651 0 186.047 83.4883 186.047 186.047h-186.047v279.069z" />
+    <glyph glyph-name="glyph85" unicode="&#xe01b;" 
+d="M387 234.14h0.0927734c0 -137 -111.093 -248.093 -248.093 -248.093c-45.0469 0 -87.209 12 -123.628 32.8828l-0.37207 0.46582c74.0469 42.8604 123.907 122.907 124 214.651l-0.0927734 0.0927734c0 68.4883 55.5117 124 124 124s124.093 -55.5117 124.093 -124z
+M731.93 702.977c36.3496 -36.3252 36.3496 -95.209 0 -131.535l-265.162 -265.162c-21.791 61.3955 -70.1162 109.744 -131.535 131.628l265.163 265.069c36.3252 36.3262 95.209 36.3262 131.534 0z" />
+    <glyph glyph-name="glyph86" unicode="&#x2601;" 
+d="M573.14 497.675c102.558 0 186.046 -83.4424 186.046 -186.047c0 -102.651 -83.4883 -186.047 -186.046 -186.047h-372.093c-102.559 0 -186.047 83.3955 -186.047 186.047c0 102.604 83.4883 186.047 186.047 186.047c8.25586 0 16.4414 -1.2334 24.6279 -2.32617
+c31.9766 56.6045 91.9297 95.3486 161.418 95.3486c68.0469 0 128 -38.2783 160.419 -95.6045c8.46484 1.16309 16.8135 2.58203 25.6279 2.58203zM573.14 218.604c51.3252 0 93.0234 41.7441 93.0234 93.0234s-41.6982 93.0234 -93.0234 93.0234
+c-51.3262 0 -93.0234 -41.7441 -93.0234 -93.0234h-93.0234c0 60.5811 29.5352 113.953 74.3955 147.931c-17.0693 23.209 -44.3252 38.1162 -74.3955 38.1162c-51.3252 0 -93.0234 -41.7441 -93.0234 -93.0234c0 -11.21 1.90723 -22.1631 5.81445 -32.4883
+l-87.1162 -32.6045c-7.62793 20.3486 -11.4424 41.8828 -11.6279 63.8135c-47.9766 -1.39551 -93.1162 -40.7441 -93.1162 -91.7441c0 -51.2793 41.6973 -93.0234 93.0234 -93.0234h372.093z" />
+    <glyph glyph-name="glyph87" unicode="&#xe025;" 
+d="M387.093 637.209c205.488 0 372.093 -275.441 372.093 -275.441s-166.604 -282.698 -372.093 -282.698s-372.093 282.698 -372.093 282.698s166.604 275.441 372.093 275.441zM387.093 172.093c102.744 0 186.047 83.3027 186.047 186.047
+s-83.3027 186.046 -186.047 186.046s-186.046 -83.3018 -186.046 -186.046s83.3018 -186.047 186.046 -186.047zM294.069 357.768c0 62.0156 31.0078 93.0234 93.0234 93.0234s93.0234 -31.0078 93.0234 -93.0234s-31.0078 -93.0234 -93.0234 -93.0234
+s-93.0234 31.0078 -93.0234 93.0234z" />
+    <glyph glyph-name="glyph88" unicode="&#xe048;" 
+d="M387.093 730.232c205.488 0 372.093 -166.604 372.093 -372.093s-166.604 -372.093 -372.093 -372.093s-372.093 166.604 -372.093 372.093s166.604 372.093 372.093 372.093zM247.559 172.093l372.278 186.047l-372.278 186.046v-372.093z" />
+    <glyph glyph-name="glyph89" unicode="&#xe041;" 
+d="M759.186 172.093h-558.325v-93.0234l-185.86 139.535l185.86 139.535v-93.0234h558.325v-93.0234zM15 451.163v93.0225h558.14v93.0234l186.046 -139.534l-186.046 -139.535v93.0234h-558.14z" />
+    <glyph glyph-name="glyph90" unicode="&#xe006;" 
+d="M731.93 702.977c36.3496 -36.3252 36.3496 -95.209 0 -131.535l-351.744 -351.837c-8.16211 30.6982 -23.4414 59.9541 -47.418 83.9307c-23.6045 23.6279 -52.6748 39.418 -84.1162 47.6045l351.744 351.837c36.3252 36.3262 95.209 36.3262 131.534 0zM267 106.232
+l-252 -120.186l120.465 251.721c36.3262 36.3252 95.21 36.3252 131.535 0c36.3252 -36.3262 36.3252 -95.209 0 -131.535z" />
+    <glyph glyph-name="glyph91" unicode="&#xe070;" 
+d="M387.093 265.116c0 62.0156 31.0078 93.0234 93.0234 93.0234s93.0234 -31.0078 93.0234 -93.0234s-31.0078 -93.0234 -93.0234 -93.0234s-93.0234 31.0078 -93.0234 93.0234zM666.163 544.186c25.7041 0 47.6338 -9.08105 65.7891 -27.2432
+c18.1553 -18.1611 27.2334 -40.0879 27.2334 -65.7793v-465.116h-744.186v465.116c0 25.6855 9.08203 47.6113 27.2471 65.7754c18.165 18.165 40.0898 27.2471 65.7764 27.2471h186.14l79.3486 153.838c3.04004 9.46191 8.61621 17.1953 16.7285 23.2012
+c8.1123 6.00488 17.2959 9.00781 27.5508 9.00781h127.721c10.1338 0 19.2324 -2.93359 27.2939 -8.80078c8.0625 -5.86719 13.6855 -13.4531 16.8691 -22.7568zM154.535 358.14c12.8486 0 23.8125 4.54004 32.8926 13.6191c9.0791 9.0791 13.6191 20.0439 13.6191 32.8926
+s-4.54004 23.8125 -13.6191 32.8926c-9.08008 9.0791 -20.0439 13.6191 -32.8926 13.6191s-23.8135 -4.54004 -32.8926 -13.6191c-9.0791 -9.08008 -13.6191 -20.0439 -13.6191 -32.8926s4.54004 -23.8135 13.6191 -32.8926s20.043 -13.6191 32.8926 -13.6191z
+M480.116 79.0693c51.3721 0 95.2227 18.165 131.553 54.4941c36.3291 36.3301 54.4941 80.1807 54.4941 131.553s-18.165 95.2227 -54.4941 131.553c-36.3301 36.3291 -80.1807 54.4941 -131.553 54.4941s-95.2227 -18.165 -131.553 -54.4941
+c-36.3291 -36.3301 -54.4941 -80.1807 -54.4941 -131.553s18.165 -95.2227 54.4941 -131.553c36.3301 -36.3291 80.1807 -54.4941 131.553 -54.4941z" />
+    <glyph glyph-name="glyph92" unicode="&#xe03a;" 
+d="M759.186 358.14c0 -205.488 -166.604 -372.093 -372.093 -372.093s-372.093 166.604 -372.093 372.093s166.604 372.093 372.093 372.093s372.093 -166.604 372.093 -372.093zM247.559 311.628h279.069v-46.5117l93.0234 93.0234l-93.0234 93.0234v-46.5117h-279.069
+v46.5117l-93.0234 -93.0234l93.0234 -93.0234v46.5117z" />
+    <glyph glyph-name="glyph93" unicode="&#x2935;" 
+d="M666.163 311.628v-46.5117h93.0225l-186.046 -186.047l-186.047 186.047h93.0234v46.5117c0 128.441 -104.116 232.558 -232.558 232.558c-128.442 0 -232.559 -104.116 -232.559 -232.558c0 179.768 145.814 325.581 325.581 325.581
+c179.768 0 325.582 -145.813 325.582 -325.581z" />
+    <glyph glyph-name="glyph94" unicode="&#xe039;" 
+d="M247.559 358.14l46.5107 -46.5117h-139.534v-93.0234l-139.535 139.535l139.535 139.535v-93.0234h139.534zM525.186 358.14l-45.0693 46.5117h139.535v93.0234l139.534 -139.535l-139.534 -139.535v93.0234h-139.535z" />
+    <glyph glyph-name="glyph95" unicode="&#xe026;" 
+d="M507.372 405.116l-188 318.303c22.0234 4.0459 44.5117 6.81348 67.7207 6.81348c97.9307 0 186.232 -38.5576 252.628 -100.372zM478.977 265.116l193.628 328.953c53.3262 -64.3721 86.5811 -145.86 86.5811 -235.93c0 -32.3486 -5.44141 -63.1396 -13.1855 -93.0234
+h-267.023zM398.721 497.675h-356.093c41.0469 101.256 125.675 178.906 230.325 212.837zM378.559 218.604h353.022c-40.6973 -100.372 -124.186 -177.512 -227.65 -212.023zM296.512 451.163l-194.163 -329.954c-53.7441 64.6055 -87.3486 146.442 -87.3486 236.931
+c0 32.3486 5.39551 63.1396 13.1631 93.0234h268.349zM268.907 312.535l188.907 -319.396c-22.9307 -4.37207 -46.5117 -7.09277 -70.7217 -7.09277c-97.4648 0 -185.418 38.3252 -251.768 99.5576z" />
+    <glyph glyph-name="glyph96" unicode="&#xe031;" horiz-adv-x="588" 
+d="M480.116 311.628h93.0234c0 -154.069 -125 -279.069 -279.07 -279.069c-154.069 0 -279.069 125 -279.069 279.069s125 279.069 279.069 279.069h93.5586v93.0234l139.813 -139.535l-139.813 -140v92.4893l-93.5586 1c-102.65 0 -186.046 -83.3955 -186.046 -186.047
+s83.3955 -186.047 186.046 -186.047c102.651 0 186.047 83.3955 186.047 186.047z" />
+    <glyph glyph-name="glyph97" unicode="&#xe074;" 
+d="M278.628 271.209c6.81348 -7.62793 13.9072 -14.7207 21.3486 -21.4414c-43.0693 -43.0703 -102.651 -102.675 -132.186 -132.279c-10.0938 10.1865 -11.4424 11.4424 -21.4424 21.4424c29.6279 29.5342 89.1396 89.209 132.279 132.278zM483.209 730.232
+c152.163 0 275.977 -123.721 275.954 -275.907c0 -152.069 -123.814 -275.79 -275.978 -275.79c-36.3252 0 -70.8604 7.44141 -102.65 20.3486l-212.744 -212.837l-152.791 152.884l212.651 212.674c-12.8145 31.791 -20.2559 66.3262 -20.2559 102.744
+c0 152.163 123.744 275.884 275.813 275.884zM483.209 271.465c100.931 0 183.047 82.1396 183.047 182.86c0 100.838 -82.1162 182.954 -183.047 182.954c-100.744 0 -182.86 -82.1162 -182.86 -182.954c0 -100.744 82.1162 -182.86 182.86 -182.86z" />
+    <glyph glyph-name="glyph98" unicode="&#xe06e;" horiz-adv-x="588" 
+d="M387.093 730.232c102.744 0 186.047 -83.3018 186.047 -186.047v-372.093c0 -102.744 -83.3027 -186.046 -186.047 -186.046h-186.046c-102.744 0 -186.047 83.3018 -186.047 186.046v372.093c0 102.745 83.3027 186.047 186.047 186.047h186.046zM294.069 31.1162
+c26.4893 0 47.9541 21.4424 47.9541 47.9531c0 26.5117 -21.4883 47.9541 -47.9541 47.9541c-26.4648 0 -47.9531 -21.4424 -47.9531 -47.9541c0 -26.5107 21.4648 -47.9531 47.9531 -47.9531zM480.116 172.093v372.093c0 51.2334 -41.791 93.0234 -93.0234 93.0234
+h-186.046c-51.2793 0 -93.0234 -41.79 -93.0234 -93.0234v-372.093h372.093z" />
+    <glyph glyph-name="glyph99" unicode="&#xe046;" horiz-adv-x="681" 
+d="M480.116 730.232h93.0234v-90.1162c0 -62.1396 -24.1631 -120.465 -68.1396 -164.419l-263.163 -261.628c-23.8838 -24 -37.3486 -55.0459 -39.79 -88.4883h92.0225l-139.534 -139.534l-139.535 139.534h94.1162c2.53516 58.2334 25.4424 112.744 67.0469 154.256
+l263.162 261.722c26.2559 26.2559 40.791 61.3018 40.791 98.5576v90.1162zM572.047 125.581h94.1162l-139.535 -139.534l-139.535 139.534h92.0234c-2.46484 33.4424 -15.9072 64.6045 -39.8838 88.4883l-52.1396 51.8613l65.9541 65.6045l51.8604 -51.5117
+c41.6045 -41.6982 64.6045 -96.209 67.1396 -154.442z" />
+    <glyph glyph-name="glyph100" unicode="&#xe06b;" 
+d="M15 -13.9531v465.116h744.186v-465.116h-744.186zM433.604 544.186v186.047h232.559l93.0225 -186.047h-325.581zM340.581 730.232v-186.047h-325.581l93.0234 186.047h232.558z" />
+    <glyph glyph-name="glyph101" unicode="&#xe00a;" horiz-adv-x="681" 
+d="M573.14 -13.9531v744.186h93.0234v-744.186h-93.0234zM387.093 -13.9531v558.139h93.0234v-558.139h-93.0234zM201.047 -13.9531v372.093h93.0225v-372.093h-93.0225zM15 -13.9531v186.046h93.0234v-186.046h-93.0234z" />
+    <glyph glyph-name="glyph102" unicode="&#x2764;" 
+d="M552.512 590.697c-46.8604 0 -89.6514 -50.3252 -89.6514 -50.3252l-75.7676 -80.4883l-75.7676 80.4883s-42.7666 50.3252 -89.6504 50.3252c-57.1396 0 -104.466 -42.3252 -112.466 -97.3945l277.977 -319.023l277.791 319.023
+c-8 55.0693 -55.3252 97.3945 -112.465 97.3945zM552.512 683.721c114.023 0 206.674 -92.8369 206.674 -206.768v-17.0693l-371.906 -427.325l-372.279 427.325v17.0693c0 113.931 92.6514 206.768 206.675 206.768c69.3945 0 127.906 -36.6973 165.418 -89.3955
+c37.5117 52.6982 96.0234 89.3955 165.419 89.3955z" />
+    <glyph glyph-name="glyph103" unicode="&#xe072;" 
+d="M201.047 172.093c-102.744 0 -186.047 83.3027 -186.047 186.047s83.3027 186.046 186.047 186.046v-372.093zM294.069 544.186l186.047 93.0234v-558.14l-186.047 93.0234v372.093zM573.232 172.186v93.0234c7.7207 0 15.5352 1 23.3486 2.90723
+c40.791 10.3486 69.582 47.4189 69.582 90.0234s-28.791 79.5811 -70.0469 90.0234c-7.34863 1.81348 -15.1631 2.81348 -22.8838 2.81348v93.0234c15.2559 0 30.5117 -1.90723 45.5117 -5.53516c82.7676 -21.0693 140.441 -95.1162 140.441 -180.325
+c0 -85.3027 -57.6738 -159.419 -140.162 -180.325c-15.4424 -3.72168 -30.7207 -5.62891 -45.791 -5.62891z" />
+    <glyph glyph-name="glyph104" unicode="&#x2718;" 
+d="M387.093 730.232c205.488 0 372.093 -166.604 372.093 -372.093s-166.604 -372.093 -372.093 -372.093s-372.093 166.604 -372.093 372.093s166.604 372.093 372.093 372.093zM571.14 239.86l-118.279 118.279l118.279 118.279l-65.7676 65.7666l-118.279 -118.278
+l-118.279 118.278l-65.7666 -65.7666l118.278 -118.279l-118.278 -118.279l65.7666 -65.7676l118.279 118.279l118.279 -118.279z" />
+    <glyph glyph-name="glyph105" unicode="&#xe077;" 
+d="M745.529 716.604c18.1621 -18.1631 18.1621 -47.6045 -0.0234375 -65.7676l-94.9307 -94.7441c59.791 -90.209 49.9766 -213.116 -29.6045 -292.604l-139.256 -139.535c-45.4189 -45.418 -106.86 -68.1387 -164.419 -68.1387
+c-44.6973 0 -89.4883 12.8135 -128.278 38.6045l-94.6514 -94.7441c-9.09277 -9.09375 -20.9766 -13.6279 -32.8838 -13.6279s-23.791 4.53418 -32.8604 13.6279c-18.1631 18.1621 -18.1631 47.6045 0 65.7666l94.6514 94.7441
+c-59.791 90.21 -49.9775 213.117 29.6045 292.605l139.534 139.534c43.4658 45.4189 104.907 68.1396 164.419 68.1396c44.6982 0 89.209 -12.8135 128 -38.6045l94.6514 94.7441c18.1631 18.1631 47.8838 18.1631 66.0469 0zM596.087 427.907
+c0 21.1621 -5.06934 41.3252 -14 59.7676l-70.6748 -70.8604c-9.06934 -9.09375 -20.9766 -13.6279 -32.79 -13.6279c-11.9072 0 -23.791 4.53418 -32.8838 13.6279c-18.1631 18.1621 -18.1631 47.6045 0 65.7666l70.7676 70.8604c-18.5117 8.81445 -38.6045 14 -59.6748 14
+c-37.2559 0 -74.2559 -14.5342 -100.604 -40.8828l-137.581 -139.535c-26.3496 -26.3486 -40.8838 -61.3955 -40.8838 -98.6514c0 -21.1631 5.09277 -41.3252 14 -59.7676l69.3945 69.3955c18.1631 18.1631 47.6055 18.1631 63.8145 0
+c18.1631 -18.1631 18.1631 -47.6045 0 -65.7676l-67.4414 -69.3955c18.5342 -8.81348 38.6973 -14 59.7666 -14c35.3027 0 72.21 14.5352 98.6514 40.8838l139.256 139.535c26.3486 26.3486 40.8838 61.3955 40.8838 98.6514z" />
+    <glyph glyph-name="glyph106" unicode="&#x263e;" 
+d="M152.093 517.488c-28.0693 -44.4414 -44.0693 -96.5352 -44.0693 -151.116c0 -158.488 128.813 -287.303 287.256 -287.279c54.6045 0 106.744 15.9766 151.162 44.1396c-208.302 19.6279 -374.628 185.907 -394.349 394.256zM290.441 730.232
+c-29.2549 -50.6514 -47.2549 -108.604 -47.2324 -171.279c0 -190.302 154.232 -344.697 344.651 -344.697c62.6748 0 120.628 17.9766 171.325 47.2324c-45.7666 -158.628 -190.488 -275.441 -363.906 -275.441c-210.047 0 -380.279 170.232 -380.279 380.325
+c0 173.419 116.744 318.069 275.441 363.86z" />
+    <glyph glyph-name="glyph107" unicode="&#xe01e;" 
+d="M704.488 412l-13.6045 -13.7207l67.0459 -66.9072l-65.7666 -65.5811l-66.9541 66.9062l-346.396 -346.65h-172.418l-91.3955 92.8369v168.604l347.721 347.931l-69.1855 69.1855l65.5342 65.6279l69.1162 -69.2324l13.8145 13.7207
+c36.1396 36.2559 83.6973 54.4189 131.303 54.4189c47.3486 0 94.9297 -18.1631 131.186 -54.4189c72.6748 -72.4883 72.6748 -190.186 0 -262.721zM255 93.791l304.512 304.697l-131.14 131.14l-300.372 -300.559c32.3486 -1.09277 64.4883 -13.1855 89.1162 -37.8838
+c26.791 -26.8135 38.9766 -62.1387 37.8838 -97.3945z" />
+    <glyph glyph-name="glyph108" unicode="&#xe036;" horiz-adv-x="681" 
+d="M570.419 588.697c61.7666 -61.5811 95.7441 -143.441 95.7441 -230.558c0 -179.512 -146.07 -325.581 -325.582 -325.581v-46.5117l-93.0225 93.0225l93.0225 93.0234v-46.5117c128.279 0 232.559 104.279 232.559 232.559c0 62.2324 -24.3486 120.721 -68.3027 164.697z
+M433.604 637.209l-93.0234 -93.0234v46.5117c-128.278 0 -232.558 -104.278 -232.558 -232.558c0 -62.2324 24.3486 -120.721 68.3018 -164.698l-65.5811 -65.8604c-61.7676 61.582 -95.7441 143.442 -95.7441 230.559c0 179.512 146.069 325.581 325.581 325.581v46.5117z
+" />
+    <glyph glyph-name="glyph109" unicode="&#xe02c;" 
+d="M15 79.0693c0 62.0312 31.0156 93.0469 93.0469 93.0469c62.0303 0 93.0459 -31.0156 93.0459 -93.0469c0 -62.0303 -31.0156 -93.0459 -93.0459 -93.0459c-62.0312 0 -93.0469 31.0156 -93.0469 93.0459zM155.069 265.023v93.1162
+c64.1133 0 118.909 -22.7549 164.388 -68.2646s68.2178 -100.336 68.2178 -164.479h-93.0703c0 38.5186 -13.6357 71.4189 -40.9062 98.7031c-27.2705 27.2832 -60.1475 40.9248 -98.6289 40.9248zM154.441 451.163v93.0225c75.7012 0 145.709 -18.7051 210.023 -56.1143
+c64.3154 -37.4102 115.173 -88.2793 152.574 -152.608c37.4004 -64.3291 56.1006 -134.352 56.1006 -210.067h-93.0703c0 58.8857 -14.5469 113.351 -43.6416 163.393c-29.0938 50.042 -68.6504 89.6152 -118.671 118.719
+c-50.0195 29.1045 -104.458 43.6562 -163.315 43.6562zM154.441 637.209v93.0234c81.8926 0 160.163 -15.9893 234.812 -47.9668c74.6484 -31.9775 138.979 -74.9736 192.988 -128.988s97.0029 -118.354 128.979 -193.017
+c31.9766 -74.6641 47.9648 -152.952 47.9648 -234.865h-93.0225c0 69.3096 -13.5303 135.554 -40.5908 198.733c-27.0605 63.1807 -63.4434 117.625 -109.146 163.333c-45.7041 45.708 -100.14 82.0928 -163.305 109.155c-63.165 27.0605 -129.393 40.5918 -198.681 40.5918
+z" />
+    <glyph glyph-name="glyph110" unicode="&#x2139;" horiz-adv-x="402" 
+d="M61.5117 358.14h-46.5117v93.0234h279.069l0.186523 -325.582c0 -25.6973 20.8838 -46.5117 46.5117 -46.5117h46.3252v-93.0225h-372.093v93.0225h46.5117c25.6973 0 46.5117 20.8145 46.5117 46.5117v186.047c0 25.6973 -20.8145 46.5117 -46.5117 46.5117z
+M108.023 637.209c0 62.0156 31.0078 93.0234 93.0234 93.0234c62.0146 0 93.0225 -31.0078 93.0225 -93.0234s-31.0078 -93.0234 -93.0225 -93.0234c-62.0156 0 -93.0234 31.0078 -93.0234 93.0234z" />
+    <glyph glyph-name="glyph111" unicode="&#xe02a;" 
+d="M434.14 405.232v134.628c65.9531 -17.0234 117.558 -68.6738 134.628 -134.628h-134.628zM434.14 311.047h134.628c-17.0703 -65.9072 -68.6748 -117.605 -134.628 -134.582v134.582zM339.953 405.232h-134.628c16.9775 65.9531 68.6748 117.604 134.628 134.628
+v-134.628zM339.953 311.047v-134.582c-65.9531 16.9766 -117.65 68.6748 -134.628 134.582h134.628zM434.14 635.977v94.2559c170.069 -21.4883 303.512 -154.977 325.046 -325h-94.3018c-20 118 -112.744 210.768 -230.744 230.744zM109.209 405.232h-94.209
+c21.4414 170.023 154.977 303.512 324.953 325v-94.2559c-118.022 -19.9766 -210.767 -112.721 -230.744 -230.744zM339.953 80.3486v-94.3018c-169.977 21.4414 -303.512 154.977 -324.953 325h94.209c20 -118.047 112.744 -210.722 230.744 -230.698zM664.884 311.047
+h94.3018c-21.5342 -170.023 -154.977 -303.559 -325.046 -325v94.3018c118 19.9766 210.768 112.651 230.744 230.698z" />
+    <glyph glyph-name="glyph112" unicode="&#xe057;" 
+d="M759.186 79.0693l-93.0225 -93.0225l-325.582 325.674l-139.534 -139.535l-186.047 558.047l558.14 -186.047l-139.535 -139.534z" />
+    <glyph glyph-name="glyph113" unicode="&#x2796;" 
+d="M15 265.116v186.047h744.186v-186.047h-744.186z" />
+    <glyph glyph-name="glyph114" unicode="&#xe00b;" 
+d="M293.791 450.441v93.7441h185.86v-93.7441h-185.86zM665.535 543.093l92.9297 -93.0234h-92.9297v-464.022h-557.604c-51.3262 0 -92.9307 41.6045 -92.9307 93.0225v558.14c0 51.4189 41.6045 93.0234 92.9307 93.0234h557.604v-93.3955h93.6504zM572.604 79.0693
+h-0.0234375v558.14h-371.721v-558.14h371.744z" />
+    <glyph glyph-name="glyph115" unicode="&#xe061;" 
+d="M712.675 311.628c25.6973 0 46.5107 -20.8135 46.5107 -46.5117v-186.047c0 -25.6973 -20.8135 -46.5107 -46.5107 -46.5107h-139.535v279.069v93.0234c0 102.651 -83.3955 186.046 -186.047 186.046s-186.046 -83.3945 -186.046 -186.046v-93.0234v-279.069h-139.535
+c-25.6982 0 -46.5117 20.8135 -46.5117 46.5107v186.047c0 25.6982 20.8135 46.5117 46.5117 46.5117h46.5117v93.0234c0 154.069 125 279.069 279.069 279.069c154.07 0 279.07 -125 279.07 -279.069v-93.0234h46.5117z" />
+    <glyph glyph-name="numbersign" unicode="#" horiz-adv-x="681" 
+d="M666.163 451.163h-128l-23.1631 -186.047h151.163v-93.0234h-162.791l-23.3027 -186.046h-93.0225l23.2559 186.046h-186l-23.2559 -186.046h-93.0234l23.2559 186.046h-116.279v93.0234h127.907l23.209 186.047h-151.116v93.0225h162.744l23.2559 186.047h93.0234
+l-23.2559 -186.047h186l23.2559 186.047h93.0234l-23.2559 -186.047h116.372v-93.0225zM421.931 265.116l23.209 186.047h-186l-23.209 -186.047h186z" />
+    <glyph glyph-name="glyph117" unicode="&#xe013;" 
+d="M387.093 -13.9531c-205.488 0 -372.093 166.604 -372.093 372.046c0 205.535 166.604 372.14 372.093 372.14s372.093 -166.604 372.093 -372.14c0 -205.441 -166.604 -372.046 -372.093 -372.046zM386.372 544.186l-185.325 -186.093l185.325 -185.278v138.813h186.768
+v92.9766h-186.768v139.581z" />
+    <glyph glyph-name="glyph118" unicode="&#xe017;" 
+d="M387.093 -13.9531c-205.488 0 -372.093 166.604 -372.093 372.093s166.604 372.093 372.093 372.093s372.093 -166.604 372.093 -372.093s-166.604 -372.093 -372.093 -372.093zM247.559 451.163l-93.0234 -93.0234l93.0234 -93.0234v46.5117h372.093v93.0234h-372.093
+v46.5117z" />
+    <glyph glyph-name="glyph119" unicode="&#xe050;" 
+d="M586.768 92.6973l-106.651 -106.65v279.069h279.069l-106.65 -106.651l106.65 -106.65l-65.7666 -65.7676zM15 451.163l106.651 106.651l-105.559 105.65l65.7676 65.7676l105.559 -105.651l106.65 106.651v-279.069h-279.069zM15 51.8145l106.651 106.65
+l-106.651 106.651h279.069v-279.069l-106.65 106.65l-106.651 -106.65zM480.116 451.163v279.069l106.651 -106.651l105.744 105.651l65.7676 -65.7676l-105.744 -105.65l106.65 -106.651h-279.069z" />
+    <glyph glyph-name="glyph120" unicode="&#xe02e;" 
+d="M666.163 172.093c51.418 0 93.0225 -41.6973 93.0225 -93.0234c0 -51.3252 -41.6045 -93.0225 -93.0225 -93.0225c-51.4189 0 -93.0234 41.6973 -93.0234 93.0225c0 11.7217 2.7207 22.7217 6.7207 33.0703l-140.907 100.558
+c-25.2324 -25.0693 -60.0459 -40.6045 -98.3721 -40.6045c-77.0225 0 -139.534 62.5117 -139.534 139.535c0 7.25586 1.09277 14.2559 2.18555 21.2559l-111.372 37.1631c-8.18555 -7.18652 -18.6279 -11.9072 -30.3486 -11.9072
+c-25.6982 0 -46.5117 20.8135 -46.5117 46.5117c0 25.6973 20.8135 46.5117 46.5117 46.5117c22.2559 0 39.9766 -15.9072 44.5117 -36.791l112 -37.4189c23.5352 43.9541 69.3252 74.21 122.558 74.21c30.1631 0 57.7676 -9.72168 80.6748 -26.0703l165.884 165.977
+c-8.09277 13.791 -14 28.9775 -14 46.1396c0 51.4189 41.6045 93.0234 93.0234 93.0234c51.418 0 93.0225 -41.6045 93.0225 -93.0234c0 -51.418 -41.6045 -93.0234 -93.0225 -93.0234c-17.1631 0 -32.3496 5.90723 -46.1396 14l-165.884 -165.977
+c16.1631 -22.7207 25.9766 -50.5117 25.9766 -80.5811c0 -21.8838 -5.44141 -42.2324 -14.4414 -60.7676l141.813 -101.209c16.0703 13.3486 36.0469 22.4414 58.6748 22.4414z" />
+    <glyph glyph-name="glyph121" unicode="&#xe04f;" 
+d="M699.047 111.953l60.1387 60.1396v-186.046h-186.046l60.1396 60.1387l-153.163 153.163l65.7676 65.7676zM75.1396 604.325l-60.1396 -60.1396v186.047h186.047l-60.1396 -60.1396l153.162 -153.162l-65.7666 -65.7676zM633.279 670.093l-60.1396 60.1396h186.046
+v-186.047l-60.1387 60.1396l-153.163 -153.162l-65.7676 65.7676zM294.069 199.349l-153.162 -153.163l60.1396 -60.1387h-186.047v186.046l60.1396 -60.1396l153.163 153.163z" />
+    <glyph glyph-name="at" unicode="@" 
+d="M709.047 172.093h-135.907c-30.3486 0 -56.1396 15.6279 -73.0469 38.1631c-31.418 -24.1631 -70.3018 -39.1631 -113 -39.1631c-102.744 0 -186.046 83.3027 -186.046 186.047s83.3018 186.046 186.046 186.046h186.047v-278.069h75.3018l0.744141 -1.09277
+c10.6279 29.4414 16.9775 61.0459 16.9775 94.1162c0 153.884 -125.187 279.069 -279.07 279.069s-279.069 -125.186 -279.069 -279.069s125.186 -279.07 279.069 -279.07c76.9541 0 146.722 31.2559 197.21 81.8613l65.8604 -65.8613
+c-67.3027 -67.418 -160.326 -109.022 -263.07 -109.022c-205.488 0 -372.093 166.604 -372.093 372.093s166.604 372.093 372.093 372.093s372.093 -166.604 372.093 -372.093c0 -67.9541 -18.2549 -131.279 -50.1387 -186.047zM480.116 404.465v45.6982h-93.0234
+c-51.2324 0 -93.0234 -41.6982 -93.0234 -93.0234s41.791 -93.0234 93.0234 -93.0234s93.0234 41.6982 93.0234 93.0234v47.3252z" />
+    <glyph glyph-name="glyph123" unicode="&#xe05e;" 
+d="M573.047 451.163c93.1162 0 186.139 -49.4189 186.139 -186.047c0 -133.721 -93.0225 -186.047 -186.139 -186.047h-92.9307c-51.3252 0 -93.0234 -41.6045 -93.0234 -93.0225c-102.744 0 -186.14 83.3018 -186.14 186.046v93.0234
+c0 37.0703 8.46582 67.4424 21.6279 93.0234h-21.6279c-92.9297 0 -185.953 52.3252 -185.953 186.046c0 136.629 93.0234 186.047 185.953 186.047h186.14c93.0234 0 185.954 -57.6045 185.954 -186.047v-93.0225zM200.953 451.163h93.1162
+c60.6748 0 114.651 -29.2559 148.628 -74.4424c22.791 16.9766 37.4189 44.0469 37.4189 74.4424v93.0225c0 80.9307 -58.2324 93.0234 -93.0234 93.0234h-186.14c-92.9297 0 -92.9297 -65.1396 -92.9297 -93.0234c0 -22.9766 0 -93.0225 92.9297 -93.0225zM573.047 172.093
+c93.1162 0 93.1162 69.9541 93.1162 93.0234c0 27.8369 0 93.0234 -93.1162 93.0234l-25.6982 0.0458984l0.0927734 0.046875c-32.2559 -55.418 -91.5811 -93.1162 -160.349 -93.1162c0 35.8838 -20.9766 66.1396 -50.79 81.6748
+c-21.0703 -13.2793 -42.2334 -39.1162 -42.2334 -81.6748v-93.0234c0 -30.3486 14.6279 -57.418 37.2559 -74.3955c33.9541 45.1396 88.0234 74.3955 148.791 74.3955h92.9307zM566.604 404.791c-0.62793 -2.13965 -1.37207 -4.23242 -2 -6.30273
+c0.62793 2.07031 1.44238 4.16309 2 6.30273z" />
+    <glyph glyph-name="glyph124" unicode="&#xe03d;" 
+d="M387.093 730.232c205.488 0 372.093 -166.604 372.093 -372.093s-166.604 -372.093 -372.093 -372.093s-372.093 166.604 -372.093 372.093s166.604 372.093 372.093 372.093zM433.604 218.604v279.07h46.5117l-93.0234 93.0225l-93.0234 -93.0225h46.5117v-279.07
+h-46.5117l93.0234 -93.0234l93.0234 93.0234h-46.5117z" />
+    <glyph glyph-name="glyph125" unicode="&#xe03c;" horiz-adv-x="309" 
+d="M154.535 218.604l46.5117 46.5117v-139.535h93.0225l-139.534 -139.534l-139.535 139.534h93.0234v139.535zM154.535 496.232l-46.5117 -45.0693v139.534h-93.0234l139.535 139.535l139.534 -139.535h-93.0225v-139.534z" />
+    <glyph glyph-name="glyph126" unicode="&#x2713;" 
+d="M292.697 58.6748l-277.697 277.697l132.441 132.465l145.256 -145.256l334.047 334.023l132.441 -132.441z" />
+    <glyph glyph-name="glyph127" unicode="&#xe05f;" horiz-adv-x="588" 
+d="M294.069 358.14c-51.418 0 -93.0225 41.6514 -93.0225 93.0234v186.046c0 51.3721 41.6045 93.0234 93.0225 93.0234c51.4189 0 93.0234 -41.6514 93.0234 -93.0234v-186.046c0 -51.3721 -41.6045 -93.0234 -93.0234 -93.0234zM387.093 79.0693
+c51.4189 0 93.0234 -41.6045 93.0234 -93.0225h-372.093c0 51.418 41.6045 93.0225 93.0234 93.0225h46.5117v97.7451c-131.722 22.2549 -232.559 136.395 -232.559 274.349v45.0693c0 25.6973 20.8135 46.5117 46.5117 46.5117
+c25.6973 0 46.5117 -20.8145 46.5117 -46.5117v-45.0693c0 -102.604 83.3955 -186.047 186.046 -186.047c102.651 0 186.047 83.4424 186.047 186.047v45.0693c0 25.6973 20.8145 46.5117 46.5117 46.5117s46.5117 -20.8145 46.5117 -46.5117v-45.0693
+c0 -137.954 -100.837 -252.094 -232.559 -274.349v-97.7451h46.5117z" />
+    <glyph glyph-name="glyph128" unicode="&#xe06c;" 
+d="M759.186 -13.9531h-744.186v651.162h93.0234v-558.14h558.14v558.14h93.0225v-651.162zM154.535 358.14v93.0234h93.0234v-93.0234h-93.0234zM340.581 357.953v93.0234h93.0234v-93.0234h-93.0234zM526.628 358.14v93.0234h93.0234v-93.0234h-93.0234zM154.535 172.093
+v93.0234h93.0234v-93.0234h-93.0234zM340.581 172.093v93.0234h93.0234v-93.0234h-93.0234zM526.628 172.093v93.0234h93.0234zM201.047 590.697v93.0234c0 25.6982 20.8135 46.5117 46.5117 46.5117c25.6973 0 46.5107 -20.8135 46.5107 -46.5117v-93.0234
+c0 -25.6973 -20.8135 -46.5117 -46.5107 -46.5117c-25.6982 0 -46.5117 20.8145 -46.5117 46.5117zM480.116 590.697v93.0234c0 25.6982 20.8145 46.5117 46.5117 46.5117s46.5117 -20.8135 46.5117 -46.5117v-93.0234c0 -25.6973 -20.8145 -46.5117 -46.5117 -46.5117
+s-46.5117 20.8145 -46.5117 46.5117z" />
+    <glyph glyph-name="glyph129" unicode="&#xe069;" horiz-adv-x="681" 
+d="M108.023 730.232h558.14v-744.186h-558.14c-51.3721 0 -93.0234 41.6045 -93.0234 93.0225v558.14c0 51.4189 41.6514 93.0234 93.0234 93.0234zM573.14 79.0693v558.14h-92.6748v-187.674l-93.7441 93.7441l-93.0234 -93.0234v186.953h-92.6504v-558.14h372.093z" />
+    <glyph glyph-name="glyph130" unicode="&#xe03f;" 
+d="M387.093 218.604l46.5117 46.5117v-139.535h93.0234l-139.535 -139.534l-139.534 139.534h93.0225v139.535zM387.093 496.232l-46.5117 -45.0693v139.534h-93.0225l139.534 139.535l139.535 -139.535h-93.0234v-139.534zM247.559 358.14l46.5107 -46.5117h-139.534
+v-93.0234l-139.535 139.535l139.535 139.535v-93.0234h139.534zM525.186 358.14l-45.0693 46.5117h139.535v93.0234l139.534 -139.535l-139.534 -139.535v93.0234h-139.535z" />
+    <glyph glyph-name="glyph131" unicode="&#xe040;" 
+d="M387.093 730.232c205.488 0 372.093 -166.604 372.093 -372.093s-166.604 -372.093 -372.093 -372.093s-372.093 166.604 -372.093 372.093s166.604 372.093 372.093 372.093zM526.628 265.116l93.0234 93.0234l-93.0234 93.0234v-46.5117h-93.0234v93.0234h46.5117
+l-93.0234 93.0225l-93.0234 -93.0225h46.5117v-93.0234h-93.0225v46.5117l-93.0234 -93.0234l93.0234 -93.0234v46.5117h93.0225v-93.0234h-46.5117l93.0234 -93.0234l93.0234 93.0234h-46.5117v93.0234h93.0234v-46.5117z" />
+    <glyph glyph-name="glyph132" unicode="&#xe022;" horiz-adv-x="402" 
+d="M201.047 265.116c32.79 0 63.7666 6.7207 93.0225 17.1162v-296.186l-93.0225 93.0225l-93.0234 -93.0225v296.186c29.2559 -10.3955 60.2324 -17.1162 93.0234 -17.1162zM201.047 730.232c102.744 0 186.046 -83.3018 186.046 -186.047
+c0 -102.744 -83.3018 -186.046 -186.046 -186.046s-186.047 83.3018 -186.047 186.046c0 102.745 83.3027 186.047 186.047 186.047zM201.047 451.163c51.418 0 93.0225 41.6514 93.0225 93.0225c0 51.373 -41.6045 93.0234 -93.0225 93.0234
+c-51.4189 0 -93.0234 -41.6504 -93.0234 -93.0234c0 -51.3711 41.6045 -93.0225 93.0234 -93.0225z" />
+    <glyph glyph-name="glyph133" unicode="&#xe078;" 
+d="M750 599c5.46484 -17.4414 9.18555 -35.6045 9.18555 -54.8145c0 -102.697 -83.3018 -186.046 -186.046 -186.046c-28.6045 0 -55.5117 7.0459 -79.6748 18.5811l-372.628 -372.512c-11.0928 -11.2559 -26.6279 -18.1621 -43.79 -18.1621
+c-34.3496 0 -62.0469 27.79 -62.0469 61.9531c0 17.2559 7 32.6973 18.1631 43.9766l372.558 372.442c-11.6279 24.3018 -18.6279 51.1162 -18.6279 79.7666c0 102.698 83.3027 186.047 186.047 186.047c18.6279 0 36.3252 -3.58105 53.3252 -8.7207l-115.372 -115.326
+v-123.999h122.093z" />
+    <glyph glyph-name="glyph134" unicode="&#xe047;" horiz-adv-x="588" 
+d="M15 730.232l558.14 -372.093l-558.14 -372.093v744.186z" />
+    <glyph glyph-name="glyph135" unicode="&#x2605;" 
+d="M529.813 273.837l90.0234 -287.79l-232.372 178.604l-232.93 -178.604l90.1162 288.512l-229.651 176.604h279.069l93.0234 279.069l93.0234 -279.069h279.069z" />
+    <glyph glyph-name="glyph136" unicode="&#xe028;" 
+d="M334.553 279.116l-232.744 -232.558c-115.744 133.813 -115.744 331.302 0 465.116zM478.133 633.535c159.35 -23.3955 281.07 -156.396 281.07 -320.535c0 -180.697 -146.441 -326.953 -329.232 -326.953c-70.7207 0 -136.488 25.6279 -190.629 66.0459l238.791 240.838
+v340.604zM387.064 357.768l-266.908 266.953c73.9531 60.4424 165.535 99.2324 266.908 105.512v-372.465z" />
+    <glyph glyph-name="glyph137" unicode="&#x26c6;" 
+d="M573.151 590.697c102.558 0 186.046 -83.4414 186.046 -186.046s-83.4883 -186.047 -186.046 -186.047h-372.093c-102.559 0 -186.047 83.4424 -186.047 186.047s83.4883 186.046 186.047 186.046c8.25586 0 16.4414 -1.23242 24.6279 -2.3252
+c31.9766 56.6045 91.9297 95.3486 161.418 95.3486c68.0469 0 128 -38.2793 160.419 -95.6045c8.46484 1.16309 16.8135 2.58105 25.6279 2.58105zM573.151 311.628c51.3252 0 93.0234 41.7441 93.0234 93.0234s-41.6982 93.0234 -93.0234 93.0234
+c-51.3262 0 -93.0234 -41.7441 -93.0234 -93.0234h-93.0234c0 60.5811 29.5352 113.953 74.3955 147.93c-17.0693 23.21 -44.3252 38.1162 -74.3955 38.1162c-51.3252 0 -93.0234 -41.7441 -93.0234 -93.0225c0 -11.21 1.90723 -22.1631 5.81445 -32.4893l-87.1162 -32.6045
+c-7.62793 20.3496 -11.4424 41.8838 -11.6279 63.8145c-47.9766 -1.39551 -93.1162 -40.7441 -93.1162 -91.7441c0 -51.2793 41.6973 -93.0234 93.0234 -93.0234h372.093zM91.3145 45.6279c-17.4424 -17.4424 -45.791 -17.4424 -63.2334 0
+c-17.4414 17.4414 -17.4414 45.791 0 63.2324c17.4424 17.4424 126.466 63.2324 126.466 63.2324s-45.791 -109.023 -63.2324 -126.465zM277.453 45.6279c-17.4414 -17.4424 -45.6973 -17.4424 -63.2324 0c-17.4414 17.4414 -17.4414 45.791 0 63.2324
+c17.5352 17.4424 126.466 63.2324 126.466 63.2324s-45.6982 -109.023 -63.2334 -126.465zM463.407 45.6279c-17.4424 -17.4424 -45.791 -17.4424 -63.2324 0c-17.4424 17.4414 -17.4424 45.791 0 63.2324c17.4414 17.4424 126.465 63.2324 126.465 63.2324
+s-45.791 -109.023 -63.2324 -126.465z" />
+    <glyph glyph-name="glyph138" unicode="&#xe065;" 
+d="M387.093 637.163h372.093v-186.094v-93.0225v-278.978c0 -51.418 -41.6973 -93.0225 -93.0225 -93.0225h-558.14c-51.4189 0 -93.0234 41.6045 -93.0234 93.0225v293.512v78.4883v279.163h372.093v-93.0693zM294.069 637.209h-186.046v-186.046h558.14v92.9766h-279.07
+h-93.0234v93.0234v0.0458984zM666.163 79.0693v278.978v0.0927734h-558.14v-279.07h558.14z" />
+    <glyph glyph-name="glyph139" unicode="&#x2600;" 
+d="M387.093 451.163c-51.2324 0 -93.0234 -41.7441 -93.0234 -93.0234s41.791 -93.0234 93.0234 -93.0234s93.0234 41.7441 93.0234 93.0234s-41.791 93.0234 -93.0234 93.0234zM387.093 544.186c102.744 0 186.047 -83.3018 186.047 -186.046
+s-83.3027 -186.047 -186.047 -186.047s-186.046 83.3027 -186.046 186.047s83.3018 186.046 186.046 186.046zM340.581 683.721c0 31.0078 15.5039 46.5117 46.5117 46.5117s46.5117 -15.5039 46.5117 -46.5117s-15.5039 -46.5117 -46.5117 -46.5117
+s-46.5117 15.5039 -46.5117 46.5117zM108.023 590.697c0 31.0078 15.5039 46.5117 46.5117 46.5117s46.5117 -15.5039 46.5117 -46.5117s-15.5039 -46.5117 -46.5117 -46.5117s-46.5117 15.5039 -46.5117 46.5117zM61.5117 404.697
+c25.6973 0 46.5117 -20.8135 46.5117 -46.5107c0 -25.7451 -20.8145 -46.5117 -46.5117 -46.5117c-25.6982 0 -46.5117 20.7666 -46.5117 46.5117c0 25.6973 20.8135 46.5107 46.5117 46.5107zM108.023 125.581c0 31.0078 15.5039 46.5117 46.5117 46.5117
+s46.5117 -15.5039 46.5117 -46.5117s-15.5039 -46.5117 -46.5117 -46.5117s-46.5117 15.5039 -46.5117 46.5117zM340.581 32.5586c0 25.79 20.8145 46.5107 46.5117 46.5107c25.791 0 46.5117 -20.7207 46.5117 -46.5107c0 -25.6279 -20.7207 -46.5117 -46.5117 -46.5117
+c-25.6973 0 -46.5117 20.8838 -46.5117 46.5117zM586.953 92.6973c-18.1621 18.1631 -18.1621 47.6055 0 65.7676c18.2559 18.1631 47.6055 18.1631 65.7676 0c18.1631 -18.1621 18.2559 -47.6045 0 -65.7676c-18.1621 -18.1621 -47.6045 -18.1621 -65.7676 0z
+M712.581 311.675c-25.6045 0 -46.418 20.79 -46.418 46.4648c0 25.791 20.7207 46.5117 46.5117 46.5117c25.6279 0 46.5107 -20.7676 46.5107 -46.4648c0 -25.7451 -20.8828 -46.5586 -46.6045 -46.5117zM652.721 557.768c-18.1621 -18.1162 -47.6045 -18.1162 -65.7676 0
+c-18.1621 18.2559 -18.1621 47.6045 0 65.8135c18.1631 18.1162 47.6055 18.1631 65.7676 -0.0458984c18.1631 -18.1631 18.1631 -47.6514 0 -65.7676z" />
+    <glyph glyph-name="glyph140" unicode="&#xe062;" horiz-adv-x="588" 
+d="M294.069 358.14c154.07 0 279.07 -125 279.07 -279.07c0 -51.418 -41.6045 -93.0225 -93.0234 -93.0225h-372.093c-51.4189 0 -93.0234 41.6045 -93.0234 93.0225c0 154.07 125 279.07 279.069 279.07zM154.535 590.697c0 93.0234 46.5117 139.535 139.534 139.535
+c93.0234 0 139.535 -46.5117 139.535 -139.535c0 -93.0225 -46.5117 -139.534 -139.535 -139.534c-93.0225 0 -139.534 46.5117 -139.534 139.534z" />
+    <glyph glyph-name="glyph141" unicode="&#xe05b;" 
+d="M759.186 451.163v-186.047h-93.0225v-46.5117c0 -25.6279 -20.8838 -46.5117 -46.5117 -46.5117h-558.14c-25.6279 0 -46.5117 20.8838 -46.5117 46.5117v279.07c0 25.5811 20.8838 46.5107 46.5117 46.5107h558.14c25.6279 0 46.5117 -20.9297 46.5117 -46.5107
+v-46.5117h93.0225zM573.14 265.116v186.047h-186.047v-186.047h186.047z" />
+    <glyph glyph-name="glyph142" unicode="&#xe00c;" 
+d="M719.86 521.559c24.4189 -49.5117 39.3252 -104.512 39.3252 -163.419c0 -23.4424 -2.7207 -46.1396 -6.90625 -68.5117h-264.349zM504.651 375.814v333.604c78.1162 -26.1631 145.349 -75.9775 190.768 -142.931zM404.488 240.581h333.884
+c-26.1631 -78.209 -76.0234 -145.534 -143.069 -190.86zM455.535 457.116l-233.094 233.093c49.8145 24.8145 105.233 40.0234 164.651 40.0234c23.4424 0 46.1865 -2.76758 68.4424 -6.90723v-266.209zM318.651 256.931l231.697 -231.628
+c-49.5117 -24.3496 -104.396 -39.2559 -163.256 -39.2559c-23.4414 0 -46.1855 2.7207 -68.4414 6.88379v264zM269.488 340.163v-333.326c-78.0234 26.1631 -145.209 75.9541 -190.535 142.814zM21.9072 426.581v0.0234375h264.581l-232.069 -232.069
+c-24.4189 49.5117 -39.4189 104.558 -39.4189 163.604c0 23.4414 2.76758 46.1855 6.90723 68.4414zM177.697 665.559l189.814 -189.814h-331.721c26.0459 77.6748 75.5117 144.488 141.906 189.814z" />
+    <glyph glyph-name="glyph143" unicode="&#xe04b;" 
+d="M15 265.116l372.093 465.116l372.093 -465.116h-744.186zM15 -13.9531v186.046h743.186v-186.046h-743.186z" />
+    <glyph glyph-name="glyph144" unicode="&#xe016;" 
+d="M759.186 358.187c0 -205.535 -166.604 -372.14 -372.093 -372.14s-372.093 166.604 -372.093 372.14c0 205.441 166.604 372.046 372.093 372.046s372.093 -166.604 372.093 -372.046zM201.047 357.465l186.046 -185.372l185.326 185.372h-138.814v186.721h-93.0234
+v-186.721h-139.534z" />
+    <glyph glyph-name="paragraph" unicode="&#xb6;" horiz-adv-x="588" 
+d="M573.14 730.232v-93.0234h-93.0234v-651.162h-93.0234v651.162h-93.0234v-651.162h-93.0225v372.093c-102.744 0 -186.047 83.3018 -186.047 186.046c0 102.745 83.3027 186.047 186.047 186.047h93.0225h93.0234h93.0234h93.0234z" />
+    <glyph glyph-name="glyph146" unicode="&#xe01a;" 
+d="M15 358.14c0 205.488 166.604 372.093 372.093 372.093s372.093 -166.604 372.093 -372.093s-166.604 -372.093 -372.093 -372.093s-372.093 166.604 -372.093 372.093zM480.116 497.675l-93.0234 93.0225l-93.0234 -93.0225h46.5117v-372.094h93.0234v372.094h46.5117z
+" />
+    <glyph glyph-name="glyph147" unicode="&#xe015;" 
+d="M387.093 730.232c205.488 0 372.093 -166.604 372.093 -372.093s-166.604 -372.093 -372.093 -372.093s-372.093 166.604 -372.093 372.093s166.604 372.093 372.093 372.093zM387.814 172.093l185.325 186.047l-185.325 185.325v-138.813h-186.768v-93.0234h186.768
+v-139.535z" />
+    <glyph glyph-name="glyph148" unicode="&#xe019;" 
+d="M387.093 730.232c205.488 0 372.093 -166.604 372.093 -372.093s-166.604 -372.093 -372.093 -372.093s-372.093 166.604 -372.093 372.093s166.604 372.093 372.093 372.093zM526.628 265.116l93.0234 93.0234l-93.0234 93.0234v-46.5117h-372.093v-93.0234h372.093
+v-46.5117z" />
+    <glyph glyph-name="glyph149" unicode="&#xe02d;" 
+d="M759.186 -13.9531h-106.278c0 351.744 -286.163 637.86 -637.907 637.86v106.325c410.256 0 744.186 -333.837 744.186 -744.186zM546.512 -13.9531h-106.279c0 234.465 -190.86 425.232 -425.232 425.232v106.325c293.069 0 531.512 -238.488 531.512 -531.558z
+M333.953 -13.9531h-106.372c0 117.278 -95.3018 212.581 -212.581 212.581v106.372c175.86 0 318.953 -143.093 318.953 -318.953zM121.279 -13.9531h-106.279v106.278c58.7676 0 106.279 -47.6045 106.279 -106.278z" />
+    <glyph glyph-name="glyph150" unicode="&#xe037;" 
+d="M124.093 94.8838c-70.3955 70.3018 -109.093 163.697 -109.093 263.256c0 205.116 166.884 372.093 372.093 372.093l46.5117 -46.5117l-46.5117 -46.5117c-153.884 0 -279.069 -125.186 -279.069 -279.069c0 -74.6748 29.0693 -144.814 81.8604 -197.488
+l-57.1396 -7.44238zM387.093 -13.9531l-46.5117 46.3252l46.5117 46.6973c153.884 0 279.07 125.187 279.07 279.07c0 74.4883 -29.0703 144.628 -81.9307 197.396l57.2324 7.53418l8.53516 58.2334c70.3955 -70.3027 109.186 -163.791 109.186 -263.163
+c0 -205.116 -166.977 -372.093 -372.093 -372.093z" />
+  </font>
+</defs></svg>
diff --git a/src/main/resources/bootstrap/font/iconic_stroke.ttf b/src/main/resources/bootstrap/font/iconic_stroke.ttf
new file mode 100644
index 0000000..f891c8d
--- /dev/null
+++ b/src/main/resources/bootstrap/font/iconic_stroke.ttf
Binary files differ
diff --git a/src/main/resources/bootstrap/font/iconic_stroke.woff b/src/main/resources/bootstrap/font/iconic_stroke.woff
new file mode 100644
index 0000000..85f5be5
--- /dev/null
+++ b/src/main/resources/bootstrap/font/iconic_stroke.woff
Binary files differ
diff --git a/resources/bootstrap/img/glyphicons-halflings-white.png b/src/main/resources/bootstrap/img/glyphicons-halflings-white.png
similarity index 100%
rename from resources/bootstrap/img/glyphicons-halflings-white.png
rename to src/main/resources/bootstrap/img/glyphicons-halflings-white.png
Binary files differ
diff --git a/resources/bootstrap/img/glyphicons-halflings.png b/src/main/resources/bootstrap/img/glyphicons-halflings.png
similarity index 100%
rename from resources/bootstrap/img/glyphicons-halflings.png
rename to src/main/resources/bootstrap/img/glyphicons-halflings.png
Binary files differ
diff --git a/resources/bootstrap/js/bootstrap.js b/src/main/resources/bootstrap/js/bootstrap.js
similarity index 100%
rename from resources/bootstrap/js/bootstrap.js
rename to src/main/resources/bootstrap/js/bootstrap.js
diff --git a/resources/bootstrap/js/jquery.js b/src/main/resources/bootstrap/js/jquery.js
similarity index 100%
rename from resources/bootstrap/js/jquery.js
rename to src/main/resources/bootstrap/js/jquery.js
diff --git a/resources/bug_16x16.png b/src/main/resources/bug_16x16.png
similarity index 100%
rename from resources/bug_16x16.png
rename to src/main/resources/bug_16x16.png
Binary files differ
diff --git a/resources/bullet_black.png b/src/main/resources/bullet_black.png
similarity index 100%
rename from resources/bullet_black.png
rename to src/main/resources/bullet_black.png
Binary files differ
diff --git a/resources/bullet_blue.png b/src/main/resources/bullet_blue.png
similarity index 100%
rename from resources/bullet_blue.png
rename to src/main/resources/bullet_blue.png
Binary files differ
diff --git a/resources/bullet_delete.png b/src/main/resources/bullet_delete.png
similarity index 100%
rename from resources/bullet_delete.png
rename to src/main/resources/bullet_delete.png
Binary files differ
diff --git a/resources/bullet_error.png b/src/main/resources/bullet_error.png
similarity index 100%
rename from resources/bullet_error.png
rename to src/main/resources/bullet_error.png
Binary files differ
diff --git a/resources/bullet_feed.png b/src/main/resources/bullet_feed.png
similarity index 100%
rename from resources/bullet_feed.png
rename to src/main/resources/bullet_feed.png
Binary files differ
diff --git a/resources/bullet_green.png b/src/main/resources/bullet_green.png
similarity index 100%
rename from resources/bullet_green.png
rename to src/main/resources/bullet_green.png
Binary files differ
diff --git a/resources/bullet_key.png b/src/main/resources/bullet_key.png
similarity index 100%
rename from resources/bullet_key.png
rename to src/main/resources/bullet_key.png
Binary files differ
diff --git a/resources/bullet_orange.png b/src/main/resources/bullet_orange.png
similarity index 100%
rename from resources/bullet_orange.png
rename to src/main/resources/bullet_orange.png
Binary files differ
diff --git a/resources/bullet_red.png b/src/main/resources/bullet_red.png
similarity index 100%
rename from resources/bullet_red.png
rename to src/main/resources/bullet_red.png
Binary files differ
diff --git a/resources/bullet_white.png b/src/main/resources/bullet_white.png
similarity index 100%
rename from resources/bullet_white.png
rename to src/main/resources/bullet_white.png
Binary files differ
diff --git a/resources/bullet_yellow.png b/src/main/resources/bullet_yellow.png
similarity index 100%
rename from resources/bullet_yellow.png
rename to src/main/resources/bullet_yellow.png
Binary files differ
diff --git a/resources/clipboard_13x13.png b/src/main/resources/clipboard_13x13.png
similarity index 100%
rename from resources/clipboard_13x13.png
rename to src/main/resources/clipboard_13x13.png
Binary files differ
diff --git a/resources/clipboard_16x16.png b/src/main/resources/clipboard_16x16.png
similarity index 100%
rename from resources/clipboard_16x16.png
rename to src/main/resources/clipboard_16x16.png
Binary files differ
diff --git a/resources/clippy.png b/src/main/resources/clippy.png
similarity index 100%
rename from resources/clippy.png
rename to src/main/resources/clippy.png
Binary files differ
diff --git a/resources/clippy.swf b/src/main/resources/clippy.swf
similarity index 100%
rename from resources/clippy.swf
rename to src/main/resources/clippy.swf
Binary files differ
diff --git a/resources/cold_16x16.png b/src/main/resources/cold_16x16.png
similarity index 100%
rename from resources/cold_16x16.png
rename to src/main/resources/cold_16x16.png
Binary files differ
diff --git a/resources/commit_branch_16x16.png b/src/main/resources/commit_branch_16x16.png
similarity index 100%
rename from resources/commit_branch_16x16.png
rename to src/main/resources/commit_branch_16x16.png
Binary files differ
diff --git a/resources/commit_changes_16x16.png b/src/main/resources/commit_changes_16x16.png
similarity index 100%
rename from resources/commit_changes_16x16.png
rename to src/main/resources/commit_changes_16x16.png
Binary files differ
diff --git a/resources/commit_divide_16x16.png b/src/main/resources/commit_divide_16x16.png
similarity index 100%
rename from resources/commit_divide_16x16.png
rename to src/main/resources/commit_divide_16x16.png
Binary files differ
diff --git a/resources/commit_join_16x16.png b/src/main/resources/commit_join_16x16.png
similarity index 100%
rename from resources/commit_join_16x16.png
rename to src/main/resources/commit_join_16x16.png
Binary files differ
diff --git a/resources/commit_merge_16x16.png b/src/main/resources/commit_merge_16x16.png
similarity index 100%
rename from resources/commit_merge_16x16.png
rename to src/main/resources/commit_merge_16x16.png
Binary files differ
diff --git a/resources/commit_up_16x16.png b/src/main/resources/commit_up_16x16.png
similarity index 100%
rename from resources/commit_up_16x16.png
rename to src/main/resources/commit_up_16x16.png
Binary files differ
diff --git a/resources/federated_16x16.png b/src/main/resources/federated_16x16.png
similarity index 100%
rename from resources/federated_16x16.png
rename to src/main/resources/federated_16x16.png
Binary files differ
diff --git a/resources/feed_16x16.png b/src/main/resources/feed_16x16.png
similarity index 100%
rename from resources/feed_16x16.png
rename to src/main/resources/feed_16x16.png
Binary files differ
diff --git a/resources/file_16x16.png b/src/main/resources/file_16x16.png
similarity index 100%
rename from resources/file_16x16.png
rename to src/main/resources/file_16x16.png
Binary files differ
diff --git a/resources/file_acrobat_16x16.png b/src/main/resources/file_acrobat_16x16.png
similarity index 100%
rename from resources/file_acrobat_16x16.png
rename to src/main/resources/file_acrobat_16x16.png
Binary files differ
diff --git a/resources/file_c_16x16.png b/src/main/resources/file_c_16x16.png
similarity index 100%
rename from resources/file_c_16x16.png
rename to src/main/resources/file_c_16x16.png
Binary files differ
diff --git a/resources/file_code_16x16.png b/src/main/resources/file_code_16x16.png
similarity index 100%
rename from resources/file_code_16x16.png
rename to src/main/resources/file_code_16x16.png
Binary files differ
diff --git a/resources/file_cpp_16x16.png b/src/main/resources/file_cpp_16x16.png
similarity index 100%
rename from resources/file_cpp_16x16.png
rename to src/main/resources/file_cpp_16x16.png
Binary files differ
diff --git a/resources/file_cs_16x16.png b/src/main/resources/file_cs_16x16.png
similarity index 100%
rename from resources/file_cs_16x16.png
rename to src/main/resources/file_cs_16x16.png
Binary files differ
diff --git a/resources/file_doc_16x16.png b/src/main/resources/file_doc_16x16.png
similarity index 100%
rename from resources/file_doc_16x16.png
rename to src/main/resources/file_doc_16x16.png
Binary files differ
diff --git a/resources/file_excel_16x16.png b/src/main/resources/file_excel_16x16.png
similarity index 100%
rename from resources/file_excel_16x16.png
rename to src/main/resources/file_excel_16x16.png
Binary files differ
diff --git a/resources/file_h_16x16.png b/src/main/resources/file_h_16x16.png
similarity index 100%
rename from resources/file_h_16x16.png
rename to src/main/resources/file_h_16x16.png
Binary files differ
diff --git a/resources/file_java_16x16.png b/src/main/resources/file_java_16x16.png
similarity index 100%
rename from resources/file_java_16x16.png
rename to src/main/resources/file_java_16x16.png
Binary files differ
diff --git a/resources/file_php_16x16.png b/src/main/resources/file_php_16x16.png
similarity index 100%
rename from resources/file_php_16x16.png
rename to src/main/resources/file_php_16x16.png
Binary files differ
diff --git a/resources/file_ppt_16x16.png b/src/main/resources/file_ppt_16x16.png
similarity index 100%
rename from resources/file_ppt_16x16.png
rename to src/main/resources/file_ppt_16x16.png
Binary files differ
diff --git a/resources/file_ruby_16x16.png b/src/main/resources/file_ruby_16x16.png
similarity index 100%
rename from resources/file_ruby_16x16.png
rename to src/main/resources/file_ruby_16x16.png
Binary files differ
diff --git a/resources/file_settings_16x16.png b/src/main/resources/file_settings_16x16.png
similarity index 100%
rename from resources/file_settings_16x16.png
rename to src/main/resources/file_settings_16x16.png
Binary files differ
diff --git a/resources/file_vs_16x16.png b/src/main/resources/file_vs_16x16.png
similarity index 100%
rename from resources/file_vs_16x16.png
rename to src/main/resources/file_vs_16x16.png
Binary files differ
diff --git a/resources/file_world_16x16.png b/src/main/resources/file_world_16x16.png
similarity index 100%
rename from resources/file_world_16x16.png
rename to src/main/resources/file_world_16x16.png
Binary files differ
diff --git a/resources/file_zip_16x16.png b/src/main/resources/file_zip_16x16.png
similarity index 100%
rename from resources/file_zip_16x16.png
rename to src/main/resources/file_zip_16x16.png
Binary files differ
diff --git a/resources/folder_16x16.png b/src/main/resources/folder_16x16.png
similarity index 100%
rename from resources/folder_16x16.png
rename to src/main/resources/folder_16x16.png
Binary files differ
diff --git a/resources/folder_star_16x16.png b/src/main/resources/folder_star_16x16.png
similarity index 100%
rename from resources/folder_star_16x16.png
rename to src/main/resources/folder_star_16x16.png
Binary files differ
diff --git a/resources/folder_star_32x32.png b/src/main/resources/folder_star_32x32.png
similarity index 100%
rename from resources/folder_star_32x32.png
rename to src/main/resources/folder_star_32x32.png
Binary files differ
diff --git a/src/main/resources/fork-black_16x16.png b/src/main/resources/fork-black_16x16.png
new file mode 100644
index 0000000..aeb8211
--- /dev/null
+++ b/src/main/resources/fork-black_16x16.png
Binary files differ
diff --git a/resources/fork_16x16.png b/src/main/resources/fork_16x16.png
similarity index 100%
rename from resources/fork_16x16.png
rename to src/main/resources/fork_16x16.png
Binary files differ
diff --git a/resources/git-black-16x16.png b/src/main/resources/git-black-16x16.png
similarity index 100%
rename from resources/git-black-16x16.png
rename to src/main/resources/git-black-16x16.png
Binary files differ
diff --git a/resources/git-black.png b/src/main/resources/git-black.png
similarity index 100%
rename from resources/git-black.png
rename to src/main/resources/git-black.png
Binary files differ
diff --git a/resources/git-black_210x210.png b/src/main/resources/git-black_210x210.png
similarity index 100%
rename from resources/git-black_210x210.png
rename to src/main/resources/git-black_210x210.png
Binary files differ
diff --git a/src/main/resources/git-black_32x32.png b/src/main/resources/git-black_32x32.png
new file mode 100644
index 0000000..037f61a
--- /dev/null
+++ b/src/main/resources/git-black_32x32.png
Binary files differ
diff --git a/resources/git-orange-16x16.png b/src/main/resources/git-orange-16x16.png
similarity index 100%
rename from resources/git-orange-16x16.png
rename to src/main/resources/git-orange-16x16.png
Binary files differ
diff --git a/src/main/resources/gitblit.css b/src/main/resources/gitblit.css
new file mode 100644
index 0000000..4db1548
--- /dev/null
+++ b/src/main/resources/gitblit.css
@@ -0,0 +1,1480 @@
+body {
+	 /* 47px is the header height */
+	padding-top: 47px;
+}
+
+footer {
+	margin-top: 25px;
+	padding: 15px 0 16px;
+	border-top: 1px solid #E5E5E5;
+}
+
+body, input, select {
+	color: #202020;
+}
+
+ul, ol {
+	margin-bottom: 10px !important;
+}
+
+a {
+	color: #2F58A0;
+}
+
+a:hover {
+	color: #002060;
+}
+
+a:focus {
+	outline: none;
+}
+
+a.btn i {
+	/* override for a links that look like bootstrap buttons */
+	vertical-align: text-bottom;
+}
+
+[class^="icon-"], [class*=" icon-"] i {
+	/* override for a links that look like bootstrap buttons */
+	vertical-align: text-bottom;
+}
+
+hr {
+	margin-top: 10px;
+	margin-bottom: 10px;
+}
+
+.settings th {
+	vertical-align: top;
+}
+
+.pageTitle {
+	padding-bottom: 5px;
+	margin: 0;
+	border-bottom: 1px solid #eee;
+}
+
+.pageTitle h1, .pageTitle h2 {
+	color: #0069D6;
+}
+
+.navbar .brand {
+	padding: 0px 10px 0px 20px;
+}
+.navbar .btn-navbar {
+	margin-top: 10px;
+}
+
+.navbar .pull-right {
+	margin: 0;
+}
+
+.navbar ul.nav {
+	margin: 0 !important;
+	padding: 4px 0px 0px 0px;
+}
+
+.navbar ul.nav li a {
+  	color: white; 
+	text-shadow: none;
+	outline: 0;
+}
+
+.navbar ul.nav li a:hover {
+	color: #abd4ff !important;
+	text-decoration: underline;
+}
+
+.navbar .nav .active > a:hover {
+	text-decoration: underline;
+}
+
+.navbar-inner {
+	background-color: #002060;
+	background-image: none;
+	box-shadow: none;
+	border-bottom: 1px solid #002060 !important;
+}
+
+.navbar ul li:focus, .navbar .active {
+	outline: 0;	
+	padding-bottom: 1px;
+	border-bottom: 3px solid #ff9900;
+	margin-bottom: -1px;
+}
+
+.navbar .active a {
+	background-color: transparent !important;
+	outline: 0;
+}
+
+.navbar div > ul .menu-dropdown .selected, .nav .menu-dropdown .selected, .navbar div > ul .dropdown-menu .selected, .nav .dropdown-menu .selected {	
+	background-image: url("bullet_blue.png");
+	background-repeat: no-repeat;
+	background-position: left;	
+}
+
+.navbar div>ul .dropdown-menu li a {
+	color: #555;
+}
+
+navbar div>ul .menu-dropdown li a:hover,.nav .menu-dropdown li a:hover,.navbar div>ul .dropdown-menu li a:hover,.nav .dropdown-menu li a:hover{
+	background-color: #000070;
+	color: #ffffff !important;
+}
+
+.nav-pills > .active > a, .nav-pills > .active > a:hover {
+    color: #fff;
+    background-color: #002060;
+}
+
+div.reflog {
+    border-bottom: 1px solid #ddd;
+	margin-bottom: 5px;
+	padding-bottom: 5px;
+}
+
+div.reflog .icon {
+    font-size: 42px;
+    line-height: 42px;
+}
+
+div.reflog .when {
+	color: #aaa;
+}
+
+div.reflog i {
+    font-size: 42px;
+    color: #bbb;
+    vertical-align: middle;
+}
+
+div.reflog td.header {
+	padding-left: 7px;
+	vertical-align:middle;
+}
+
+div.reflog td.commits {
+	padding-left: 7px;
+}
+
+div.reflog tr.commit td {
+	vertical-align:top;
+	padding-left: 5px;
+}
+
+div.reflog tr.commit img {
+	max-width: none;
+}
+
+div.dashboardTitle {
+	font-size: 1.75em;
+	padding: 10px 0px 5px 0px;
+	margin: 10px 0px;
+	border-bottom: 1px solid #ccc;
+}
+
+div.dashboardTitle small {
+	color: #888;
+	font-size: 0.7em;
+}
+
+.repositorynavbar {
+	background-color: #fbfbfb;
+	border-bottom: 1px solid #ccc;
+	margin-bottom: 10px;
+}
+
+.repositorynavbar .title {
+	line-height: 32px;
+	padding: 5px 0px;
+}
+
+.repositorynavbar .repository {
+    font-weight: bold;
+}
+
+.title .repository a, .repositorynavbar .project a, .repositorynavbar .repository a {
+    font-family: Helvetica,arial,freesans,clean,sans-serif;
+    font-size: 22px;
+    color: #002060;
+}
+
+.repositorynavbar .repositorynavbar-inner {
+	padding-top: 2px;
+}
+
+.repositorynavbar ul {
+	list-style: none outside;
+	display: block;
+	position: relative;
+	border-top: 1px solid #ccc;
+}
+
+.repositorynavbar ul li {
+	display: block;
+	float: left;
+	padding: 10px;
+}
+
+.repositorynavbar ul li:focus, .repositorynavbar .active {
+	color: black;
+	background-repeat:no-repeat;
+	background-image: url(arrow_project.png);
+	background-position: center bottom;
+	font-weight: bold;
+	outline: 0;	
+}
+
+.repositorynavbar ul a {
+	color: #002060;
+}
+
+.repositorynavbar ul li:hover {
+	background-color: #eee;
+}
+
+.repositorynavbar ul li a:hover {
+	background-color: inherit;
+	text-decoration: underline;
+}
+
+@media (max-width: 767px) {
+	.repositorynavbar {
+    margin-right: -20px;
+    margin-left: -20px;
+    padding: 0px 5px;
+  }
+}
+
+.btn-appmenu {
+	border-radius: 4px !important;
+    background-color: #002060;
+    background-image:-khtml-gradient(linear, left top, left bottom, from(#4060A0), to(#002060));
+	background-image:-moz-linear-gradient(center top, #4060A0, #002060);
+	background-image:-ms-linear-gradient(top, #4060A0, #002060);
+	background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #4060A0), color-stop(100%, #002060));
+	background-image:-webkit-linear-gradient(top, #4060A0, #002060);
+	background-image:-o-linear-gradient(top, #4060A0, #002060);
+	background-image:linear-gradient(top, #4060A0, #002060);
+    
+    background-repeat: repeat-x;
+    border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+    color: #ffffff;
+    text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+}
+
+.btn-appmenu:hover, .btn-appmenu:active, .btn-appmenu.active, .btn-appmenu.disabled, .btn-appmenu[disabled] {
+    background-color: #002060;
+    color: #ffffff;
+    text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+}
+
+
+.btn-appmenu .caret {
+    border-bottom-color: #FFFFFF;
+    border-top-color: #FFFFFF;
+    opacity: 0.75;
+}
+
+.breadcrumb {
+	margin-top: 10px !important;
+	margin-bottom: 10px !important;
+}
+
+.pageTitle {	
+	margin-bottom: 5px;	
+}
+
+.pageTitle h2 small {
+	font-size: 80%;
+	font-weight: bold;
+}
+
+.pageTitle {
+	color: #888;
+	font-size: 18px;
+	line-height: 27px;
+}
+.pageTitle .project, .pageTitle .repository {
+	font-family: Helvetica, arial, freesans, clean, sans-serif;
+	font-size: 22px;
+}
+
+.pageTitle .controls {
+	font-size: 12px;
+}
+
+.pageTitle .repository {
+	font-weight: bold;
+}
+
+.originRepository {
+	font-family: Helvetica, arial, freesans, clean, sans-serif;
+	color: #888;
+	font-size: 12px;
+	line-height: 14px;
+	margin: 0px;
+}
+
+.forkSource, .forkEntry {
+	color: #888;
+}
+
+.forkSource {
+	font-size: 18px;
+	line-height: 20px;
+	padding: 5px 0px;
+}
+
+.forkEntry {
+	font-size: 14px;
+	padding: 2px 0px;
+}
+
+.forkSource .forks, .forkEntry .forks {
+	font-size: 10px;
+	padding-left: 5px;
+	text-decoration: underline;
+	vertical-align: middle;
+}
+
+div.repositoryUrlContainer {
+	padding: 2px;
+	background-color: #F5F5F5;
+    background-image: -moz-linear-gradient(center top , #FFFFFF, #E6E6E6);
+    background-repeat: repeat-x;
+    border-color: #E6E6E6 #E6E6E6 #B3B3B3;
+    border-image: none;
+    border-radius: 4px;
+    border-style: solid;
+    border-width: 1px;
+    box-shadow: 0 1px 0 rgba(255, 255, 255, 0.2) inset, 0 1px 2px rgba(0, 0, 0, 0.05);
+    color: #333333;    
+    vertical-align: middle;
+    border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+}
+
+div.repositoryUrlContainer:hover {
+	background-color: #E6E6E6;
+    background-position: 0 -15px;
+    color: #333333;
+    text-decoration: none;
+    transition: background-position 0.1s linear 0s;
+}
+
+div.repositoryUrlContainer:hover .caret {
+    opacity: 1;
+}
+
+div.repositoryUrlContainer:hover a:hover {
+	text-decoration: none;
+}
+
+span.repositoryUrlLeftCap, span.repositoryUrlRightCap {	
+	text-align: center;
+	color: black;
+	padding: 3px;
+	font-size: 11px;
+}
+
+span.repositoryUrlRightCap {	
+	font-weight: bold;
+	font-family:menlo,consolas,monospace;
+}
+
+div.repositoryUrl {
+	display: inline-block;	
+	font-size: 1em;
+	padding: 1px 4px 2px 4px;	
+	background-color: #fff;
+	border: 1px solid #ddd;
+	margin: 1px;
+}
+
+div.repositoryIndicator {
+	display:inline;
+	padding-top:0px;
+	margin-bottom:0px;
+}
+
+div.repositoryIndicator span.alert {
+	padding: 2px 7px 2px 7px;
+	vertical-align: middle;
+	font-size:0.85em;
+	font-weight:normal;
+}
+
+ul.urlMenu {
+	min-width: 350px;
+}
+
+ul.urlMenu li.url {
+	background-color: white;
+	padding: 0px 5px;
+	line-height: 24px;
+}
+
+ul.applicationMenu {
+	background-color: whiteSmoke;
+	min-width: 400px;
+}
+
+ul.applicationMenu li.action {
+	background-color: white;
+	padding: 0px 5px;
+	line-height: 24px;
+}
+
+span.applicationTitle, span.applicationTitle a {
+	display: inline;
+	font-weight: bold;
+	font-size:1.1em;
+	color: black !important;
+	padding: 0px;
+}
+
+div.applicationHeaderMenuItem {
+	padding-left: 10px;
+	color: black;
+}
+
+div.applicationLegalMenuItem {
+	padding-left: 10px;
+	color: #999;
+	font-size: 0.85em;
+}
+
+a.applicationMenuItem, span.commandMenuItem {
+	padding: 3px 10px;	
+	color: black;
+	display: inline;
+	padding: 0px;
+}
+
+span.commandMenuItem {
+	font-size: 0.85em;
+	font-family: menlo,consolas,monospace;
+}
+
+div.odd {
+	
+}
+
+div.even {
+	background-color: whiteSmoke;
+	vertical-align: middle;
+}
+
+span.authorizationControl label {
+	display: inline;
+	color: #777;
+	padding:5px 0px 5px 10px;	
+}
+
+div.page_footer {
+	clear: both;
+	height: 17px;
+	color: black;
+	background-color: #ffffff;
+	padding: 5px;
+	border-top: 1px solid #bbb;
+	font-style: italic;
+}
+
+pre, code, pre.prettyprint, pre.plainprint {
+	background-color: #ffffff;
+	color: black;
+	font-family: monospace;
+	font-size:12px;
+	border:0px;
+	padding: 0;
+	line-height: 1.35em;
+	vertical-align:top;
+}
+
+table {
+	margin-bottom: 5px;
+	font-size: inherit;
+}
+
+.table th {
+	vertical-align: top;
+}
+
+th {
+	vertical-align: middle;
+	text-align: left;	
+}
+
+div.sourceview {
+	overflow: hidden;
+}
+
+pre.prettyprint ol {
+	padding-left:25px;
+}
+
+#nums {
+    text-align: right;
+    padding-right:10px;
+    border-right:1px solid #ddd;
+    font-family: monospace;
+    line-height: 1.35em;
+    vertical-align:top;
+}
+
+#nums pre {
+    white-space: pre;
+}
+
+#nums pre, #lines pre {
+	margin: 0;	
+}
+
+#lines pre {
+	padding: 0px !important;	
+	border: 0px !important;
+	white-space: nowrap;
+}
+
+/* CSS trick to workaround #link topOfWindow offset problem */
+#nums .num {
+    border-top: 160px solid transparent;
+    margin-top: -160px;
+    -webkit-background-clip: padding-box;
+    -moz-background-clip: padding;
+    background-clip: padding-box;
+
+    color: #888;
+}
+
+#nums span:target {
+	background-color: #ffffbf;
+	color: black;
+	font-weight: bold;
+	border-bottom: 1px solid red;
+}
+
+#lines table {
+	margin: 0;
+}
+
+#lines td {
+	padding: 0;
+}
+
+#lines a {
+	padding-left: 5px;
+}
+
+#lines a:hover {
+	background-color: #ffffbf;
+	text-decoration: none;
+}
+
+#lines tr:hover {
+	background-color: #ffffbf;
+}
+#lines .odd {
+	background-color: white;
+}
+
+#lines .even {
+	background-color: #fafafa;
+}
+
+
+
+h1 small, h2 small, h3 small, h4 small, h5 small, h6 small {
+    color: #888;
+}
+
+.age0, .age1, .age2, .age3, .age4 {	
+	font-size: 12px;
+}
+
+/* age0: age < 2 hours */
+.age0 {
+	font-style: italic;
+	color: #008000;
+	font-weight: bold;
+}
+
+/* age1: 2 hours <= age < 2 days */
+.age1 {
+	font-style: italic;
+	color: #0000ff;
+	font-weight: bold;	
+}
+
+/* age2: 2 days < age <= 7 days */
+.age2 {
+	font-style: italic;
+	color: #2b60de;
+}
+
+/* age3: 7 days < age <= 30 days */
+.age3 {
+	color: #800080;
+}
+
+/* age4: > 30 days */
+.age4 {
+}
+
+/* Ensure that hovered ages are white */
+tr.light:hover .age0,
+tr.light:hover .age1,
+tr.light:hover .age2,
+tr.light:hover .age3,
+tr.light:hover .age4,
+tr.dark:hover .age0,
+tr.dark:hover .age1,
+tr.dark:hover .age2,
+tr.dark:hover .age3,
+tr.dark:hover .age4 {
+	color: #ffffff !important;
+}
+
+a.list {
+	text-decoration: none;
+	color: inherit;
+}
+
+a.list.subject {
+	font-weight: bold;
+}
+
+a.list.name {
+	font-weight: bold;	
+}
+
+a.list:hover {
+	text-decoration: underline;
+	color: #880000;
+}
+
+span.empty {
+	font-size: 0.9em;
+	font-style: italic;
+	padding-left:10px;
+	color: #008000;
+}
+
+span.link {
+	color: #888;
+}
+
+span.link, span.link a {
+	font-family: sans-serif;
+	font-size: 11px;
+}
+
+span.link em, div.link span em {
+	font-style: normal;
+	font-family: sans-serif;
+	font-size: 11px;	
+}
+
+span.activitySwatch {
+	border-radius: 3px;
+	padding: 1px 4px 2px 4px;
+	color: #ffffff;
+	vertical-align: center;
+}
+
+span.activitySwatch a {
+	color: inherit;
+}
+
+span.repositorySwatch {
+	padding: 1px 1px 2px 1px;	
+	color: #ffffff;
+	vertical-align: center;
+}
+
+span.repositorySwatch a {
+	color: inherit;
+}
+
+img.inlineIcon {
+	padding-left: 1px;
+	padding-right: 1px;
+}
+
+img.overview {
+	float:right;
+	border:1px solid #CCCCCC;
+}
+
+img.gravatar {
+    background-color: #ffffff;
+    border: 1px solid #ddd;
+    border-radius: 5px;
+    padding: 2px;
+}
+
+img.navbarGravatar {
+	border: 1px solid #fff;
+}
+
+div.searchResult {
+	padding: 10px 5px 10px 5px;
+}
+
+div.searchResult .summary {
+	font-weight: bold;
+}
+
+div.searchResult .branch {
+	color: #008000;
+}
+
+div.searchResult .author {
+	font-style: italic !important;
+}
+
+div.searchResult .date {
+	color:#999;
+}
+
+div.searchResult .body {
+	padding-left:20px;
+}
+
+div.searchResult .fragment {
+	padding: 7px 0;
+}
+
+div.searchResult .highlight {
+	background-color: #ccff66;
+	padding: 0 2px;
+}
+
+div.searchResult .ellipses {	
+	padding-left:25px;
+	color: #aaa;
+}
+
+div.searchResult pre {
+	margin: 1px 0px;
+	border: 0px;
+}
+
+div.searchResult .text {
+	border-left: 2px solid #ccc;
+	border-radius: 0px;
+	
+    padding: 0 0 0 15px;
+}
+
+div.searchResult ol {	
+	margin-bottom: 0px !important;
+}
+
+div.header, div.commitHeader, table.repositories th {
+	background-color: #fbfbfb;
+}
+
+div.header {
+	padding: 3px;
+	border: 1px solid #ddd;
+	border-bottom: 0;
+	border-radius: 3px 3px 0 0;
+	font-weight: bold;
+	font-family: Helvetica,arial,freesans,clean,sans-serif;
+}
+
+div.diffHeader {
+	/* CSS trick to workaround #link topOfWindow offset problem */
+    border-top: 65px solid transparent;
+    margin-top: -65px;
+    -webkit-background-clip: padding-box;
+    -moz-background-clip: padding;
+    background-clip: padding-box;
+}
+
+div.commitHeader {
+	margin:0 0 2px;
+	padding:7px 14px;	
+	border:1px solid #ddd;
+	border-radius: 3px;
+	-webkit-border-radius:3px;
+	-moz-border-radius:3px;border-radius:3px;
+}
+
+div.header a, div.commitHeader a {
+	color: black;
+	text-decoration: none;
+	font-weight: bold;
+}
+
+div.header a:hover, div.commitHeader a:hover {
+	text-decoration: underline;
+}
+
+div.page_nav2 {
+	padding: 5px 10px;
+	margin: -10px 0px 10px;
+	border-left: 1px solid #ccc;
+	border-right: 1px solid #ccc;
+	border-bottom: 1px solid #ccc;
+	border-radius: 0px 0px 3px 3px;	
+	background-color: #ECF1F4;
+	color: #666;
+	text-align: left;
+}
+
+div.page_nav2 a {
+	color: #002060;
+}
+
+div.admin_nav {
+	border-bottom: 0px;
+	text-align: right;
+	padding: 5px 5px 5px 2px;	
+}
+
+div.admin_nav a {
+	text-decoration: none;
+}
+
+div.admin_nav a:hover {	
+	text-decoration: underline;
+}
+
+span.search {
+	height: 40px;
+	padding-top:2px;
+}
+
+span.search input {
+	-webkit-border-radius:0;-moz-border-radius:0x;border-radius:0;
+	vertical-align: top;
+	background: url(search-icon.png) no-repeat 4px center;
+	background-color: transparent;
+	border: 1px solid transparent;
+	outline: none;
+	padding: 2px 2px 2px 22px;
+	text-shadow: none;
+	margin: 0px;
+	
+	color: #ddd;
+}
+
+span.search input:hover, span.search input:focus {
+	background-color: transparent;
+	border: 1px solid transparent;
+	padding: 2px 2px 2px 22px;
+	box-shadow: none;
+	color: #ddd;
+	border-bottom: 1px solid #ff9900;	
+}
+
+span.search input:focus {
+	color: white;
+}
+
+/* div.search input:focused { */
+/* 	background-color: transparent; */
+/* 	border: 1px solid transparent; */
+/* 	padding: 2px 2px 2px 22px; */
+/* 	text-shadow: none; */
+/* } */
+
+span.login input:focus {
+	background-color: rgba(255, 255, 255, 0.6);	
+	text-shadow: none;
+	color: white;
+}
+
+.commit_message {
+	padding: 8px;
+	border: solid #ddd;
+	border-width: 1px 0px 0px;
+	border-radius: 0px;
+}
+
+div.bug_open, span.bug_open {
+	padding: 2px;
+	background-color: #803333;
+	color: white;	
+	text-align: center;
+}
+
+div.bug_resolved, span.bug_resolved {
+	padding: 2px;
+	background-color: #408040;
+	color: white;
+	text-align: center;
+}
+
+div.bug_invalid, span.bug_invalid {
+	padding: 2px;
+	background-color: gray;
+	text-align: center;
+}
+
+div.bug_hold, span.bug_hold {
+	padding: 2px;
+	background-color: orange;
+	text-align: center;
+}
+
+div.diff {
+	font-family: monospace;
+	overflow: auto;
+}
+
+div.diff.header {
+	-moz-border-bottom-colors: none;
+    -moz-border-image: none;
+    -moz-border-left-colors: none;
+    -moz-border-right-colors: none;
+    -moz-border-top-colors: none;
+    background-color: #EDECE6;
+    border-color: #D9D8D1;
+    border-style: solid;
+    border-width: 1px;
+    font-weight: bold;
+    margin-top: 10px;
+    padding: 4px 0 2px;
+}
+
+div.diff.extended_header {
+	background-color: #F6F5EE;
+    padding: 2px 0;
+    font-family: inherit;
+}
+
+div.diff table {
+	border: 1px solid #ddd;
+}
+
+span.diff.add {
+	color: #008800;
+	font-family: inherit;
+}
+
+span.diff.remove {
+	color: #FFDDDD;
+	font-family: inherit;
+}
+
+span.diff.unchanged {
+	color: inherit;
+	font-family: inherit;
+}
+
+div.diff.hunk_header {
+	-moz-border-bottom-colors: none;
+    -moz-border-image: none;
+    -moz-border-left-colors: none;
+    -moz-border-right-colors: none;
+    -moz-border-top-colors: none;
+    border-color: #FFE0FF;
+    border-style: dotted;
+    border-width: 1px 0 0;
+    margin-top: 2px;
+    font-family: inherit;
+}
+
+span.diff.hunk_info {
+	background-color: #FFEEFF;	
+	color: #990099;
+	font-family: inherit;
+}
+
+span.diff.hunk_section {	
+	color: #AA22AA;
+	font-family: inherit;
+}
+
+div.diff.add2 {
+	background-color: #DDFFDD;
+    font-family: inherit;
+}
+
+div.diff.remove2 {
+	background-color: #FFDDDD;
+    font-family: inherit;
+}
+
+div.diff table {
+	border-radius: 0;
+	border-right: 1px solid #bbb;
+	border-bottom: 1px solid #bbb;
+	width: 100%;
+}
+
+div.diff table th, div.diff table td {
+	margin: 0px;
+	padding: 0px;
+	font-family: monospace;
+	border: 0;
+}
+
+div.diff table th {
+	background-color: #f0f0f0;
+	text-align: center;
+	color: #999;
+	padding-left: 5px;
+	padding-right: 5px;
+	width: 30px;
+}
+
+div.diff table th.header {
+	background-color: #D2C3AF;
+	border-right: 0px;
+	border-bottom: 1px solid #808080;
+	font-family: inherit;
+	font-size:0.9em;
+	color: black;
+	padding: 2px;
+	text-align: left;
+}
+
+div.diff table td.hunk_header {
+	background-color: #dAe2e5 !important;
+	border-top: 1px solid #bac2c5;	
+	border-bottom: 1px solid #bac2c5;
+	color: #555;
+}
+
+div.diff table td {
+	border-left: 1px solid #bbb;
+	background-color: #fbfbfb;
+}
+
+td.changeType {
+	width: 15px;
+}
+
+span.addition, span.modification, span.deletion, span.rename {
+	border: 1px solid #888;
+	float: left;
+	height: 0.8em;
+	margin: 0.2em 0.5em 0 0;
+	overflow: hidden;
+	width: 0.8em;
+}
+
+span.addition {
+	background-color: #ccffcc;
+}
+
+span.modification {
+	background-color: #ffdd88;
+}
+
+span.deletion {
+	background-color: #f8bbbb;
+}
+
+span.rename {
+	background-color: #cAc2f5;
+}
+
+div.commitLegend {
+	float: right;
+	padding: 0.4em 0.4em 0.2em 0.4em;
+	vertical-align:top;
+	margin: 0px;
+}
+
+div.commitLegend span {
+	font-size: 0.9em;
+	vertical-align: top;
+}
+
+div.references {
+	float: right;
+	text-align: right;
+}
+
+table.plain, table.summary {
+	width: 0 !important;
+	border: 0;
+}
+
+table.plain th, table.plain td, table.summary th, table.summary td {
+	white-space: nowrap;
+	padding: 1px 3px;
+	border: 0;
+}
+
+table.summary {
+	margin: 0px;
+}
+
+table.summary th {
+	color: #999;
+	padding-right: 10px;
+	text-align: right;
+	font-weight: normal;
+}
+
+table.pretty {
+	border:1px solid #ddd;
+	border-radius: 0 0 3px 3px;
+	width: 100%;
+}
+
+table.pretty td.icon {
+	padding: 0px 0px 0px 2px;	
+	width: 18px;
+	vertical-align: middle;
+}
+
+table.pretty td.icon img {
+	vertical-align: top;
+}
+
+table.pretty td {
+	padding: 2px 4px;
+	border-left: 0;
+}
+
+table.pretty td.message {
+	padding: 0px;
+}
+
+table.pretty table.nestedTable {
+	width: 100%;
+	margin-left: 4px !important;
+	margin-bottom: 0px !important;
+}
+
+table.comments td {
+	padding: 4px;
+	line-height: 17px;
+}
+
+table.repositories {	
+	border:1px solid #ddd;
+	border-spacing: 0px;
+	width: 100%;
+}
+
+table.repositories th {
+	padding: 4px;
+	border:0;
+}
+
+table.repositories th.right {	
+	border-right: 1px solid #ddd;	
+}	
+
+table.repositories td {
+	padding: 2px;
+	border-left: 0;
+}
+
+table.repositories td.rightAlign {	
+	text-align: right;
+	border-right: 1px solid #ddd;	
+}	
+
+table.repositories td.icon img {
+	vertical-align: top;
+}
+
+table.repositories tr.group {
+	background-color: #ccc;
+	border-left: 1px solid #ccc;
+	border-right: 1px solid #ccc;
+}
+
+table.repositories tr.group td {
+	font-weight: bold;		
+	color: black;
+	background-color: #ddd;
+	padding-left: 5px;
+	border-top: 1px solid #aaa; 	
+ 	border-bottom: 1px solid #aaa; 
+}
+
+table.repositories tr.group td a {
+	color: black;
+}
+
+table.palette { border:0; width: 0 !important; }
+table.palette td.header { 
+	font-weight: bold; 
+	background-color: #ffffff !important;
+	padding-top: 0px !important;
+	margin-bottom: 0 !imporant;	
+	border: 0 !important;
+	border-radius: 0 !important;
+	line-height: 1em;
+}
+table.palette td.pane {
+	padding: 0px;
+}
+
+table.gitnotes {		
+	border: 0;	
+}
+table.gitnotes td {
+	border-top: 1px solid #ddd;
+	padding-top: 3px;
+	vertical-align:top;
+}
+
+table.gitnotes table {
+	border: none;
+}
+
+table.gitnotes td table td {
+	border: none;
+	padding: 0px;
+}
+
+table.gitnotes td.info {
+	padding-right: 10px;
+}
+
+table.gitnotes td.message {
+	width: 65%;
+	border-left: 1px solid #ddd;
+	padding-left: 10px;
+}
+
+table.annotated {
+	border:1px solid #ddd;
+}
+
+table.annotated tr.even {
+	background-color: white;
+}
+
+table.annotated tr.odd {
+	background-color: #f5f5f5;
+}
+
+table.annotated td {
+	padding: 0px;
+	border: 0;
+}
+
+table.activity {
+	width: 100%;
+	margin-top: 10px;
+}
+
+table.activity td {
+	padding-top:7px;
+	padding-bottom:7px;
+}
+
+tr th a { background-position: right; padding-right: 15px; background-repeat:no-repeat; }
+tr th.wicket_orderDown a {background-image: url(arrow_down.png); }
+tr th.wicket_orderUp a { background-image: url(arrow_up.png); }
+tr th.wicket_orderNone a { background-image: url(arrow_off.png); }
+
+tr.light {
+	background-color: #ffffff;
+}
+
+tr.dark {
+	background-color: #f6f6f6;
+}
+
+/* currently both use the same, but it can change */
+tr.light:hover,
+tr.dark:hover {
+	background-color: #002060;
+	color: white;
+}
+
+tr.light:hover a,
+tr.dark:hover a {
+	color: white;	
+}
+
+td.author {
+	font-style: italic !important;
+}
+
+td.date {
+	/*font-style: italic !important;*/
+	white-space: nowrap;
+}
+
+span.sha1, span.sha1 a, span.sha1 a span, .commit_message, span.shortsha1 {
+	font-family: consolas, monospace;
+	font-size: 13px;
+}
+
+span.shortsha1 {
+	font-size: 12px;
+}
+
+td.mode {
+	text-align: right;
+	font-family: monospace;
+	width: 8em;
+	padding-right:15px;
+}
+
+td.size {
+	text-align: right;
+	width: 8em;	
+	padding-right:15px;
+}
+
+td.rightAlign {
+	text-align: right;
+}
+
+td.treeLinks {
+	text-align: right;
+	width: 13em;
+}
+
+span.help-inline {
+	color: #777;
+}
+
+span.metricsTitle {
+	font-size: 2em;
+}
+
+.tagRef, .headRef, .localBranch, .remoteBranch, .otherRef, .pullRef {	
+	padding: 0px 3px;
+	margin-right:2px;
+	font-family: sans-serif;
+	font-size: 9px;
+	font-weight: normal;
+	border: 1px solid;
+	color: black;	
+}
+
+.tagRef a, .headRef a, .localBranch a, .remoteBranch a, .otherRef a, .pullRef a {
+	font-size: 9px;
+	text-decoration: none;
+	color: black !important;
+}
+
+.tagRef a:hover, .headRef a:hover, .localBranch a:hover, .remoteBranch a:hover, .otherRef a:hover, .pullRef a:hover {
+	color: black !important;
+	text-decoration: underline;
+}
+
+.otherRef {
+	background-color: #b0e0f0;
+	border-color: #80aaaa;	
+}
+
+.pullRef {
+	background-color: rgb(255, 221, 136);
+	border-color: rgb(136, 136, 136);
+}
+
+.remoteBranch {
+	background-color: #cAc2f5;
+	border-color: #6c6cbf;
+}
+
+.tagRef {
+	background-color: #ffffaa;
+	border-color: #ffcc00;
+}
+
+.headRef {
+	background-color: #ffaaff;
+	border-color: #ff00ee;
+}
+
+.localBranch {
+	background-color: #ccffcc;
+	border-color: #00cc33;
+}
+
+table .palette td.buttons button {
+	-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;	
+	border: 1px solid #ccc !important;
+	padding: 10px;
+	margin-bottom: 10px;
+}
+
+table .palette td.buttons button:hover {
+	border: 1px solid #0069D6 !important;
+}
+
+table .palette td.buttons button:active {
+	border: 1px solid orange !important;
+}
+
+.feedbackPanelERROR, .feedbackPanelINFO {	
+	list-style: none;
+	line-height: 35px;
+}
+
+.feedbackPanelINFO span, .feedbackPanelERROR span {
+	position:relative;padding:7px 15px;margin-top:5px;margin-bottom:5px;color:#404040;background-color:#eedc94;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#fceec1), to(#eedc94));background-image:-moz-linear-gradient(top, #fceec1, #eedc94);background-image:-ms-linear-gradient(top, #fceec1, #eedc94);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #fceec1), color-stop(100%, #eedc94));background-image:-webkit-linear-gradient(top, #fceec1, #eedc94);background-image:-o-linear-gradient(top, #fceec1, #eedc94);background-image:linear-gradient(top, #fceec1, #eedc94);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fceec1', endColorstr='#eedc94', GradientType=0);text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);border-color:#eedc94 #eedc94 #e4c652;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);text-shadow:0 1px 0 rgba(255, 255, 255, 0.5);border-width:1px;border-style:solid;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.25);-moz-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.25);box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.25);
+}
+
+.feedbackPanelERROR span {
+	color: #ffffff;
+	background-color:#c43c35;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#ee5f5b), to(#c43c35));background-image:-moz-linear-gradient(top, #ee5f5b, #c43c35);background-image:-ms-linear-gradient(top, #ee5f5b, #c43c35);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #ee5f5b), color-stop(100%, #c43c35));background-image:-webkit-linear-gradient(top, #ee5f5b, #c43c35);background-image:-o-linear-gradient(top, #ee5f5b, #c43c35);background-image:linear-gradient(top, #ee5f5b, #c43c35);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ee5f5b', endColorstr='#c43c35', GradientType=0);text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);border-color:#c43c35 #c43c35 #882a25;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+}
+
+/* google-code-prettify line numbers */
+li.L0,
+li.L1,
+li.L2,
+li.L3,
+li.L4,
+li.L5,
+li.L6,
+li.L7,
+li.L8,
+li.L9 { color: #888; border-left: 1px solid #ccc; padding-left:5px; list-style-type: decimal !important; }
+
+/* Alternate shading for lines */
+li.L1,
+li.L3,
+li.L5,
+li.L7,
+li.L9 { background: #fafafa !important; }
+
+div.markdown pre {
+    background-color: #F5F5F5;
+    border: 1px solid rgba(0, 0, 0, 0.15);
+    border-radius: 4px 4px 4px 4px;
+    display: block;
+    font-size: 12px;
+    line-height: 18px;
+    margin: 0 0 9px;
+    padding: 8.5px;
+    white-space: pre-wrap;
+}
+
+div.markdown pre code {
+    background-color: inherit;
+    border: none;    
+    padding: 0;
+}
+
+div.markdown code {
+	background-color: #ffffe0;
+    border: 1px solid orange;
+    border-radius: 3px;
+    padding: 0 0.2em;
+}
+
+div.markdown a {
+	text-decoration: underline;	
+}
+
+div.markdown em {
+	color: #b05000;
+}
+
+div.markdown table.text th, div.markdown table.text td {
+	vertical-align: top;
+	border-top: 1px solid #ccc;
+	padding:5px;
+}
\ No newline at end of file
diff --git a/src/main/resources/gitblt-favicon.png b/src/main/resources/gitblt-favicon.png
new file mode 100644
index 0000000..64b52fe
--- /dev/null
+++ b/src/main/resources/gitblt-favicon.png
Binary files differ
diff --git a/resources/gitblt-logo.png b/src/main/resources/gitblt-logo.png
similarity index 100%
rename from resources/gitblt-logo.png
rename to src/main/resources/gitblt-logo.png
Binary files differ
diff --git a/resources/gitblt2.png b/src/main/resources/gitblt2.png
similarity index 100%
rename from resources/gitblt2.png
rename to src/main/resources/gitblt2.png
Binary files differ
diff --git a/resources/gitblt2_white.png b/src/main/resources/gitblt2_white.png
similarity index 100%
rename from resources/gitblt2_white.png
rename to src/main/resources/gitblt2_white.png
Binary files differ
diff --git a/resources/gitblt_25.png b/src/main/resources/gitblt_25.png
similarity index 100%
rename from resources/gitblt_25.png
rename to src/main/resources/gitblt_25.png
Binary files differ
diff --git a/resources/gitblt_25_white.png b/src/main/resources/gitblt_25_white.png
similarity index 100%
rename from resources/gitblt_25_white.png
rename to src/main/resources/gitblt_25_white.png
Binary files differ
diff --git a/src/main/resources/github_32x32.png b/src/main/resources/github_32x32.png
new file mode 100644
index 0000000..8b25551
--- /dev/null
+++ b/src/main/resources/github_32x32.png
Binary files differ
diff --git a/resources/gitweb-favicon.png b/src/main/resources/gitweb-favicon.png
similarity index 100%
rename from resources/gitweb-favicon.png
rename to src/main/resources/gitweb-favicon.png
Binary files differ
diff --git a/resources/health_16x16.png b/src/main/resources/health_16x16.png
similarity index 100%
rename from resources/health_16x16.png
rename to src/main/resources/health_16x16.png
Binary files differ
diff --git a/resources/heart_16x16.png b/src/main/resources/heart_16x16.png
similarity index 100%
rename from resources/heart_16x16.png
rename to src/main/resources/heart_16x16.png
Binary files differ
diff --git a/resources/information_16x16.png b/src/main/resources/information_16x16.png
similarity index 100%
rename from resources/information_16x16.png
rename to src/main/resources/information_16x16.png
Binary files differ
diff --git a/resources/lock_16x16.png b/src/main/resources/lock_16x16.png
similarity index 100%
rename from resources/lock_16x16.png
rename to src/main/resources/lock_16x16.png
Binary files differ
diff --git a/resources/lock_go_16x16.png b/src/main/resources/lock_go_16x16.png
similarity index 100%
rename from resources/lock_go_16x16.png
rename to src/main/resources/lock_go_16x16.png
Binary files differ
diff --git a/resources/lock_pull_16x16.png b/src/main/resources/lock_pull_16x16.png
similarity index 100%
rename from resources/lock_pull_16x16.png
rename to src/main/resources/lock_pull_16x16.png
Binary files differ
diff --git a/resources/mail_16x16.png b/src/main/resources/mail_16x16.png
similarity index 100%
rename from resources/mail_16x16.png
rename to src/main/resources/mail_16x16.png
Binary files differ
diff --git a/resources/pixel.png b/src/main/resources/pixel.png
similarity index 100%
rename from resources/pixel.png
rename to src/main/resources/pixel.png
Binary files differ
diff --git a/resources/rosette_16x16.png b/src/main/resources/rosette_16x16.png
similarity index 100%
rename from resources/rosette_16x16.png
rename to src/main/resources/rosette_16x16.png
Binary files differ
diff --git a/resources/rosette_32x32.png b/src/main/resources/rosette_32x32.png
similarity index 100%
rename from resources/rosette_32x32.png
rename to src/main/resources/rosette_32x32.png
Binary files differ
diff --git a/resources/script_16x16.png b/src/main/resources/script_16x16.png
similarity index 100%
rename from resources/script_16x16.png
rename to src/main/resources/script_16x16.png
Binary files differ
diff --git a/resources/search-icon.png b/src/main/resources/search-icon.png
similarity index 100%
rename from resources/search-icon.png
rename to src/main/resources/search-icon.png
Binary files differ
diff --git a/resources/settings_16x16.png b/src/main/resources/settings_16x16.png
similarity index 100%
rename from resources/settings_16x16.png
rename to src/main/resources/settings_16x16.png
Binary files differ
diff --git a/resources/settings_32x32.png b/src/main/resources/settings_32x32.png
similarity index 100%
rename from resources/settings_32x32.png
rename to src/main/resources/settings_32x32.png
Binary files differ
diff --git a/resources/shield_16x16.png b/src/main/resources/shield_16x16.png
similarity index 100%
rename from resources/shield_16x16.png
rename to src/main/resources/shield_16x16.png
Binary files differ
diff --git a/src/main/resources/smartgithg_32x32.png b/src/main/resources/smartgithg_32x32.png
new file mode 100644
index 0000000..e232791
--- /dev/null
+++ b/src/main/resources/smartgithg_32x32.png
Binary files differ
diff --git a/src/main/resources/sourcetree_32x32.png b/src/main/resources/sourcetree_32x32.png
new file mode 100644
index 0000000..a5dd96f
--- /dev/null
+++ b/src/main/resources/sourcetree_32x32.png
Binary files differ
diff --git a/src/main/resources/sparkleshare_32x32.png b/src/main/resources/sparkleshare_32x32.png
new file mode 100644
index 0000000..51afbe2
--- /dev/null
+++ b/src/main/resources/sparkleshare_32x32.png
Binary files differ
diff --git a/resources/star_16x16.png b/src/main/resources/star_16x16.png
similarity index 100%
rename from resources/star_16x16.png
rename to src/main/resources/star_16x16.png
Binary files differ
diff --git a/resources/star_32x32.png b/src/main/resources/star_32x32.png
similarity index 100%
rename from resources/star_32x32.png
rename to src/main/resources/star_32x32.png
Binary files differ
diff --git a/resources/tag_16x16.png b/src/main/resources/tag_16x16.png
similarity index 100%
rename from resources/tag_16x16.png
rename to src/main/resources/tag_16x16.png
Binary files differ
diff --git a/src/main/resources/tower_32x32.png b/src/main/resources/tower_32x32.png
new file mode 100644
index 0000000..4de46f2
--- /dev/null
+++ b/src/main/resources/tower_32x32.png
Binary files differ
diff --git a/resources/user_16x16.png b/src/main/resources/user_16x16.png
similarity index 100%
rename from resources/user_16x16.png
rename to src/main/resources/user_16x16.png
Binary files differ
diff --git a/resources/users_16x16.png b/src/main/resources/users_16x16.png
similarity index 100%
rename from resources/users_16x16.png
rename to src/main/resources/users_16x16.png
Binary files differ
diff --git a/resources/vcard_16x16.png b/src/main/resources/vcard_16x16.png
similarity index 100%
rename from resources/vcard_16x16.png
rename to src/main/resources/vcard_16x16.png
Binary files differ
diff --git a/docs/.gitignore b/src/site/.gitignore
similarity index 100%
rename from docs/.gitignore
rename to src/site/.gitignore
diff --git a/src/site/administration.mkd b/src/site/administration.mkd
new file mode 100644
index 0000000..7ed40eb
--- /dev/null
+++ b/src/site/administration.mkd
@@ -0,0 +1,200 @@
+## Gitblit Configuration
+
+### Administering Repositories
+Repositories can be created, edited, renamed, and deleted through the web UI.  They may also be created, edited, and deleted from the command-line using real [Git](http://git-scm.com) or your favorite file manager and text editor.
+
+All repository settings are stored within the repository `.git/config` file under the *gitblit* section.
+
+    [gitblit]
+	    description = master repository
+	    owner = james
+	    useTickets = false
+	    useDocs = true
+	    showRemoteBranches = false
+	    accessRestriction = clone
+	    isFrozen = false
+	    showReadme = false
+	    federationStrategy = FEDERATE_THIS
+	    isFederated = false
+	    skipSizeCalculation = false
+	    federationSets = 
+
+#### Repository Names
+Repository names must be case-insensitive-unique but are CASE-SENSITIVE ON CASE-SENSITIVE FILESYSTEMS.  The name must be composed of letters, digits, or `/ _ - . ~`<br/>
+Whitespace is illegal.
+
+Repositories can be grouped within subfolders.  e.g. *libraries/mycoollib.git* and *libraries/myotherlib.git*
+
+All repositories created with Gitblit are *bare* and will automatically have *.git* appended to the name at creation time, if not already specified. 
+
+#### Repository Owner
+The *Repository Owner* has the special permission of being able to edit a repository through the web UI.  The Repository Owner is not permitted to rename the repository, delete the repository, or reassign ownership to another user.
+
+### Access Restrictions and Access Permissions
+![permissions matrix](permissions_matrix.png "Permissions and Restrictions")
+
+#### Discrete Permissions (Gitblit v1.2.0+)
+
+Since v1.2.0, Gitblit supports more discrete permissions.  While Gitblit does not offer a built-in solution for branch-based permissions like Gitolite, it does allow for the following repository access permissions:
+
+- **V** (view in web ui, RSS feeds, download zip)
+- **R** (clone)
+- **RW** (clone and push)
+- **RWC** (clone and push with ref creation)
+- **RWD** (clone and push with ref creation, deletion)
+- **RW+** (clone and push with ref creation, deletion, rewind)
+
+These permission codes are combined with the repository path to create a user permission:
+
+    RW:mygroup/myrepo.git
+
+**NOTE:**  
+The following repository permissions are equivalent:
+
+- myrepo.git
+- RW+:myrepo.git
+
+This is to preserve backwards-compatibility with Gitblit <= 1.1.0 which granted rewind power to all access-permitted users.
+
+#### Discrete Permissions with Regex Matching (Gitblit v1.2.0+)
+
+Gitblit also supports *case-insensitive* regex matching for repository permissions.  The following permission grants push privileges to all repositories in the *mygroup* folder.
+
+    RW:mygroup/.*
+
+##### Exclusions
+
+When using regex matching it may also be useful to exclude specific repositories or to exclude regex repository matches.  You may specify the **X** permission for exclusion.  The following example grants clone permission to all repositories except the repositories in mygroup.  The user/team will have no access whatsoever to these repositories.
+
+    X:mygroup/.*
+    R:.*
+
+##### Order is Important
+
+The preceding example should suggest that order of permissions is important with regex matching.  Here are the rules for determining the permission that is applied to a repository request:
+
+1. If the user is an admin or repository owner, then RW+
+2. Else if user has an explicit permission, use that
+3. Else check for the first regex match in user permissions
+4. Else check for the HIGHEST permission from team memberships
+    1. If the team is an admin team, then RW+
+    2. Else if a team has an explicit permission, use that
+    3. Else check for the first regex match in team permissions
+
+#### No-So-Discrete Permissions (Gitblit &lt;= v1.1.0)
+
+Prior to v1.2.0, Gitblit has two main access permission groupings:  
+
+1. what you are permitted to do as an anonymous user
+2. **RW+** for any permitted user
+
+#### Committer Verification
+
+You may optionally enable committer verification which requires that each commit be committed by the authenticated user pushing the commits.  i.e. If Bob is pushing the commits, Bob **must** be the committer of those commits.
+
+**How is this enforced?**
+
+Bob must set his *user.name* and *user.email* values for the repository to match his Gitblit user account **BEFORE** committing to his repository.
+<pre>
+[user "bob"]
+    displayName = Bob Jones
+    emailAddress = bob@somewhere.com
+</pre>
+<pre>
+    git config user.name "Bob Jones"
+    git config user.email bob@somewhere.com	
+</pre>
+or
+
+    git config user.name bob
+    git config user.email bob@somewhere.com	
+
+If the Gitblit account does not specify an email address, then the committer email address is ignored.  However, if the account does specify an address it must match the committer's email address.  Display name or username can be used as the committer name.
+
+All checks are case-insensitive.
+
+**What about merges?**
+
+You can not use fast-forward merges on your client when using committer verification.  You must specify *--no-ff* to ensure that a merge commit is created with your identity as the committer.  Only the first/left parent chain is traversed when verifying commits.
+
+#### Reflog
+
+Gitblit v1.2.1 introduced an incomplete reflog mechanism which was completed in 1.3.0.  All pushes to Gitblit are automatically logged on an orphan branch, refs/gitblit/reflog.  If this ref exists, the reflog page link will be displayed on the repository pages.
+
+This reflog is similar to, but not the same as, the normal Git reflog. The Gitblit reflog links Gitblit accounts to ref changes and because it is stored on an orphan branch, the reflog is portable by the federation mechanism or by a normal <code>git clone --mirror</code> command.
+
+### Teams
+
+Teams have assigned users and assigned repositories.  A user can be a member of multiple teams and a repository may belong to multiple teams.  This allows the administrator to quickly add a user to a team without having to keep track of all the appropriate repositories. 
+
+### Administering Users
+All users and permissions are stored in the `users.conf` file. Your file extension must be *.conf* in order to use this user service.
+
+The `users.conf` file uses a Git-style configuration format:
+
+    [user "admin"]
+	    password = admin
+	    role = "#admin"
+	    role = "#notfederated"
+	    repository = RW+:repo1.git
+	    repository = RW+:repo2.git
+	    
+	[user "hannibal"]
+		password = bossman
+		repository = RWD:topsecret.git
+		repository = RW+:ateam/[A-Za-z0-9-~_\\./]+
+
+	[user "faceman"]
+		password = vanity
+
+	[user "murdock"]
+		password = crazy		
+	    
+	[user "babaracus"]
+		password = grrrr
+	    
+	[team "ateam"]
+		user = hannibal
+		user = faceman
+		user = murdock
+		user = babaracus
+		repository = RW:topsecret.git
+		mailingList = list@ateam.org
+		postReceiveScript = sendmail
+
+The `users.conf` file allows flexibility for adding new fields to a UserModel object without imposing the complexity of relying on an embedded SQL database. 
+
+### Usernames
+Usernames must be unique and are case-insensitive.  
+Whitespace is illegal.
+
+### Passwords
+User passwords are CASE-SENSITIVE and may be *plain*, *md5*, or *combined-md5* formatted (see `gitblit.properties` -> *realm.passwordStorage*).
+
+### User Roles
+There are four actual *roles* in Gitblit:
+
+- *#admin*, which grants administrative powers to that user for all repositories, users, and teams
+- *#notfederated*, which prevents an account from being pulled by another Gitblit instance
+- *#create*, which allows the user the power to create personal repositories
+- *#fork*, which allows the user to create a personal fork of an existing Gitblit-hosted repository
+
+### Personal Repositories & Forks
+
+Personal Repositories and Forks are related but are controlled individually.
+
+#### Creating a Personal Repository
+A user may be granted the power to create personal repositories by specifying the *#create* role through the web ui or through the RPC mechanism via the Gitblit Manager.  Personal repositories are exactly like common/shared repositories except that the owner has a few additional administrative powers for that repository, like rename and delete.
+
+#### Creating a Fork
+A user may also be granted the power to fork an existing repository hosted on your Gitblit server to their own personal clone by specifying the *#fork* role through the web ui or via the Gitblit Manager.
+
+Forks are mostly likely personal repositories or common/shared repositories except for two important differences:
+
+1. Forks inherit a view/clone access list from the origin repository.  
+i.e. if Team A has clone access to the origin repository, then by default Team A also has clone access to the fork.  This is to facilitate collaboration.
+2. Forks are always listed in the fork network, regardless of any access restriction set on the fork.  
+In other words, if you fork *RepoA.git* to *~me/RepoA.git* and then set the access restriction of *~me/RepoA.git* to *Authenticated View, Clone, & Push* your fork will still be listed in the fork network for *RepoA.git*.
+
+If you really must have an invisible fork, the clone it locally, create a new personal repository for your invisible fork, and push it back to that personal repository.
+
diff --git a/docs/architecture.odg b/src/site/architecture.odg
similarity index 100%
rename from docs/architecture.odg
rename to src/site/architecture.odg
Binary files differ
diff --git a/src/site/custom.less b/src/site/custom.less
new file mode 100644
index 0000000..96e70ca
--- /dev/null
+++ b/src/site/custom.less
@@ -0,0 +1,94 @@
+/*!
+ * Gitblit Bootstrap Overrides
+ *
+ * Copyright 2012 gitblit.com
+ * Licensed under the Apache License v2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ */
+
+// GLOBAL VALUES
+// --------------------------------------------------
+@blueDark:					#002060;
+@standardGray:				#ccc;
+@cornflower:				#abd4ff;
+@white:						#fff;
+
+
+// Dropdown
+// -------------------------
+@dropdownLinkBackgroundHover:   @blueDark;
+
+// Navbar
+// -------------------------
+@navbarHeight:                    45px;
+@navbarBackground:                @blueDark;
+@navbarBackgroundHighlight:       @blueDark;
+@navbarText:                      @white;
+@navbarLinkColor:                 @white;
+@navbarLinkColorHover:            @cornflower;
+@navbarLinkColorActive:           @cornflower;
+@navbarLinkBackgroundHover:       transparent;
+@navbarLinkBackgroundActive:      transparent;
+
+
+.navbar {
+	.nav {
+		margin: 0px;
+
+		li > a {
+			text-shadow: 0 1px 0 #000;
+		}
+
+		li > a:hover {
+			text-shadow: 0 0 1em white;
+		}
+		
+		.active > a, 
+		.active > a:hover,
+		.active > a:focus {
+			box-shadow: none;
+			text-decoration: underline;
+		}
+		
+		ul.dropdown-menu > li > a {
+			text-shadow: none;
+		}
+	}
+
+	.brand {
+		@elementHeight: 27px;
+		padding: 10px 20px;
+	}
+	
+	.pull-right {
+		margin: 0;
+	}
+	
+	ul > li:focus, .active {
+		outline: 0;	
+		padding-bottom: 1px;
+		border-bottom: 3px solid #ff9900;
+		margin-bottom: -1px;
+	}
+}
+
+.navbar-inner {
+	background-color: @blueDark;
+	border-bottom: 2px solid #ff9900;
+}
+
+h3 small { font-size: @baseFontSize + 3; }
+body { padding-top: @navbarHeight + 15 } /* 60px to make the container go all the way to the bottom of the topbar */
+footer { margin-top: 25px; padding: 15px 0 16px; border-top: 1px solid #E5E5E5; }
+
+a:hover { text-decoration: underline !important; }
+em { color: #b05000; }
+code {
+	color: #000000;
+	background-color: #ffffe0;
+    border: 1px solid orange;
+    border-radius: 3px;
+    padding: 0 0.2em;
+    font-family: monospace;
+}
\ No newline at end of file
diff --git a/src/site/design.mkd b/src/site/design.mkd
new file mode 100644
index 0000000..1abb14a
--- /dev/null
+++ b/src/site/design.mkd
@@ -0,0 +1,85 @@
+## Design Principles
+1. [Keep It Simple, Stupid](http://en.wikipedia.org/wiki/KISS_principle)
+2. Offer useful features for serving Git repositories.  If feature is complex, refer to #1.
+3. All dependencies must be retrievable from a publicly accessible [Maven](http://maven.apache.org) repository.<br/>This is to ensure authenticity of dependencies and to automate the setup of developer environments.
+
+## Architecture
+
+![block diagram](architecture.png "Gitblit Architecture")
+
+### Bundled Dependencies
+The following dependencies are bundled with Gitblit.
+
+- [Bootstrap](http://twitter.github.com/bootstrap) (Apache 2.0)
+- [GLYPHICONS](http://glyphicons.com) (Creative Commons CC-BY)
+- [Iconic](http://somerandomdude.com/work/iconic) (Creative Commons Share Alike 3.0)
+- [AngularJS](http://angularjs.org) (MIT)
+- [Clippy](https://github.com/mojombo/clippy) (MIT)
+- [google-code-prettify](http://code.google.com/p/google-code-prettify) (Apache 2.0)
+- [Commons Daemon](http://commons.apache.org/daemon) (Apache 2.0)
+- magnifying glass search icon courtesy of [Gnome](http://gnome.org) (Creative Commons CC-BY)
+- Git logo originally designed by [Jason Long](http://git-scm.com/downloads/logos)
+- modified Git logo originally designed by [Henrik Nyh](http://henrik.nyh.se/2007/06/alternative-git-logo-and-favicon)
+- fork icon courtesy of [Ember.js](http://emberjs.com)
+- other icons courtesy of [FatCow Hosting](http://www.fatcow.com/free-icons) (Creative Commons CC-BY)
+
+### Downloaded Dependencies
+The following dependencies are automatically downloaded by Gitblit GO (or already bundled with the WAR) from the Apache Maven repository and from the Eclipse Maven repository when Gitblit is launched for the first time.
+
+- [JGit][jgit] (EDL 1.0)
+- [Wicket](http://wicket.apache.org) (Apache 2.0)
+- [WicketStuff GoogleCharts](https://github.com/wicketstuff/core/wiki/GoogleCharts) (Apache 2.0)
+- [MarkdownPapers](http://markdown.tautua.org) (Apache 2.0)
+- [Jetty](http://eclipse.org/jetty) (Apache 2.0, EPL 1.0)
+- [SLF4J](http://www.slf4j.org) (MIT/X11)
+- [Log4j](http://logging.apache.org/log4j) (Apache 2.0)
+- [JCommander](http://jcommander.org) (Apache 2.0)
+- [BouncyCastle](http://www.bouncycastle.org) (MIT/X11)
+- [JSch - Java Secure Channel](http://www.jcraft.com/jsch) (BSD)
+- [Rome](http://rome.dev.java.net) (Apache 1.1)
+- [jdom](http://www.jdom.org) (Apache-style JDOM license)
+- [google-gson](http://code.google.com/google-gson) (Apache 2.0)
+- [javamail](http://kenai.com/projects/javamail) (CDDL-1.0, BSD, GPL-2.0, GNU-Classpath)
+- [Groovy](http://groovy.codehaus.org) (Apache 2.0)
+- [Lucene](http://lucene.apache.org) (Apache 2.0)
+- [UnboundID](http://www.unboundid.com) (LGPL 2.1)
+- [Ivy](http://ant.apache.org/ivy) (Apache 2.0)
+- [JCalendar](http://www.toedter.com/en/jcalendar) (LGPL 2.1)
+- [Commons-Compress](http://commons.apache.org/compress) (Apache 2.0)
+- [XZ for Java](http://tukaani.org/xz/java.html) (Public Domain)
+- [FreeMarker](http://www.freemarker.org) (modified BSD)
+- [Waffle](http://dblock.github.io/waffle) (EPL 1.0)
+- [JNA](https://github.com/twall/jna) (LGPL 2.1)
+- [Guava](https://code.google.com/p/guava-libraries) (Apache 2.0)
+
+### Other Build Dependencies
+- [Fancybox image viewer](http://fancybox.net) (MIT and GPL dual-licensed)
+- [JUnit](http://junit.org) (Common Public License)
+- [Moxie](http://moxie.gitblit.com) (Apache 2.0)
+
+## Building from Source
+[Eclipse](http://eclipse.org) is recommended for development as the project settings are preconfigured.
+
+Additionally, [Google CodePro AnalytiX](http://code.google.com/javadevtools), [eclipse-cs](http://eclipse-cs.sourceforge.net), [FindBugs](http://findbugs.sourceforge.net), and [EclEmma](http://www.eclemma.org) are recommended development tools.
+
+1. Clone the git repository from [Github][gitbltsrc].
+2. Import the gitblit project into your Eclipse workspace.
+*There will be lots of build errors.*
+3. Using Ant, execute the `build.xml` script in the project root.
+*This will download all necessary build dependencies and will also generate the Keys class for accessing settings.*
+4. Select your gitblit project root and **Refresh** the project, this should correct all build problems.
+5. Using JUnit, execute the `com.gitblit.tests.GitBlitSuite` test suite.
+*This will clone some repositories from the web and run through the unit tests.*
+6. Execute the *com.gitblit.GitBlitServer* class to start Gitblit.
+
+
+## Contributing
+Pull requests are preferred.  Patches are welcome.
+
+Contributions must be your own original work and must licensed under the [Apache License, Version 2.0][apachelicense], the same license used by Gitblit.
+
+[jgit]: http://eclipse.org/jgit "Eclipse JGit Site"
+[git]: http://git-scm.com "Official Git Site"
+[gitbltsrc]: http://github.com/gitblit "gitblit git repository"
+[googlecode]: http://code.google.com/p/gitblit "gitblit project management"
+[apachelicense]: http://www.apache.org/licenses/LICENSE-2.0 "Apache License, Version 2.0"
diff --git a/docs/fancybox/blank.gif b/src/site/fancybox/blank.gif
similarity index 100%
rename from docs/fancybox/blank.gif
rename to src/site/fancybox/blank.gif
Binary files differ
diff --git a/docs/fancybox/fancy_close.png b/src/site/fancybox/fancy_close.png
similarity index 100%
rename from docs/fancybox/fancy_close.png
rename to src/site/fancybox/fancy_close.png
Binary files differ
diff --git a/docs/fancybox/fancy_loading.png b/src/site/fancybox/fancy_loading.png
similarity index 100%
rename from docs/fancybox/fancy_loading.png
rename to src/site/fancybox/fancy_loading.png
Binary files differ
diff --git a/docs/fancybox/fancy_nav_left.png b/src/site/fancybox/fancy_nav_left.png
similarity index 100%
rename from docs/fancybox/fancy_nav_left.png
rename to src/site/fancybox/fancy_nav_left.png
Binary files differ
diff --git a/docs/fancybox/fancy_nav_right.png b/src/site/fancybox/fancy_nav_right.png
similarity index 100%
rename from docs/fancybox/fancy_nav_right.png
rename to src/site/fancybox/fancy_nav_right.png
Binary files differ
diff --git a/docs/fancybox/fancy_shadow_e.png b/src/site/fancybox/fancy_shadow_e.png
similarity index 100%
rename from docs/fancybox/fancy_shadow_e.png
rename to src/site/fancybox/fancy_shadow_e.png
Binary files differ
diff --git a/docs/fancybox/fancy_shadow_n.png b/src/site/fancybox/fancy_shadow_n.png
similarity index 100%
rename from docs/fancybox/fancy_shadow_n.png
rename to src/site/fancybox/fancy_shadow_n.png
Binary files differ
diff --git a/docs/fancybox/fancy_shadow_ne.png b/src/site/fancybox/fancy_shadow_ne.png
similarity index 100%
rename from docs/fancybox/fancy_shadow_ne.png
rename to src/site/fancybox/fancy_shadow_ne.png
Binary files differ
diff --git a/docs/fancybox/fancy_shadow_nw.png b/src/site/fancybox/fancy_shadow_nw.png
similarity index 100%
rename from docs/fancybox/fancy_shadow_nw.png
rename to src/site/fancybox/fancy_shadow_nw.png
Binary files differ
diff --git a/docs/fancybox/fancy_shadow_s.png b/src/site/fancybox/fancy_shadow_s.png
similarity index 100%
rename from docs/fancybox/fancy_shadow_s.png
rename to src/site/fancybox/fancy_shadow_s.png
Binary files differ
diff --git a/docs/fancybox/fancy_shadow_se.png b/src/site/fancybox/fancy_shadow_se.png
similarity index 100%
rename from docs/fancybox/fancy_shadow_se.png
rename to src/site/fancybox/fancy_shadow_se.png
Binary files differ
diff --git a/docs/fancybox/fancy_shadow_sw.png b/src/site/fancybox/fancy_shadow_sw.png
similarity index 100%
rename from docs/fancybox/fancy_shadow_sw.png
rename to src/site/fancybox/fancy_shadow_sw.png
Binary files differ
diff --git a/docs/fancybox/fancy_shadow_w.png b/src/site/fancybox/fancy_shadow_w.png
similarity index 100%
rename from docs/fancybox/fancy_shadow_w.png
rename to src/site/fancybox/fancy_shadow_w.png
Binary files differ
diff --git a/docs/fancybox/fancy_title_left.png b/src/site/fancybox/fancy_title_left.png
similarity index 100%
rename from docs/fancybox/fancy_title_left.png
rename to src/site/fancybox/fancy_title_left.png
Binary files differ
diff --git a/docs/fancybox/fancy_title_main.png b/src/site/fancybox/fancy_title_main.png
similarity index 100%
rename from docs/fancybox/fancy_title_main.png
rename to src/site/fancybox/fancy_title_main.png
Binary files differ
diff --git a/docs/fancybox/fancy_title_over.png b/src/site/fancybox/fancy_title_over.png
similarity index 100%
rename from docs/fancybox/fancy_title_over.png
rename to src/site/fancybox/fancy_title_over.png
Binary files differ
diff --git a/docs/fancybox/fancy_title_right.png b/src/site/fancybox/fancy_title_right.png
similarity index 100%
rename from docs/fancybox/fancy_title_right.png
rename to src/site/fancybox/fancy_title_right.png
Binary files differ
diff --git a/docs/fancybox/fancybox-x.png b/src/site/fancybox/fancybox-x.png
similarity index 100%
rename from docs/fancybox/fancybox-x.png
rename to src/site/fancybox/fancybox-x.png
Binary files differ
diff --git a/docs/fancybox/fancybox-y.png b/src/site/fancybox/fancybox-y.png
similarity index 100%
rename from docs/fancybox/fancybox-y.png
rename to src/site/fancybox/fancybox-y.png
Binary files differ
diff --git a/docs/fancybox/fancybox.png b/src/site/fancybox/fancybox.png
similarity index 100%
rename from docs/fancybox/fancybox.png
rename to src/site/fancybox/fancybox.png
Binary files differ
diff --git a/docs/fancybox/jquery-1.4.3.min.js b/src/site/fancybox/jquery-1.4.3.min.js
similarity index 100%
rename from docs/fancybox/jquery-1.4.3.min.js
rename to src/site/fancybox/jquery-1.4.3.min.js
diff --git a/docs/fancybox/jquery.easing-1.3.pack.js b/src/site/fancybox/jquery.easing-1.3.pack.js
similarity index 100%
rename from docs/fancybox/jquery.easing-1.3.pack.js
rename to src/site/fancybox/jquery.easing-1.3.pack.js
diff --git a/docs/fancybox/jquery.fancybox-1.3.4.css b/src/site/fancybox/jquery.fancybox-1.3.4.css
similarity index 100%
rename from docs/fancybox/jquery.fancybox-1.3.4.css
rename to src/site/fancybox/jquery.fancybox-1.3.4.css
diff --git a/docs/fancybox/jquery.fancybox-1.3.4.js b/src/site/fancybox/jquery.fancybox-1.3.4.js
similarity index 100%
rename from docs/fancybox/jquery.fancybox-1.3.4.js
rename to src/site/fancybox/jquery.fancybox-1.3.4.js
diff --git a/docs/fancybox/jquery.fancybox-1.3.4.pack.js b/src/site/fancybox/jquery.fancybox-1.3.4.pack.js
similarity index 100%
rename from docs/fancybox/jquery.fancybox-1.3.4.pack.js
rename to src/site/fancybox/jquery.fancybox-1.3.4.pack.js
diff --git a/docs/fancybox/jquery.mousewheel-3.0.4.pack.js b/src/site/fancybox/jquery.mousewheel-3.0.4.pack.js
similarity index 100%
rename from docs/fancybox/jquery.mousewheel-3.0.4.pack.js
rename to src/site/fancybox/jquery.mousewheel-3.0.4.pack.js
diff --git a/src/site/faq.mkd b/src/site/faq.mkd
new file mode 100644
index 0000000..bf8055d
--- /dev/null
+++ b/src/site/faq.mkd
@@ -0,0 +1,179 @@
+## Troubleshooting
+
+### Eclipse/Egit/JGit complains that it "can't open upload pack"?
+There are a few ways this can occur:
+
+1. Are you running Java 7?<br />Java 7 introduced SNI support for SSL connections and it is enabled by default.<br />[Java 7 Security Enhancements](http://docs.oracle.com/javase/7/docs/technotes/guides/security/enhancements-7.html)<br />To disable SNI alerts, add this line to your eclipse.ini file and restart Eclipse.<br /><pre>-Djsse.enableSNIExtension=false</pre>
+2. You are using https with a self-signed certificate and you **did not** configure *http.sslVerify=false*
+    1. Window->Preferences->Team->Git->Configuration
+    2. Click the *New Entry* button
+    3. <pre>Key = <em>http.sslVerify</em>
+Value = <em>false</em></pre>
+3. Gitblit GO's default self-signed certificate is bound to *localhost* and you are trying to clone/push between machines.
+    1. Review the contents of `makekeystore.cmd`
+    2. Set *your hostname* in the *HOSTNAME* variable.
+    3. Execute the script.<br/>This will generate a new certificate and keystore for *your hostname* protected by *server.storePassword*. 
+4. The repository is clone-restricted and you don't have access.
+5. The repository is clone-restricted and your password changed.
+6. A regression in Gitblit.  :(
+
+### I can not push using git:// protocol on Windows using native Git
+
+This is a long-standing, known bug in the native Git for Windows implementation.
+
+Please see [this thread](https://groups.google.com/d/topic/msysgit/at8D7J-h7mw/discussion) for details.
+
+### Why can't I access Gitblit GO from another machine?
+1. Please check *server.httpBindInterface* and *server.httpsBindInterface* in `gitblit.properties`, you may be only be serving on *localhost*.
+2. Please see the above answer about "**can't open upload pack**".
+3. Ensure that any firewall you may have running on the Gitblit server either has an exception for your specified ports or for the running process.
+
+### How do I run Gitblit GO on port 80 or 443 in Linux?
+Linux requires root permissions to serve on ports < 1024.<br/>
+Run the server as *root* (security concern) or change the ports you are serving to 8080 (http) and/or 8443 (https). 
+
+### Gitblit GO does not list my repositories?!
+1. Confirm that the value *git.repositoriesFolder* in `gitblit.properties` actually points to your repositories folder.
+2. Confirm that the Gitblit GO process has full read-write-execute permissions to your *git.repositoriesFolder*. 
+
+### Gitblit WAR does not list my repositories?!
+1. Confirm that the &lt;context-param&gt; *git.repositoriesFolder* value in your `web.xml` file actually points to your repositories folder.
+2. Confirm that the servlet container process has full read-write-execute permissions to your *git.repositoriesFolder*.
+
+### Gitblit WAR will not authenticate any users?!
+Confirm that the &lt;context-param&gt; *realm.userService* value in your `web.xml` file actually points to a `users.conf` file.
+
+### Gitblit won't open my grouped repository (/group/myrepo.git) or browse my log/branch/tag/ref?!
+This is likely an url encoding/decoding problem with forward slashes:
+
+**bad**
+
+    http://192.168.1.2/log/myrepo.git/refs/heads/master
+
+**good**
+
+    http://192.168.1.2/log/myrepo.git/refs%2Fheads%2Fmaster
+
+**NOTE:**  
+You can not trust the url in the address bar of your browser since your browser may decode it for presentation.  When in doubt, *View Source* of the generated html to confirm the *href*.
+
+There are two possible workarounds for this issue.  In `gitblit.properties` or `web.xml`:
+
+1. try setting *web.mountParameters* to *false*.<br/>This changes the url scheme from mounted (*/commit/myrepo.git/abcdef*) to parameterized (*/commit/?r=myrepo.git&h=abcdef*).
+2. try changing *web.forwardSlashCharacter* to an asterisk or a **!**
+
+### Running Gitblit behind mod_proxy or some other proxy layer
+
+You must ensure that the proxy does not decode and then re-encode request urls with interpretation of forward-slashes (*%2F*).  If your proxy layer does re-encode embedded forward-slashes then you may not be able to browse grouped repositories or logs, branches, and tags **unless** you set *web.mountParameters=false*.
+
+If you are using Apache mod_proxy you may have luck with specifying [AllowEncodedSlashes NoDecode](http://httpd.apache.org/docs/2.2/mod/core.html#allowencodedslashes).
+
+### Running Gitblit on Tomcat
+
+Tomcat takes the extra precaution of [disallowing embedded slashes by default](http://tomcat.apache.org/security-6.html#Fixed_in_Apache_Tomcat_6.0.10).  This breaks Gitblit urls.  
+You have a few options on how to handle this scenario:
+
+1. [Tweak Tomcat](http://tomcat.apache.org/security-6.html#Fixed_in_Apache_Tomcat_6.0.10)  
+Add *-Dorg.apache.tomcat.util.buf.UDecoder.ALLOW_ENCODED_SLASH=true* to *CATALINA_OPTS* or to your JVM launch parameters
+2. *web.mountParameters = false* and use non-pretty, parameterized urls
+3. *web.forwardSlashCharacter = !* which tells Gitblit to use **!** instead of **/**
+
+#### UTF-8 Filenames
+
+Tomcat also dislikes urls with non-ASCII characters. If your repositories have non-ASCII filenames you will have to modify your connector properties to allow UTF-8 encoded urls.  
+
+[Tomcat Character Encoding](http://wiki.apache.org/tomcat/FAQ/CharacterEncoding)  
+[Tomcat Connector Properties](http://tomcat.apache.org/tomcat-6.0-doc/config/http.html)
+
+## General Interest Questions
+
+### Gitblit?  What kind of name is that?
+It's a phonetic play on [bitblt][bitblt] which is an image processing operation meaning *bit-block transfer*.
+
+### Why use Gitblit?
+It's a small tool that allows you to easily manage shared repositories and doesn't require alot of setup or git kung-foo.
+
+### Who is the target user for Gitblit?
+Small workgroups that require centralized repositories.
+
+Gitblit is not meant to be a social coding resource like [Github](http://github.com) or [Bitbucket](http://bitbucket.com) with 100s or 1000s of users.  Gitblit is designed to fulfill the same function as your centralized Subversion or CVS server.
+
+### Why does Gitblit exist when there is Git and Gitweb?
+As a Java developer I prefer that as much of my tooling as possible is Java.<br/>
+Originally, I was going to use [Mercurial](http://mercurial.selenic.com) but...
+
+- MercurialEclipse [shells to Python, writes to System.out, and captures System.in](http://mercurial.808500.n3.nabble.com/Hg4J-Mercurial-pure-Java-library-tp2693090p2694555.html)<br/>
+Parsing command-line output is fragile and suboptimal.<br/>Unfortunately this is necessary because Mercurial is an application, not a library.
+- Mercurial HTTP/HTTPS needs to run as CGI through Apache/IIS/etc, as mod_python through Apache, or served with a built-in http server.<br/>
+This requires setup and maintenance of multiple, mixed 3rd party components.
+
+Gitblit eliminates all that complication with its 100% Java stack and simple single configuration file.
+
+Additionally, Git and Gitweb do not offer repository creation or user management.
+
+### Do I need real Git?
+No (mostly).  Gitblit is based on [JGit][jgit] which is a pure Java implementation of the [Git version control system][git].<br/>
+Everything you need for Gitblit (except Java) is either bundled in the distribution file or automatically downloaded on execution.
+
+#### mostly
+JGit does not fully support the git-gc featureset (garbage collection) so you may want native Git to periodically run git-gc until [JGit][jgit] fully supports this feature.
+
+### Can I run Gitblit in conjunction with my existing Git tooling?
+Yes.
+
+### Do I need a JDK or can I use a JRE?
+Gitblit will run just fine with a JRE.  Gitblit can optionally use `keytool` from the JDK to generate self-signed certificates, but normally Gitblit uses [BouncyCastle][bouncycastle] for that need.
+
+### Does Gitblit use a database to store its data?
+No.  Gitblit stores its repository configuration information within the `.git/config` file and its user information in `users.conf` or whatever filename is configured in `gitblit.properties`.
+
+### Can I manually edit users.conf, gitblit.properties, or .git/config?
+Yes.  You can manually manipulate all of them and (most) changes will be immediately available to Gitblit.<br/>Exceptions to this are noted in `gitblit.properties`.
+
+**NOTE:**  
+Care must be taken to preserve the relationship between user roles and repository names.<br/>Please see the *User Roles* section of the [setup](/setup.html) page for details.
+
+### Can I restrict access to branches or paths within a repository?
+No, not out-of-the-box.  Access restrictions apply to the repository as a whole.
+
+Gitblit's simple authentication and authorization mechanism can be used to facilitate one or more of the [workflows outlined here](http://progit.org/book/ch5-1.html).
+
+Should you require more fine-grained access controls you might consider writing a Groovy *prereceive* script to block updating branch refs based on some permissions file.  I would be interested in a generic, re-usable script to include with Gitblit, should someone want to implement it.
+
+Alternatively, you could use [gitolite](https://github.com/sitaramc/gitolite) and SSH for your repository access.
+
+### Can I authenticate users against XYZ?
+Yes.  The user service is pluggable.  You may write your own complete user service by implementing the *com.gitblit.IUserService* interface.  Or you may subclass *com.gitblit.GitblitUserService* and override just the authentication. Set the fully qualified classname as the *realm.userService* property.
+
+### Why doesn't Gitblit support SSH?
+Gitblit could integrate [Apache Mina][mina] to provide SSH access.  However, doing so violates Gitblit's first design principle: [KISS](http://en.wikipedia.org/wiki/KISS_principle).<br/>
+SSH support requires creating, exchanging, and managing SSH keys (arguably not more complicated than managing users).  While this is possible, JGit's SmartHTTP implementation is a simpler and universal transport mechanism.
+
+You might consider running [Gerrit](http://gerrit.googlecode.org) which does integrate [Apache Mina][mina] and supports SSH or you might consider serving [Git][git] on Linux which would offer real SSH support and also allow use of [many other compelling Git solutions](https://git.wiki.kernel.org/index.php/InterfacesFrontendsAndTools).
+
+### What types of Search does Gitblit support?
+
+As of 0.9.0, Gitblit supports Lucene-based searching.
+
+If Lucene indexing is disabled, Gitblit falls back to brute-force commit-traversal search.  Commit-traversal search supports case-insensitive searching of *commit message* (default), *author*, and *committer*.<br/>
+
+To search by *author* or *committer* use the following syntax in the search box:
+
+    author: james
+    committer: james
+    
+Alternatively, you could enable the search type dropdown list in your `gitblit.properties` file.
+
+### Why did you call the setting federation.N.frequency instead of federation.N.period?!
+
+Yes, yes I know that you are really specifying the period, but Frequency sounds better to me.  :)
+
+### Can Gitblit be translated?
+
+Yes.  Most messages are localized to a standard Java properties file.
+
+[bitblt]: http://en.wikipedia.org/wiki/Bit_blit "Wikipedia Bitblt"
+[jgit]: http://eclipse.org/jgit "Eclipse JGit Site"
+[git]: http://git-scm.com "Official Git Site"
+[mina]: http://mina.apache.org "Apache Mina"
+[bouncycastle]: http://bouncycastle.org "The Legion of the Bouncy Castle"
diff --git a/src/site/features.mkd b/src/site/features.mkd
new file mode 100644
index 0000000..b9b44a5
--- /dev/null
+++ b/src/site/features.mkd
@@ -0,0 +1,82 @@
+## Standard Features (GO/WAR)
+- JGit http/https SmartHTTP servlet
+- JGit git protocol daemon
+- Menu driven native platform clone links for all popular Git clients
+- Browser and git client authentication
+- Four *per-repository* access restriction configurations with a Read-Only control flag
+    - ![anonymous](blank.png) *Anonymous View, Clone & Push*
+    - ![push](lock_go_16x16.png) *Authenticated Push*
+    - ![clone](lock_pull_16x16.png) *Authenticated Clone & Push*
+    - ![view](shield_16x16.png) *Authenticated View, Clone & Push*
+    - ![freeze](cold_16x16.png) Freeze repository (i.e. deny push, make read-only)
+- Six *per-user/team* repository access permissions
+    - **V** (view in web ui, RSS feeds, download zip)
+    - **R** (clone)
+    - **RW** (clone and push)
+    - **RWC** (clone and push with ref creation)
+    - **RWD** (clone and push with ref creation, deletion)
+    - **RW+** (clone and push with ref creation, deletion, rewind)
+- Optional feature to allow users to create personal repositories
+- Optional feature to fork a repository to a personal repository
+- Optional feature to create a repository on push
+- *Experimental* built-in Garbage Collection
+- Ability to federate with one or more other Gitblit instances
+- RSS/JSON RPC interface
+- Java/Swing Gitblit Manager tool
+- Gitweb inspired web UI
+- Responsive web UI that subtracts elements to be usable on phones, tablets, and desktop browsers
+- Groovy pre- and post- push hook scripts, per-repository or globally for all repositories
+- Email push notifications *(via sendmail.groovy push script)*
+- Lucene indexing of specified repository branches
+- Administrators may create, edit, rename, or delete repositories through the web UI or RPC interface
+- Administrators may create, edit, rename, or delete users through the web UI or RPC interface
+- Administrators may create, edit, rename, or delete teams through the web UI or RPC interface
+- Repository Owners may edit repositories through the web UI
+- Administrators and Repository Owners may set the default branch through the web UI or RPC interface
+- LDAP authentication and optional LDAP-controlled Team memberships
+- Redmine authentication
+- Salesforce.com authentication
+- Windows authentication
+- Gravatar integration
+- Git-notes display support
+- Submodule support
+- User-tracked reflog for pushes, tags, etc.
+- Fanout PubSub notifications service for self-hosted [Sparkleshare](http://sparkleshare.org) use
+- gh-pages display support (Jekyll is not supported)
+- Branch metrics (uses Google Charts)
+- HEAD and Branch RSS feeds
+- Blame annotations view
+- Dates can optionally be displayed using the browser's reported timezone
+- Display of Author and Committer email addresses can be disabled
+- Case-insensitive searching of commit messages, authors, or committers
+- Dynamic zip downloads feature
+- Markdown file view support
+- Syntax highlighting for popular source code types
+- Customizable regular expression substitution for commit messages (i.e. bug or code review link integration)
+- Single text file for users configuration
+- Optional utility pages
+    - ![docs](book_16x16.png) Docs page which enumerates all Markdown files within a repository
+    - ![tickets](bug_16x16.png) **readonly and deprecated** Ticgit ticket pages *(based on last MIT release bf57b032 2009-01-27)*
+- Translations
+    - English
+    - Japanese
+    - Spanish
+    - Polish
+    - Korean
+    - Brazilian Portuguese
+    - Dutch
+    - Chinese (zh_CN)
+
+## Gitblit GO Features
+- Out-of-the-box integrated stack requiring minimal configuration
+- Automatic generation of ssl certificate for https communications
+- Integrated GUI tool to facilitate x509 PKI including ssl and client certificate generation, client certificate revocation, and client certificate distribution
+- Single text file for configuring server and gitblit
+- A Windows service installation script and configuration tool
+- Built-in AJP connector for Apache httpd
+
+## Limitations
+- HTTP/HTTPS/GIT are the only supported Git protocols
+- Built-in access controls are not path-based, they are repository-based.
+
+[jgit]: http://eclipse.org/jgit "Eclipse JGit Site"
diff --git a/src/site/federation.mkd b/src/site/federation.mkd
new file mode 100644
index 0000000..756d3fc
--- /dev/null
+++ b/src/site/federation.mkd
@@ -0,0 +1,339 @@
+## Federating Gitblit
+
+*SINCE 0.6.0*
+
+A Gitblit federation is a mechanism to clone repositories and keep them in sync from one Gitblit instance to another.  Federation can be used to maintain a mirror of your Gitblit instance, to aggregate repositories from developer workstations, or to initially clone groups of repositories to developer workstations.  If you are/were a Subversion user you might think of this as [svn-sync](http://svnbook.red-bean.com/en/1.5/svn.ref.svnsync.html), but better.
+
+If your Gitblit instance allows federation and it is properly registered with another Gitblit instance, each of the *non-excluded* repositories of your Gitblit instance can be mirrored, in their entirety, to the pulling Gitblit instance.  You may optionally allow pulling of user accounts and backup of server settings.
+
+The federation feature should be considered a security backdoor and enabled or disabled as appropriate for your installation.<br/>
+Please review all the documentation to understand how it works and its limitations.
+
+![block diagram](fed_aggregation.png "Gitblit Federation Aggregation")
+
+### Important Changes to Note
+
+The *Gitblit 0.8.0* federation protocol adds retrieval of teams and referenced push scripts.  Older clients will not know to request team or push script information. 
+
+The *Gitblit 0.7.0* federation protocol is incompatible with the 0.6.0 federation protocol because of a change in the way timestamps are formatted.
+
+Gitblit 0.6.0 uses the default [google-gson](http://google-gson.googlecode.com) timestamp serializer which generates locally formatted timestamps.  Unfortunately, this creates problems for distributed repositories and distributed developers.  Gitblit 0.7.0 corrects this error by serializing dates to the [iso8601](http://en.wikipedia.org/wiki/ISO_8601) standard.  As a result 0.7.0 is not compatible with 0.6.0.  A partial backwards-compatibility fallback was considered but it would only work one direction and since the federation mechanism is bidirectional it was not implemented.
+
+### Origin Gitblit Instance Requirements
+
+- *git.enableGitServlet* must be true, all Git clone and pull requests are handled through Gitblit's JGit servlet
+- *federation.passphrase* must be non-empty
+- The Gitblit origin instance must be http/https accessible by the pulling Gitblit instance.<br/>That may require configuring port-forwarding on your router and/or opening ports on your firewall.
+
+#### federation.passphrase
+
+The passphrase is used to generate permission tokens that can be shared with other Gitblit instances.
+
+The passphrase value never needs to be shared, although if you give another Gitblit instance the *ALL* federation token then your passphrase will be transferred/backed-up along with all other server settings.
+
+This value can be anything you want: an integer, a sentence, an haiku, etc.  You should probably keep the passphrase simple and use standard Latin characters to prevent Java properties file encoding errors.  The tokens generated from this value are affected by case, so consider this value CASE-SENSITIVE.
+
+The federation feature is completely disabled if your passphrase value is empty.
+
+**NOTE**:  
+Changing your *federation.passphrase* will break any registrations you have established with other Gitblit instances.
+
+### Pulling Gitblit Instance Requirements
+
+ - consider setting *federation.allowProposals=true* to facilitate the registration process from origin Gitblit instances
+ - properly registered Gitblit instance including, at a minimum, url, *federation token*, and update frequency
+
+### Controlling What Gets Pulled
+
+If you want your repositories (and optionally users accounts and settings) to be pulled by another Gitblit instance, you need to register your origin Gitblit instance with a pulling Gitblit instance by providing the url of your Gitblit instance and a federation token.
+
+Gitblit generates the following standard federation tokens:
+---JAVA---
+String allToken = SHA1(passphrase + "-ALL");
+String usersAndRepositoriesToken = SHA1(passphrase + "-USERS_AND_REPOSITORIES");
+String repositoriesToken = SHA1(passphrase + "-REPOSITORIES");
+---JAVA---
+    
+The *ALL* token allows another Gitblit instance to pull all your repositories, user accounts, server settings, and referenced push scripts.  
+The *USERS_AND_REPOSITORIES* token allows another Gitblit instance to pull all your repositories and  user accounts.  
+The *REPOSITORIES* token only allows pulling of the repositories.
+
+Individual Gitblit repository configurations such as *description* and *accessRestriction* are always mirrored.
+
+If *federation.passphrase* has a non-empty value, the federation tokens are displayed in the log file and are visible, to administrators, in the web ui.
+
+The three standard tokens grant access to ALL your non-excluded repositories.  However, if you only want to specify different groups of repositories to be federated then you need to define *federation sets*. 
+
+#### Federation Sets
+
+Federation Sets (*federation.sets*) are named groups of repositories.  The Federation Sets are defined in `gitblit.properties` and are available for selection in the repository settings page.  You can assign a repository to one or more sets and then distribute the federation token for the set.  This allows you to grant federation pull access to a subset of your available repositories.  Tokens for federation sets only grant pull access for the member repositories.
+
+### Federation Proposals (Origin Gitblit Instance)
+
+Once you have properly setup your passphrase and can see your federation tokens, you are ready to share them with a pulling Gitblit instance.
+ 
+The registration process can be partially automated by sending a *federation proposal* to the pulling Gitblit instance.  
+To send a proposal:
+
+1. Login to your Gitblit instance as an administrator
+2. Select and click the *propose* link for the appropriate token on the *federation* page
+3. Confirm the publicly accessible url of your (origin) Gitblit instance
+4. Enter the url of the pulling Gitblit instance you want to federate with
+5. Optionally enter a message for the administrators
+6. Click *propose*
+
+Not all Gitblit instances accept *federation proposals*, there is a setting which allows Gitblit to outright reject them.  In this case an email or instant message to the administrator of the other Gitblit instance is required.  :)
+
+If your proposal is accepted, the proposal is cached to disk on the pulling Gitblit server and, if properly configured, the administrators of that Gitblit server will receive an email notification of your proposal.
+
+Your proposal includes:
+
+1. the url of your Gitblit instance
+2. the federation token you selected and its type
+3. the list of your *non-excluded* repositories, and their configuration details, that you propose to share
+
+Submitting a proposal does not automatically register your server with the pulling Gitblit instance.  
+Registration is a manual process for an administrator.
+
+### Federation Proposals (Pulling Gitblit Instance)
+
+If your Giblit instance has received a *federation proposal*, you will be alerted to that information the next time you login to Gitblit as an administrator.
+
+You may view the details of a proposal by scrolling down to the bottom of the repositories page and selecting a proposal.  Sample registration settings will be generated for you that you may copy & paste into either your `gitblit.properties` file or your `web.xml` file.
+
+### Excluding Repositories (Origin Gitblit Instance)
+
+You may exclude a repository from being pulled by any federated Gitblit instance by setting its *federation strategy* to EXCLUDE in the repository's settings page.
+
+### Excluding Repositories (Pulling Gitblit Instance)
+
+You may exclude repositories to pull in a federation registration.  You may exclude all or you may exclude based on a simple fuzzy pattern.  Only one wildcard character may be used within each pattern.  Patterns are space-separated within the exclude and include fields. 
+
+    federation.example.exclude = skipit.git
+
+**OR**
+
+    federation.example.exclude = *
+    federation.example.include = somerepo.git someotherrepo.git
+
+**OR**
+
+    federation.example.exclude = *
+    federation.example.include = common/* library/*
+    
+### Tracking Status (Pulling Gitblit Instance)
+
+Below the repositories list on the repositories page you will find a section named *federation registrations*.  This section enumerates the other gitblit servers you have configured to periodically pull.  The *status* of the latest pull will be indicated on the left with a colored circle, similar to the status of an executed unit test or Hudson/Jenkins build.  You can drill into the details of the registration to view the status of the last pull from each repository available from that origin Gitblit instance.  Additionally, you can specify the *federation.N.notifyOnError=true* flag, to be alerted via email of regressive status changes to individual registrations.
+
+### Tracking Status (Origin Gitblit Instance)
+
+Origin Gitblit instances can not directly track the success or failure status of Pulling Gitblit instances.  However, the Pulling Gitblit instance may elect to send a status acknowledgment (*federation.N.sendStatus=true*) to the origin Gitblit server that indicates the per-repository status of the pull operation.  This is the same data that is displayed on the Pulling Gitblit instances ui.
+
+### How does it work? (Origin Gitblit Instances)
+
+A pulling Gitblit instance will periodically contact your Gitblit instance and will provide the token as proof that you have granted it federation access.  Your Gitblit instance will decide, based on the supplied token, if the requested data should be returned to the pulling Gitblit instance.  Gitblit data (user accounts, repository metadata, and server settings) are serialized as [JSON](http://json.org) using [google-gson](http://google-gson.googlecode.com) and returned to the pulling Gitblit instance.  Standard Git clone and pull operations are used to transfer commits.
+
+The federation process executes using an internal administrator account, *$gitblit*.  All the normal authentication and authorization processes are used for federation requests. For example, Git commands are authenticated as *$gitblit / token*.
+
+While the *$gitblit* account has access to all repositories, server settings, and user accounts, it is prohibited from accessing the web ui and it is disabled if *federation.passphrase* is empty.
+
+### How does it work? (Pulling Gitblit Instances)
+
+Federated repositories defined in `gitblit.properties` are checked after Gitblit has been running for 1 minute.  The next registration check is scheduled at the completion of the current registration check based on the registration's specified frequency.
+
+- The shortest frequency allowed is every 5 minutes
+- Decimal frequency values are cast to integers
+- Frequency values may be specified in mins, hours, or days
+- Values that can not be parsed default to 60 minutes
+
+After a repository has been cloned it is flagged as *isFederated* (which identifies it as being sourced from another Gitblit instance), *isFrozen* (which prevents Git pushes to this mirror) and *federationStrategy=EXCLUDED* (which prevents this repository from being pulled by another federated Gitblit instance).
+
+#### Origin Verification
+
+During a federated pull operation, Gitblit does check that the *origin* of the local repository starts with the url of the federation registration.  
+If they do not match, the repository is skipped and this is indicated in the log.
+
+#### User Accounts & Teams
+
+By default all user accounts and teams (except the *admin* account) are automatically pulled when using the *ALL* token or the *USERS_AND_REPOSITORIES* token.  You may exclude a user account from being pulled by a federated Gitblit instance by checking *exclude from federation* in the edit user page.
+
+The pulling Gitblit instance will store a registration-specific `users.conf` file for the pulled user accounts and their repository permissions. This file is stored in the *federation.N.folder* folder.
+
+If you specify *federation.N.mergeAccounts=true*, then the user accounts and team definitions from the origin Gitblit instance will be integrated into the `users.conf` file of your Gitblit instance and allow sign-on of those users.
+
+**NOTE:**  
+Upgrades from older Gitblit versions will not have the *#notfederated* role assigned to the *admin* account.  Without that role, your admin account WILL be transferred with an *ALL* or *USERS_AND_REPOSITORIES* token.  
+Please consider adding the *#notfederated* role to your admin account!
+
+#### Server Settings 
+
+Server settings are only pulled when using the *ALL* token.
+
+The pulling Gitblit instance will store a registration-specific `gitblit.properties` file for all pulled settings.  This file is stored in the *federation.N.folder* folder.
+
+These settings are unused by the pulling Gitblit instance.
+
+#### Push Scripts 
+
+Your Groovy push scripts are only pulled when using the *ALL* token.
+
+The pulling Gitblit instance will retrieve any referenced (i.e. used) push script and store it locally as *registration_scriptName.groovy* in the *federation.N.folder* folder.
+
+These scripts are unused by the pulling Gitblit instance.
+
+### Collisions and Conflict Resolution
+
+Gitblit does **not** detect conflict and it does **not** offer conflict resolution of repositories, users, teams, or settings.
+
+If an object exists locally that has the same name as the remote object, it is assumed they are the same and the contents of the remote object are merged into the local object.  If you can not guarantee that this is the case, then you should not store any federated repositories directly in *git.repositoriesFolder* and you should not enable *mergeAccounts*.
+
+By default, federated repositories can not be pushed to, they are read-only by the *isFrozen* flag.  This flag is **ONLY** enforced by Gitblit's JGit servlet.  If you push to a federated repository after resetting the *isFrozen* flag or via some other Git access technique then you may break Gitblit's ability to continue pulling from the origin repository.  If you are only pushing to a local branch then you might be safe.
+
+## Federation Pull Registration Keys
+
+<table class="table">
+<tr><th>federation.N.url</th>
+<td>string</td>
+<td>the url of the origin Gitblit instance <em>(required)</em></td>
+</tr>
+
+<tr><th>federation.N.token</th>
+<td>string</td>
+<td>the token provided by the origin Gitblit instance <em>(required)</em></td>
+</tr>
+
+<tr><th>federation.N.frequency</th>
+<td>x [mins/hours/days]</td>
+<td>the period to wait between pull executions</td>
+</tr>
+
+<tr><th>federation.N.folder</th>
+<td>string</td>
+<td>the destination folder, relative to <em>git.repositoriesFolder</em>, for these repositories.<br/>default is <em>git.repositoriesFolder</em></td>
+</tr>
+
+<tr><th>federation.N.bare</th>
+<td>boolean</td>
+<td>if <b>true</b> <em>(default)</em>, each repository is cloned as a bare repository (i.e. no working folder).</td>
+</tr>
+
+<tr><th>federation.N.mirror</th>
+<td>boolean</td>
+<td>if <b>true</b> <em>(default)</em>, each repository HEAD is reset to <em>origin/master</em> after each pull.  The repository is flagged <em>isFrozen</em> after the initial clone.<br/><br/>If <b>false</b>, each repository HEAD will point to the FETCH_HEAD of the initial clone from the origin until pushed to or otherwise manipulated.</td>
+</tr>
+
+<tr><th>federation.N.mergeAccounts</th>
+<td>boolean</td>
+<td>if <b>true</b>, merge the retrieved accounts into the <code>users.conf</code> of <b>this</b> Gitblit instance.<br/><em>default is false</em></td>
+</tr>
+
+<tr><th>federation.N.sendStatus</th>
+<td>boolean</td>
+<td>if <b>true</b>, send the status of the federated pull to the origin Gitblit instance.<br/><em>default is false</em></td>
+</tr>
+
+<tr><th>federation.N.include</th>
+<td>string array<br/>(space-delimited)</td>
+<td>list of included repositories <em>(wildcard and fuzzy matching supported)</em></td>
+</tr>
+
+<tr><th>federation.N.exclude</th>
+<td>string array<br/>(space-delimited)</td>
+<td>list of excluded repositories <em>(wildcard and fuzzy matching supported)</em></td>
+</tr>
+
+<tr><th>federation.N.notifyOnError</th>
+<td>boolean</td>
+<td>if <b>true</b>, send an email to the administrators on an error.<br/><em>default is false</em></td>
+</tr>
+</table>
+
+## Example Federation Pull Registrations
+
+These examples would be entered into the `gitblit.properties` file of the pulling gitblit instance.
+
+#### (Nearly) Perfect Mirror Example
+
+![block diagram](fed_mirror.png "Gitblit Mirror")
+
+This assumes that the *token* is the *ALL* token from the origin gitblit instance.
+
+The repositories, example1_users.conf, example1_gitblit.propertiesn and all example1_scripts.groovy will be put in *git.repositoriesFolder* and the origin user accounts will be merged into the local user accounts, including passwords and all roles.  The Gitblit instance will also send a status acknowledgment to the origin Gitblit instance at the end of the pull operation.  The status report will include the state of each repository pull (EXCLUDED, SKIPPED, NOCHANGE, PULLED, MIRRORED).  This way the origin Gitblit instance can monitor the health of its mirrors.
+
+This example is considered *nearly* perfect because while the origin Gitblit's server settings & push scripts are pulled and saved locally, they are not merged with your server settings so its not a true mirror.
+
+    federation.example1.url = https://go.gitblit.com
+    federation.example1.token = 6f3b8a24bf970f17289b234284c94f43eb42f0e4
+    federation.example1.frequency = 120 mins
+    federation.example1.folder =
+    federation.example1.bare = true 
+    federation.example1.mirror = true
+    federation.example1.mergeAccounts = true
+    federation.example1.sendStatus = true
+    
+#### Just Repositories Example
+
+This assumes that the *token* is the *REPOSITORIES* token from the origin gitblit instance.  
+The repositories will be put in *git.repositoriesFolder*/example2.
+
+    federation.example2.url = https://tomcat.gitblit.com/gitblit
+    federation.example2.token = 6f3b8a24bf970f17289b234284c94f43eb42f0e4
+    federation.example2.frequency = 120 mins
+    federation.example2.folder = example2
+    federation.example2.bare = true
+    federation.example2.mirror = true
+    
+#### All-but-One Repository Example
+
+This assumes that the *token* is the *REPOSITORIES* token from the origin gitblit instance.  
+The repositories will be put in *git.repositoriesFolder*/example3.
+
+    federation.example3.url = https://tomcat.gitblit.com/gitblit
+    federation.example3.token = 6f3b8a24bf970f17289b234284c94f43eb42f0e4
+    federation.example3.frequency = 120 mins
+    federation.example3.folder = example3
+    federation.example3.bare = true
+    federation.example3.mirror = true
+    federation.example3.exclude = somerepo.git
+    
+#### Just One Repository Example
+
+This assumes that the *token* is the *REPOSITORIES* token from the origin gitblit instance.  
+The repositories will be put in *git.repositoriesFolder*/example4.
+
+    federation.example4.url = https://tomcat.gitblit.com/gitblit
+    federation.example4.token = 6f3b8a24bf970f17289b234284c94f43eb42f0e4
+    federation.example4.frequency = 120 mins
+    federation.example4.folder = example4
+    federation.example4.bare = true
+    federation.example4.mirror = true
+    federation.example4.exclude = *
+    federation.example4.include = somerepo.git
+    
+## Federation Client
+
+Instead of setting up a full-blown pulling Gitblit instance, you can also use the [federation client](http://code.google.com/p/gitblit/downloads/detail?name=%FEDCLIENT%) command-line utility.  This is a packaged subset of the federation feature in a smaller, simpler command-line only tool.
+
+The *federation client* relies on many of the same dependencies as Gitblit and will download them on first execution.
+
+### federation.properties
+You may use the `federation.properties` file to configure one or more Gitblit instances that you want to pull from.  This file is a subset of the standard `gitblit.properties` file.
+
+By default this tool does not daemonize itself; it executes and then quits.  This allows you to use the native scheduling feature of your OS.  Of course, if you'd rather use Gitblit's scheduler you may use that by specifying the `--daemon` parameter.
+
+### http.sslVerify
+
+If you are pulling from a Gitblit with a self-signed SSL certificate you will need to configure Git/JGit to bypass certificate verification.  
+([Git-Config Manual Page](http://www.kernel.org/pub/software/scm/git/docs/git-config.html))  
+
+<pre>git config --global --bool --add http.sslVerify false</pre>
+
+### Command-Line Parameters
+Instead of using `federation.properties` you may directly specify a Gitblit instance to pull from with command-line parameters.
+
+    java -jar fedclient.jar --url https://go.gitblit.com --mirror --bare --token 123456789
+         --repositoriesFolder c:/mymirror
+    
+    java -jar fedclient.jar --url https://go.gitblit.com --mirror --bare --token 123456789
+         --repositoriesFolder c:/mymirror --daemon --frequency "24 hours"
+    
diff --git a/docs/federation.odg b/src/site/federation.odg
similarity index 100%
rename from docs/federation.odg
rename to src/site/federation.odg
Binary files differ
diff --git a/docs/gitblit_logo_white.xcf b/src/site/gitblit_logo_white.xcf
similarity index 100%
rename from docs/gitblit_logo_white.xcf
rename to src/site/gitblit_logo_white.xcf
Binary files differ
diff --git a/distrib/openshift.mkd b/src/site/openshift.mkd
similarity index 100%
rename from distrib/openshift.mkd
rename to src/site/openshift.mkd
diff --git a/docs/permissions_matrix.ods b/src/site/permissions_matrix.ods
similarity index 100%
rename from docs/permissions_matrix.ods
rename to src/site/permissions_matrix.ods
Binary files differ
diff --git a/docs/03_properties.mkd b/src/site/properties.mkd
similarity index 100%
rename from docs/03_properties.mkd
rename to src/site/properties.mkd
diff --git a/src/site/releasecurrent.mkd b/src/site/releasecurrent.mkd
new file mode 100644
index 0000000..613f1bf
--- /dev/null
+++ b/src/site/releasecurrent.mkd
@@ -0,0 +1,67 @@
+## Current Release (${project.releaseVersion})
+
+<div class="alert alert-info">
+<h4>Update Note ${project.releaseVersion}</h4>
+Because there are now several types of files and folders that must be considered Gitblit data, the default location for data has changed.
+<p>You will need to move a few files around when upgrading.  Please see the Upgrading section of the <a href="setup.html">setup</a> page for details.</p>
+
+<b>Express Users</b> make sure to update your web.xml file with the ${baseFolder} values!
+</div>
+
+**downloads:** [GO Windows](%GCURL%gitblit-${project.releaseVersion}.zip) | [GO Linux/OSX](%GCURL%gitblit-${project.releaseVersion}.tar.gz) | [WAR](%GCURL%gitblit-${project.releaseVersion}.war) | [Express](%GCURL%express-${project.releaseVersion}.zip) | [Federatoin Client](%GCURL%fedclient-${project.releaseVersion}.zip) | [Gitblit Manager](%GCURL%manager-${project.releaseVersion}.zip) | [API Library](%GCURL%gbapi-${project.releaseVersion}.zip) &nbsp; *released ${project.releaseDate}*
+
+#### fixes
+
+- Fixed nullpointer on recursively calculating folder sizes when there is a named pipe or symlink in the hierarchy
+- Added nullchecking when concurrently forking a repository and trying to display it's fork network (issue-187)
+- Fixed bug where permission changes were not visible in the web ui to a logged-in user until the user logged-out and then logged back in again (issue-186)
+- Fixed nullpointer on creating a repository with mixed case (issue 185)
+- Include missing model classes in api library (issue-184)
+- Fixed nullpointer when using *web.allowForking = true* && *git.cacheRepositoryList = false* (issue 182)
+- Likely fix for commit and commitdiff page failures when a submodule reference changes (issue 178)
+- Build project models from the repository model cache, when possible, to reduce page load time (issue 172)
+- Fixed loading of Brazilian Portuguese translation from *nix server (github/inaiat)
+
+#### additions
+
+- Fanout PubSub service for self-hosted [Sparkleshare](http://sparkleshare.org) notifications.<br/>
+This service is disabled by default.<br/>
+    **New:** *fanout.bindInterface = localhost*<br/>
+	**New:** *fanout.port = 0*<br/>
+	**New:** *fanout.useNio = true*<br/>
+	**New:** *fanout.connectionLimit = 0*
+- Implemented a simple push log based on a hidden, orphan branch refs/gitblit/pushes (issue 177)<br/>
+The push log is not currently visible in the ui, but the data will be collected and it will be exposed to the ui in the next release.
+- Support for locally and remotely authenticated accounts in LdapUserService and RedmineUserService (issue 183)
+- Added Dutch translation (github/kwoot)
+
+#### changes
+
+- Gitblit GO and Gitblit WAR are now both configured by `gitblit.properties`. WAR is no longer configured by `web.xml`.<br/>
+However, Express for OpenShift continues to be configured by `web.xml`.
+- Support for a *--baseFolder* command-line argument for Gitblit GO and Gitblit Certificate Authority
+- Support for specifying a *${baseFolder}* parameter in `gitblit.properties` and `web.xml` for several settings
+- Improve history display of a submodule link
+- Updated Korean translation (github/ds5apn)
+- Updated checkstyle definition (github/mystygage)
+
+<div style="padding-top:20px;" />
+
+## Next Release (${project.version})
+
+<div class="alert alert-info">
+<h4>Update Note ${project.version}</h4>
+These are the fixes, changes, and additions queued for the next release.
+</div>
+
+#### fixes
+
+- Can't set reset settings with $ or { characters through Gitblit Manager because they are not properly escaped
+
+#### additions
+ 
+ - FogBugz post-receive hook script (github/djschny)
+ - Implemented multiple repository owners (github/akquinet)
+ - Chinese translation (github/dapengme, github/yin8086)
+
+[jgit]: http://eclipse.org/jgit "Eclipse JGit Site"
diff --git a/src/site/releasehistory.mkd b/src/site/releasehistory.mkd
new file mode 100644
index 0000000..7ea7676
--- /dev/null
+++ b/src/site/releasehistory.mkd
@@ -0,0 +1,488 @@
+## Older Releases
+
+<div class="alert alert-info">
+<h4>Update Note 1.2.0</h4>
+The permissions model has changed in the 1.2.0 release.
+<p>If you are updating your server, you must also update any Gitblit Manager and Federation Client installs to 1.2.0 as well.  The data model used by the RPC mechanism has changed slightly for the new permissions infrastructure.</p>
+</div>
+
+**1.2.0** *released 2012-12-31*
+
+#### fixes
+
+- Fixed regression in *isFrozen* (issue 181)
+- Author metrics can be broken by newlines in email addresses from converted repositories (issue 176)
+- Set subjectAlternativeName on generated SSL cert if CN is an ip address (issue 170)
+- Fixed incorrect links on history page for files not in the current/active commit (issue 166)
+- Empty repository page failed to handle missing repository (issue 160)
+- Fixed broken ticgit urls (issue 157)
+- Exclude submodules from zip downloads (issue 151)
+- Fixed bug where repository ownership was not updated on rename user
+- Fixed bug in create/rename repository if you explicitly specified the alias for the root group (e.g. main/myrepo) (issue 143)
+- Wrapped Markdown parser with improved exception handler (issue 142)
+- Fixed duplicate entries in repository cache (issue 140)
+- Fixed connection leak in LDAPUserService (issue 139)
+- Fixed bug in commit page where changes to a submodule threw a null pointer exception (issue 132)
+- Fixed bug in the diff view for filenames that have non-ASCII characters (issue 128)
+
+#### additions
+
+- Implemented discrete repository permissions (issue 36)
+    - V (view in web ui, RSS feeds, download zip)
+    - R (clone)
+    - RW (clone and push)
+    - RWC (clone and push with ref creation)
+    - RWD (clone and push with ref creation, deletion)
+    - RW+ (clone and push with ref creation, deletion, rewind)
+While not as sophisticated as Gitolite, this does give finer access controls.  These permissions fit in cleanly with the existing users.conf and users.properties files.  In Gitblit <= 1.1.0, all your existing user accounts have RW+ access.   If you are upgrading to 1.2.0, the RW+ access is *preserved* and you will have to lower/adjust accordingly.
+- Implemented *case-insensitive* regex repository permission matching (issue 36)<br/>
+This allows you to specify a permission like `RW:mygroup/.*` to grant push privileges to all repositories within the *mygroup* project/folder.
+- Added DELETE, CREATE, and NON-FAST-FORWARD ref change logging
+- Added support for personal repositories.<br/>
+Personal repositories can be created by accounts with the *create* permission and are stored in *git.repositoriesFolder/~username*.  Each user with personal repositories will have a user page, something like the GitHub profile page.  Personal repositories have all the same features as common repositories, except personal repositories can be renamed by their owner.
+- Added support for server-side forking of a repository to a personal repository (issue 137)<br/>
+In order to fork a repository, the user account must have the *fork* permission **and** the repository must *allow forks*.  The clone inherits the access list of its origin.  i.e. if Team A has clone access to the origin repository, then by default Team A also has clone access to the fork.  This is to facilitate collaboration.  The fork owner may change access to the fork and add/remove users/teams, etc as required <u>however</u> it should be noted that all personal forks will be enumerated in the fork network regardless of access view restrictions.  If you really must have an invisible fork, the clone it locally, create a new repository for your invisible fork, and push it back to Gitblit.<br/>
+    **New:** *web.allowForking=true*
+- Added optional *create-on-push* support<br/>
+    **New:** *git.allowCreateOnPush=true*
+- Added **experimental** JGit-based garbage collection service.  This service is disabled by default.<br/>
+    **New:** *git.allowGarbageCollection=false*<br/>
+    **New:** *git.garbageCollectionHour = 0*<br/>
+    **New:** *git.defaultGarbageCollectionThreshold = 500k*<br/>
+    **New:** *git.defaultGarbageCollectionPeriod = 7 days*
+- Added support for X509 client certificate authentication (github/kevinanderson1).  (issue 106)<br/>
+You can require all git servlet access be authenticated by a client certificate.  You may also specify the OID fingerprint to use for mapping a certificate to a username.  It should be noted that the user account MUST already exist in Gitblit for this authentication mechanism to work; this mechanism can not be used to automatically create user accounts from a certificate.<br/>
+    **New:** *git.requireClientCertificates = false*<br/>
+    **New:** *git.enforceCertificateValidity = true*<br/>
+    **New:** *git.certificateUsernameOIDs = CN*
+- Revised clean install certificate generation to create a Gitblit GO Certificate Authority certificate; an SSL certificate signed by the CA certificate; and to create distinct server key and server trust stores.  <u>The store files have been renamed!</u>
+- Added support for Gitblit GO to require usage of client certificates to access the entire server.<br/>
+This is extreme and should be considered carefully since it affects every https access.  The default is to **want** client certificates.  Setting this value to *true* changes that to **need** client certificates.<br/>
+    **New:** *server.requireClientCertificates = false*
+- Added **Gitblit Certificate Authority**, an x509 PKI management tool for Gitblit GO to encourage use of x509 client certificate authentication.
+- Added setting to control length of shortened commit ids<br/>
+    **New:** *web.shortCommitIdLength=8*
+- Added alternate compressed download formats: tar.gz, tar.xz, tar.bzip2 (issue 174)<br/>
+    **New:** *web.compressedDownloads = zip gz*
+- Added simple project pages.  A project is a subfolder off the *git.repositoriesFolder*.
+- Added support for X-Forwarded-Context for Apache subdomain proxy configurations (issue 135)
+- Delete branch feature (issue 121, Github/ajermakovics)
+- Added line links to blob view (issue 130)
+- Added HTML sendmail hook script and Gitblit.sendHtmlMail method (github/sauthieg)
+- Added RedmineUserService (github/mallowlabs)
+- Support for committer verification.  Requires use of *--no-ff* when merging branches or pull requests.  See setup page for details.
+- Added Brazilian Portuguese translation (github/rafaelcavazin)
+
+#### changes
+
+- Added server setting to specify keystore alias for ssl certificate (issue 98)
+- Added optional global and per-repository activity page commit contribution throttle to help tame *really* active repositories (issue 173)
+- Added support for symlinks in tree page and commit page (issue 171)
+- All access restricted servlets (e.g. DownloadZip, RSS, etc) will try to authenticate using X509 certificates, container principals, cookies, and BASIC headers, in that order.
+- Added *groovy* and *scala* to *web.prettyPrintExtensions*
+- Added short commit id column to log and history tables (issue 168)
+- Teams can now specify the *admin*, *create*, and *fork* roles to simplify user administration
+- Use https Gravatar urls to avoid browser complaints
+- Added frm to default pretty print extensions (issue 156)
+- Expose ReceivePack to Groovy push hooks (issue 125)
+- Redirect to summary page when refreshing the empty repository page on a repository that is not empty (issue 129)
+- Emit a warning in the log file if running on a Tomcat-based servlet container which is unfriendly to %2F forward-slash url encoding AND Gitblit is configured to mount parameters with %2F forward-slash url encoding (Github/jpyeron, issue 126)
+- LDAP admin attribute setting is now consistent with LDAP teams setting and admin teams list.
+If *realm.ldap.maintainTeams==true* **AND** *realm.ldap.admins* is not empty, then User.canAdmin() is controlled by LDAP administrative team membership.  Otherwise, User.canAdmin() is controlled by Gitblit.
+- Support servlet container authentication for existing UserModels (issue 68)
+
+#### dependency changes
+
+- updated to Jetty 7.6.8
+- updated to JGit 2.2.0.201212191850-r
+- updated to Groovy 1.8.8
+- updated to Wicket 1.4.21
+- updated to Lucene 3.6.1
+- updated to BouncyCastle 1.47
+- updated to MarkdownPapers 1.3.2
+- added JCalendar 1.3.2
+- added Commons-Compress 1.4.1
+- added XZ for Java 1.0
+<hr/>
+
+<div class="alert alert-error">
+<h4>Update Note 1.1.0</h4>
+If you are updating from an earlier release AND you have indexed branches with the Lucene indexing feature, you need to be aware that this release will completely re-index your repositories.  Please be sure to provide ample heap resources as appropriate for your installation.
+</div>
+
+**1.1.0** *released 2012-08-25*
+
+#### fixes
+
+- Bypass Wicket's inability to handle direct url addressing of a view-restricted, grouped repository for new, unauthenticated sessions (e.g. click link from email or rss feed without having an active Wicket session)
+- Fixed MailExecutor's failure to cope with mail server connection troubles resulting in 100% CPU usage
+- Fixed generated urls in Groovy *sendmail* hook script for grouped repositories
+- Fixed generated urls in RSS feeds for grouped repositories
+- Fixed nullpointer exception in git servlet security filter (issue 123)
+- Eliminated an unnecessary repository enumeration call on the root page which should result in faster page loads (issue 103)
+- Gitblit could not delete a Lucene index in a working copy on index upgrade
+- Do not index submodule links (issue 119)
+- Restore original user or team object on failure to update (issue 118)
+- Fixes to relative path determination in repository search algorithm for symlinks (issue 116)
+- Fix to GitServlet to allow pushing to symlinked repositories (issue 116)
+- Repository URL now uses `X-Forwarded-Proto` and `X-Forwarded-Port`, if available, for reverse proxy configurations (issue 115)
+- Output real RAW content, not simulated RAW content (issue 114)
+- Fixed Lucene charset encoding bug when reindexing a repository (issue 112)
+- Fixed search box linking to Lucene page for grouped repository on Tomcat (issue 111)
+- Fixed null pointer in LdapUserSerivce if account has a null email address (issue 110)
+- Really fixed failure to update a GO setting from the manager (issue 85)
+
+#### additions
+
+- Identified repository list is now cached by default to reduce disk io and to improve performance (issue 103)<br/>
+    **New:** *git.cacheRepositoryList=true*
+- Preliminary bare repository submodule support<br/>
+    **New:** *git.submoduleUrlPatterns=*
+    - *git.submoduleUrlPatterns* is a space-delimited list of regular expressions for extracting a repository name from a submodule url.<br/>
+    For example, `git.submoduleUrlPatterns = .*?://github.com/(.*)` would extract *gitblit/gitblit.git* from *git://github.git/gitblit/gitblit.git*<br/>
+    **Note:** You may not need this control to work with submodules, but it is there if you do.
+    - If there are no matches from *git.submoduleUrlPatterns* then the repository name is assumed to be whatever comes after the last `/` character *(e.g. gitblit.git)*
+    - Gitblit will try to locate this repository relative to the current repository *(e.g. myfolder/myrepo.git, myfolder/mysubmodule.git)* and then at the root level *(mysubmodule.git)* if that fails.
+    - Submodule references in a working copy will be properly identified as gitlinks, but Gitblit will not traverse into the working copy submodule repository.
+- Added a repository setting to control authorization as AUTHENTICATED or NAMED. (issue 117)<br/>
+NAMED is the original behavior for authorizing against a list of permitted users or permitted teams.
+AUTHENTICATED allows restricted access for any authenticated user.  This is a looser authorization control.
+- Added default authorization control setting (AUTHENTICATED or NAMED)<br/>
+    **New:** *git.defaultAuthorizationControl=NAMED*
+- Added setting to control how deep Gitblit will recurse into *git.repositoriesFolder* looking for repositories (issue 103)<br/>
+    **New:** *git.searchRecursionDepth=-1*
+- Added setting to specify regex exclusions for repositories (issue 103)<br/>
+    **New:** *git.searchExclusions=*
+- Blob page now supports displaying images (issue 6)
+- Non-image binary files can now be downloaded using the RAW link
+- Support StartTLS in LdapUserService (Steffen Gebert, issue 122)
+- Added Korean translation
+
+#### changes
+
+- Line breaks inserted for readability in raw Markdown content display in the event of a parsing/transformation error.  An error message is now displayed prepended to the raw content.
+- Improve UTF-8 reading for Markdown files
+- Updated Polish translation
+- Updated Japanese translation
+- Updated Spanish translation
+
+<hr/>
+
+**1.0.0** *released 2012-07-14*
+
+#### fixes
+
+- Fixed bug in Lucene search where old/stale blobs were never properly deleted during incremental updates.  This resulted in duplicate blob entries in the index.
+- Fixed intermittent bug in identifying line numbers in Lucene search (issue 105)
+- Adjust repository identification algorithm to handle the scenario where a repository name collides with a group/folder name (e.g. foo.git and foo/bar.git) (issue 104)
+- Fixed bug where a repository set as *authenticated push* did not have anonymous clone access (issue 96)
+- Fixed bug in Basic authentication if passwords had a colon (Github/peterloron)
+- Fixed bug where the Gitblit Manager could not update a setting that was not referenced in reference.properties (issue 85)
+
+#### changes
+
+- **Updated Lucene index version which will force a rebuild of ALL your Lucene indexes**<br/>
+Make sure to properly set *web.blobEncodings* before starting Gitblit if you are updating!  (issue 97)
+- Changed default layout for web ui from Fixed-Width layout to Responsive layout (issue 101)
+- IUserService interface has changed to better accomodate custom authentication and/or custom authorization<br/>
+    The default `users.conf` now supports persisting display names and email addresses.
+- Updated Japanese translation (Github/zakki)
+
+#### additions
+
+- Added setting to allow specification of a robots.txt file (issue 99)<br/>
+    **New:** *web.robots.txt =*
+- Added setting to control Responsive layout or Fixed-Width layout (issue 101)<br/>
+    Responsive layout is now the default.  This layout gracefully scales the web ui from a desktop layout to a mobile layout by hiding page components.  It is easy to try, just resize your browser or point your Android/iOS device to the url of your Gitblit install.
+    **New:** *web.useResponsiveLayout = true*
+- Added setting to control charsets for blob string decoding.  Default encodings are UTF-8, ISO-8859-1, and server's default charset. (issue 97)<br/>
+    **New:** *web.blobEncodings = UTF-8 ISO-8859-1*
+- Exposed JGit's internal configuration settings in gitblit.properties/web.xml (issue 93)<br/>
+    Review your `gitblit.properties` or `web.xml` for detailed explanations of these settings.<br/>
+    **New:** *git.packedGitWindowSize = 8k*<br/>
+    **New:** *git.packedGitLimit = 10m*<br/>
+    **New:** *git.deltaBaseCacheLimit = 10m*<br/>
+    **New:** *git.packedGitOpenFiles = 128*<br/>
+    **New:** *git.streamFileThreshold = 50m*<br/>
+    **New:** *git.packedGitMmap = false*
+- Added default access restriction.  Applies to new repositories and repositories that have not been configured with Gitblit. (issue 88)<br/>
+    **New:** *git.defaultAccessRestriction = NONE*
+- Added Ivy 2.2.0 dependency which enables Groovy Grapes, a mechanism to resolve and retrieve library dependencies from a Maven 2 repository within a Groovy push hook script
+- Added setting to control Groovy Grape root folder (location where resolved dependencies are stored)<br/>
+    [Grape](http://groovy.codehaus.org/Grape) allows you to add Maven dependencies to your pre-/post-receive hook script classpath.<br/>
+    **New:** *groovy.grapeFolder = groovy/grape*
+- Added LDAP User Service with many new *realm.ldap* keys (Github/jcrygier)
+- Added support for custom repository properties for Groovy hooks (Github/jcrygier)<br/>
+    Custom repository properties complement hook scripts by providing text field prompts in the web ui and the Gitblit Manager for the defined properties.  This allows your push hooks to be parameterized.
+- Added script to facilitate proxy environment setup on Linux (Github/mragab)
+- Added Polish translation (Lukasz Jader)
+- Added Spanish translation (Eduardo Guervos Narvaez)
+
+#### dependency changes
+
+- updated to Bootstrap 2.0.4
+- updated to JGit 2.0.0.201206130900-r
+- updated to Groovy 1.8.6
+- updated to Gson 1.7.2
+- updated to Log4J 1.2.17
+- updated to SLF4J 1.6.6
+- updated to Apache Commons Daemon 1.0.10
+- added Ivy 2.2.0
+
+<hr/>
+
+**0.9.3** *released 2012-04-11*
+
+#### fixes
+
+- Fixed bug where you could not remove all selections from a RepositoryModel list (permitted users, permitted teams, hook scripts, federation sets, etc) (issue 81)
+- Automatically set *java.awt.headless=true* for Gitblit GO
+
+<hr/>
+
+**0.9.2** *released 2012-04-04*
+
+#### changes
+
+- Added *clientLogger* bound variable to Groovy hook mechanism to allow custom info and error messages to be returned to the client (Github/jcrygier)
+
+#### fixes
+
+- Fixed absolute path/canonical path discrepancy between Gitblit and JGit regarding use of symlinks (issue 78)
+- Fixed row layout on activity page (issue 79)
+- Fixed Centos service script (Github/mohamedmansour)
+- Fixed EditRepositoryPage for IE8; missing save button (issue 80, Github/jonnybbb)
+
+<hr/>
+
+**0.9.1** *released 2012-03-27*
+
+#### fixes
+
+- Lucene folder was stored in working copy instead of in .git folder
+
+<hr/>
+
+**0.9.0** *released 2012-03-27*
+
+#### security
+
+- Fixed session fixation vulnerability where the session identifier was not reset during the login process (issue 62)
+
+#### changes
+
+- Reject pushes to a repository with a working copy (i.e. non-bare repository) (issue-49)
+- Changed default web.datetimestampLongFormat from *EEEE, MMMM d, yyyy h:mm a z* to *EEEE, MMMM d, yyyy HH:mm Z* (issue 50)
+- Expanded commit age coloring from 2 days to 30 days (issue 57)
+
+#### additions
+
+- Added optional Lucene branch indexing (issue 16)<br/>
+    **New:** *web.allowLuceneIndexing = true*<br/>
+    **New:** *web.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*
+Repository branches may be optionally indexed by Lucene for improved searching.  To use this feature you must specify which branches to index within the *Edit Repository* page; _no repositories are automatically indexed_.  Gitblit will build or incrementally update enrolled repositories on a 2 minute cycle. (i.e you will have to wait 2-3 minutes after respecifying indexed branches or pushing new commits before Gitblit will build/update the repository's Lucene index.)
+If a repository has Lucene-indexed branches the *search* form on the repository pages will redirect to the root-level Lucene search page and only the content of those branches can be searched.<br/>
+If the repository does not specify any indexed branches then repository commit-traversal search is used.
+**Note:** Initial indexing of an existing repository can be memory-exhaustive. Be sure to provide your Gitblit server adequate heap space to index your repositories (e.g. -Xmx1024M).<br/>
+See the [setup](setup.html) page for additional details.
+- Allow specifying timezone to use for Gitblit which is independent of both the JVM and the system timezone (issue 54)<br/>
+    **New:** *web.timezone =*
+- Added a built-in AJP connector for integrating Gitblit GO into an Apache mod_proxy setup (issue 59)<br/>
+    **New:** *server.ajpPort = 0*<br/>
+    **New:** *server.ajpBindInterface = localhost*
+- On the Repositories page show a bang *!* character in the color swatch of a repository with a working copy (issue 49)<br/>
+Push requests to these repositories will be rejected.
+- On all non-bare Repository pages show *WORKING COPY* in the upper right corner (issue 49)
+- New setting to prevent display/serving non-bare repositories<br/>
+    **New:** *git.onlyAccessBareRepositories = false*
+- Added *protect-refs.groovy* (Github/plm)
+- Allow setting default branch (relinking HEAD) to a branch or a tag (Github/plm)
+- Added Ubuntu service init script (issue 72)
+- Added partial Japanese translation (Github/zakki)
+
+#### fixes
+
+- Ensure that Welcome message is parsed using UTF-8 encoding (issue 74)
+- Activity page chart layout broken by Google (issue 73)
+- Uppercase repositories not selectable in edit palettes (issue 71)
+- Not all git notes were properly displayed on the commit page (issue 70)
+- Activity page now displays all local branches (issue 65)
+- Fixed (harmless) nullpointer on pushing to an empty repository (issue 69)
+- Fixed possible nullpointer from the servlet container on startup (issue 67)
+- Fixed UTF-8 encoding bug on diff page (issue 66)
+- Fixed timezone bugs on the activity page (issue 54)
+- Prevent add/edit team with no selected repositories (issue 56)
+- Disallow browser autocomplete on add/edit user/team/repository pages
+- Fixed username case-sensitivity issues (issue 43)
+- Disregard searching a subfolder if Gitblit does not have filesystem permissions (Github/lemval issue 51)
+
+#### dependency changes
+
+- updated to Bootstrap 2.0.2
+- added GLYPHICONS (as bundled with Bootstrap 2.0.2)
+- updated to MarkdownPapers 1.2.7
+- updated to JGit 1.3.0.201202151440-r
+- updated to Wicket 1.4.20
+
+<hr/>
+
+**0.8.2** ([go](http://code.google.com/p/gitblit/downloads/detail?name=gitblit-0.8.2.zip) | [war](http://code.google.com/p/gitblit/downloads/detail?name=gitblit-0.8.2.war) | [express](http://code.google.com/p/gitblit/downloads/detail?name=express-0.8.2.zip) | [fedclient](http://code.google.com/p/gitblit/downloads/detail?name=fedclient-0.8.2.zip) | [manager](http://code.google.com/p/gitblit/downloads/detail?name=manager-0.8.2.zip) | [api](http://code.google.com/p/gitblit/downloads/detail?name=gbapi-0.8.2.zip)) based on [JGit 1.2.0 (201112221803-r)][jgit] &nbsp; *released 2012-01-13*
+
+#### fixes
+
+- Fixed bug when upgrading from users.properties to users.conf (issue 41)
+
+<hr/>
+
+**0.8.1** &nbsp; *released 2012-01-11*
+
+#### fixes
+
+- Include missing icon resource for the manager (issue 40)
+- Fixed sendmail.groovy message content with incorrect tag/branch labels
+
+<hr/>
+
+**0.8.0** &nbsp; *released 2012-01-11*
+
+#### additions
+
+- Platform-independent, Groovy push hook script mechanism.<br/>
+Hook scripts can be set per-repository, per-team, or globally for all repositories.<br/>
+    **New:** *groovy.scriptsFolder = groovy*<br/>
+    **New:** *groovy.preReceiveScripts =*<br/>
+    **New:** *groovy.postReceiveScripts =*
+- *sendmail.groovy* for optional email notifications on push.<br/>
+You must properly configure your SMTP server settings in `gitblit.properties` or `web.xml` to use *sendmail.groovy*.
+- New global key for mailing lists.  This is used in conjunction with the *sendmail.groovy* hook script.  All repositories that use the *sendmail.groovy* script will include these addresses in the notification process.  Please see the Setup page for more details about configuring sendmail.<br/>
+    **New:** *mail.mailingLists =*
+- *com.gitblit.GitblitUserService*.  This is a wrapper object for the built-in user service implementations.  For those wanting to only implement custom authentication it is recommended to subclass GitblitUserService and override the appropriate methods.  Going forward, this will help insulate custom authentication from new IUserService API and/or changes in model classes.
+- New default user service implementation: *com.gitblit.ConfigUserService* (`users.conf`)<br/>
+This user service implementation allows for serialization and deserialization of more sophisticated Gitblit User objects without requiring the encoding trickery now present in FileUserService (users.properties).  This will open the door for more advanced Gitblit features.
+For those upgrading from an earlier Gitblit version, a `users.conf` file will automatically be created for you from your existing `users.properties` file on your first launch of Gitblit <u>however</u> you will have to manually set *realm.userService=users.conf* to switch to the new user service.<br/>
+The original `users.properties` file and it's corresponding implementation are **deprecated**.<br/>
+    **New:** *realm.userService = users.conf*
+- Teams for specifying user-repository access in bulk.  Teams may also specify mailing lists addresses and pre- & post- receive hook scripts.
+- Gravatar integration<br/>
+    **New:** *web.allowGravatar = true*
+- Activity page for aggregated repository activity.  This is a timeline of commit activity over the last N days for one or more repositories.<br/>
+   **New:** *web.activityDuration = 14*<br/>
+   **New:** *web.timeFormat = HH:mm*<br/>
+   **New:** *web.datestampLongFormat = EEEE, MMMM d, yyyy*
+- *Filters* menu for the Repositories page and Activity page.  You can filter by federation set, team, and simple custom regular expressions.  Custom expressions can be stored in `gitblit.properties` or `web.xml` or directly defined in your url (issue 27)<br/>
+   **New:** *web.customFilters=*
+- Flash-based 1-step *copy to clipboard* of the primary repository url based on Clippy<br/>
+   **New:** *web.allowFlashCopyToClipboard = true*
+- JavaScript-based 3-step (click, ctrl+c, enter) *copy to clipboard* of the primary repository url in the event that you do not want to use Flash on your installation
+- Empty repositories now link to an *empty repository* page which gives some direction to the user for the next step in using Gitblit.  This page displays the primary push/clone url of the repository and gives sample syntax for the git command-line client. (issue 31)
+- Repositories with a *gh-pages* branch will now have a *pages* link which will serve the content of this branch.  All resource requests are against the repository, Gitblit does not checkout/export this branch to a temporary filesystem.  Jekyll templating is not supported.
+- Gitblit Express bundle to get started running Gitblit on RedHat's OpenShift cloud <span class="label label-warning">BETA</span>
+
+#### changes
+
+- Dropped display of trailing .git from repository names
+- Gitblit GO is now monolithic like the WAR build. (issue 30)<br/>
+This change helps adoption of GO in environments without an internet connection or with a restricted connection.
+- Unit testing framework has been migrated to JUnit4 syntax and the test suite has been redesigned to run all unit tests, including rpc, federation, and git push/clone tests
+
+#### fixes
+
+- Several a bugs in FileUserService related to cleaning up old repository permissions on a rename or delete
+- Renaming a repository into a new subfolder failed (issue 33)
+
+#### dependency changes
+
+- updated to JGit 1.2.0
+- added Groovy 1.8.5
+- added Clippy (bundled)
+
+<hr/>
+
+**0.7.0** &nbsp; *released 2011-11-11*
+
+- **security**: fixed security hole when cloning clone-restricted repository with TortoiseGit (issue 28)
+- improved: updated ui with Twitter's Bootstrap CSS toolkit<br/>
+    **New:** *web.loginMessage = gitblit*
+- improved: repositories list performance by caching repository sizes (issue 27)
+- improved: summary page performance by caching metric calculations (issue 25)
+- added: authenticated JSON RPC mechanism<br/>
+    **New:** *web.enableRpcServlet = true*<br/>
+    **New:** *web.enableRpcManagement = false*<br/>
+    **New:** *web.enableRpcAdministration = false*
+- added: Gitblit API RSS/JSON RPC library
+- added: Gitblit Manager (Java/Swing Application) for remote administration of a Gitblit server.
+- added: per-repository setting to skip size calculation (faster repositories page loading)
+- added: per-repository setting to skip summary metrics calculation (faster summary page loading)
+- added: IUserService.setup(IStoredSettings) for custom user service implementations
+- added: setting to control Gitblit GO context path for proxy setups *(Github/trygvis)*<br/>
+    **New:** *server.contextPath = /*
+- added: *combined-md5* password storage option which stores the hash of username+password as the password *(Github/alyandon)*
+- added: repository owners are automatically granted access for git, feeds, and zip downloads without explicitly selecting them *(Github/dadalar)*
+- added: RSS feeds now include regex substitutions on commit messages for bug trackers, etc
+- fixed: federation protocol timestamps.  dates are now serialized to the [iso8601](http://en.wikipedia.org/wiki/ISO_8601) standard.<br/>
+    **This breaks 0.6.0 federation clients/servers.**
+- fixed: collision on rename for repositories and users
+- fixed: Gitblit can now browse the Linux kernel repository (issue 25)
+- fixed: Gitblit now runs on Servlet 3.0 webservers (e.g. Tomcat 7, Jetty 8) (issue 23)
+- fixed: Set the RSS content type of syndication feeds for Firefox 4 (issue 22)
+- fixed: RSS feeds are now properly encoded to UTF-8
+- fixed: RSS feeds now properly generate parameterized links if *web.mountParameters=false*
+- fixed: Null pointer exception if did not set federation strategy (issue 20)
+- fixed: Gitblit GO allows SSL renegotiation if running on Java 1.6.0_22 or later
+- updated: MarkdownPapers 1.2.5
+- updated: Wicket 1.4.19
+
+<hr/>
+
+**0.6.0** &nbsp; *released 2011-09-27*
+
+- added: federation feature to allow gitblit instances (or gitblit federation clients) to pull repositories and, optionally, settings and accounts from other gitblit instances.  This is something like [svn-sync](http://svnbook.red-bean.com/en/1.5/svn.ref.svnsync.html) for gitblit.<br/>
+    **New:** *federation.name =*<br/>
+    **New:** *federation.passphrase =*<br/>
+    **New:** *federation.allowProposals = false*<br/>
+    **New:** *federation.proposalsFolder = proposals*<br/>
+    **New:** *federation.defaultFrequency = 60 mins*<br/>
+    **New:** *federation.sets =*<br/>
+    **New:** *mail.* settings for sending emails<br/>
+    **New:** user role *#notfederated* to prevent a user account from being pulled by a federated Gitblit instance
+- added: google-gson dependency
+- added: javamail dependency
+- updated: MarkdownPapers 1.1.1
+- updated: Wicket 1.4.18
+- updated: JGit 1.1.0
+- fixed: syndication urls for WAR deployments
+- fixed: authentication for zip downloads
+
+<hr/>
+
+**0.5.2** &nbsp; *released 2011-07-27*
+
+- fixed: active repositories with a HEAD that pointed to an empty branch caused internal errors (issue 14)
+- fixed: bare-cloned repositories were listed as (empty) and were not clickable (issue 13)
+- fixed: default port for Gitblit GO is now 8443 to be more linux/os x friendly (issue 12)
+- fixed: repositories can now be reliably deleted and renamed (issue 10)
+- fixed: users can now change their passwords (issue 1)
+- fixed: always show root repository group first, i.e. don't sort root group with other groups
+- fixed: tone-down repository group header color
+- added: optionally display repository on-disk size on repositories page<br/>
+    **New:** *web.showRepositorySizes = true*
+- added: forward-slashes ('/', %2F) can be encoded using a custom character to workaround some servlet container default security measures for proxy servers<br/>
+    **New:** *web.forwardSlashCharacter = /*
+- updated: MarkdownPapers 1.1.0
+- updated: Jetty 7.4.3
+
+<hr/>
+
+**0.5.1** &nbsp; *released 2011-06-28*
+
+- clarified SSL certificate generation and configuration for both server-side and client-side
+- added some more troubleshooting information to documentation
+- replaced JavaService with Apache Commons Daemon
+
+<hr/>
+
+**0.5.0** &nbsp; *released 2011-06-26*
+
+- initial release
+
+[jgit]: http://eclipse.org/jgit "Eclipse JGit Site"
diff --git a/src/site/releases.mkd b/src/site/releases.mkd
new file mode 100644
index 0000000..7dd6b17
--- /dev/null
+++ b/src/site/releases.mkd
@@ -0,0 +1,550 @@
+## Release History
+
+### Current Release
+
+**%VERSION%** ([go](http://code.google.com/p/gitblit/downloads/detail?name=%GO%) | [war](http://code.google.com/p/gitblit/downloads/detail?name=%WAR%) | [express](http://code.google.com/p/gitblit/downloads/detail?name=%EXPRESS%) | [fedclient](http://code.google.com/p/gitblit/downloads/detail?name=%FEDCLIENT%) | [manager](http://code.google.com/p/gitblit/downloads/detail?name=%MANAGER%) | [api](http://code.google.com/p/gitblit/downloads/detail?name=%API%)) based on [%JGIT%][jgit] &nbsp; *released %BUILDDATE%*
+
+#### fixes
+
+- Can't set reset settings with $ or { characters through Gitblit Manager because they are not properly escaped
+
+#### additions
+
+ - Option to force client-side basic authentication instead of form-based authentication if web.authenticateViewPages=true (github/furinzen)
+ - Optional periodic LDAP user and team pre-fetching & synchronization (github/mschaefers)
+ - Display name and version in Tomcat Manager (github/thefake) 
+ - FogBugz post-receive hook script (github/djschny)
+ - Implemented multiple repository owners (github/akquinet)
+ - Chinese translation (github/dapengme, github/yin8086)
+
+### Older Releases
+
+<div class="alert alert-info">
+<h4>Update Note 1.2.1</h4>
+Because there are now several types of files and folders that must be considered Gitblit data, the default location for data has changed.
+<p>You will need to move a few files around when upgrading.  Please see the Upgrading section of the <a href="setup.html">setup</a> page for details.</p>
+
+<b>Express Users</b> make sure to update your web.xml file with the ${baseFolder} values!
+</div>
+
+#### fixes
+
+- Fixed nullpointer on recursively calculating folder sizes when there is a named pipe or symlink in the hierarchy
+- Added nullchecking when concurrently forking a repository and trying to display it's fork network (issue-187)
+- Fixed bug where permission changes were not visible in the web ui to a logged-in user until the user logged-out and then logged back in again (issue-186)
+- Fixed nullpointer on creating a repository with mixed case (issue 185)
+- Include missing model classes in api library (issue-184)
+- Fixed nullpointer when using *web.allowForking = true* && *git.cacheRepositoryList = false* (issue 182)
+- Likely fix for commit and commitdiff page failures when a submodule reference changes (issue 178)
+- Build project models from the repository model cache, when possible, to reduce page load time (issue 172)
+- Fixed loading of Brazilian Portuguese translation from *nix server (github/inaiat)
+
+#### additions
+
+- Fanout PubSub service for self-hosted [Sparkleshare](http://sparkleshare.org) notifications.<br/>
+This service is disabled by default.<br/>
+    **New:** *fanout.bindInterface = localhost*<br/>
+	**New:** *fanout.port = 0*<br/>
+	**New:** *fanout.useNio = true*<br/>
+	**New:** *fanout.connectionLimit = 0*
+- Implemented a simple push log based on a hidden, orphan branch refs/gitblit/pushes (issue 177)<br/>
+The push log is not currently visible in the ui, but the data will be collected and it will be exposed to the ui in the next release.
+- Support for locally and remotely authenticated accounts in LdapUserService and RedmineUserService (issue 183)
+- Added Dutch translation (github/kwoot)
+
+#### changes
+
+- Gitblit GO and Gitblit WAR are now both configured by `gitblit.properties`. WAR is no longer configured by `web.xml`.<br/>
+However, Express for OpenShift continues to be configured by `web.xml`.
+- Support for a *--baseFolder* command-line argument for Gitblit GO and Gitblit Certificate Authority
+- Support for specifying a *${baseFolder}* parameter in `gitblit.properties` and `web.xml` for several settings
+- Improve history display of a submodule link
+- Updated Korean translation (github/ds5apn)
+- Updated checkstyle definition (github/mystygage)
+
+<div class="alert alert-info">
+<h4>Update Note 1.2.0</h4>
+The permissions model has changed in the 1.2.0 release.
+<p>If you are updating your server, you must also update any Gitblit Manager and Federation Client installs to 1.2.0 as well.  The data model used by the RPC mechanism has changed slightly for the new permissions infrastructure.</p>
+</div>
+
+**1.2.0** *released 2012-12-31*
+
+#### fixes
+
+- Fixed regression in *isFrozen* (issue 181)
+- Author metrics can be broken by newlines in email addresses from converted repositories (issue 176)
+- Set subjectAlternativeName on generated SSL cert if CN is an ip address (issue 170)
+- Fixed incorrect links on history page for files not in the current/active commit (issue 166)
+- Empty repository page failed to handle missing repository (issue 160)
+- Fixed broken ticgit urls (issue 157)
+- Exclude submodules from zip downloads (issue 151)
+- Fixed bug where repository ownership was not updated on rename user
+- Fixed bug in create/rename repository if you explicitly specified the alias for the root group (e.g. main/myrepo) (issue 143)
+- Wrapped Markdown parser with improved exception handler (issue 142)
+- Fixed duplicate entries in repository cache (issue 140)
+- Fixed connection leak in LDAPUserService (issue 139)
+- Fixed bug in commit page where changes to a submodule threw a null pointer exception (issue 132)
+- Fixed bug in the diff view for filenames that have non-ASCII characters (issue 128)
+
+#### additions
+
+- Implemented discrete repository permissions (issue 36)
+    - V (view in web ui, RSS feeds, download zip)
+    - R (clone)
+    - RW (clone and push)
+    - RWC (clone and push with ref creation)
+    - RWD (clone and push with ref creation, deletion)
+    - RW+ (clone and push with ref creation, deletion, rewind)
+While not as sophisticated as Gitolite, this does give finer access controls.  These permissions fit in cleanly with the existing users.conf and users.properties files.  In Gitblit <= 1.1.0, all your existing user accounts have RW+ access.   If you are upgrading to 1.2.0, the RW+ access is *preserved* and you will have to lower/adjust accordingly.
+- Implemented *case-insensitive* regex repository permission matching (issue 36)<br/>
+This allows you to specify a permission like `RW:mygroup/.*` to grant push privileges to all repositories within the *mygroup* project/folder.
+- Added DELETE, CREATE, and NON-FAST-FORWARD ref change logging
+- Added support for personal repositories.<br/>
+Personal repositories can be created by accounts with the *create* permission and are stored in *git.repositoriesFolder/~username*.  Each user with personal repositories will have a user page, something like the GitHub profile page.  Personal repositories have all the same features as common repositories, except personal repositories can be renamed by their owner.
+- Added support for server-side forking of a repository to a personal repository (issue 137)<br/>
+In order to fork a repository, the user account must have the *fork* permission **and** the repository must *allow forks*.  The clone inherits the access list of its origin.  i.e. if Team A has clone access to the origin repository, then by default Team A also has clone access to the fork.  This is to facilitate collaboration.  The fork owner may change access to the fork and add/remove users/teams, etc as required <u>however</u> it should be noted that all personal forks will be enumerated in the fork network regardless of access view restrictions.  If you really must have an invisible fork, the clone it locally, create a new repository for your invisible fork, and push it back to Gitblit.<br/>
+    **New:** *web.allowForking=true*
+- Added optional *create-on-push* support<br/>
+    **New:** *git.allowCreateOnPush=true*
+- Added **experimental** JGit-based garbage collection service.  This service is disabled by default.<br/>
+    **New:** *git.allowGarbageCollection=false*<br/>
+    **New:** *git.garbageCollectionHour = 0*<br/>
+    **New:** *git.defaultGarbageCollectionThreshold = 500k*<br/>
+    **New:** *git.defaultGarbageCollectionPeriod = 7 days*
+- Added support for X509 client certificate authentication (github/kevinanderson1).  (issue 106)<br/>
+You can require all git servlet access be authenticated by a client certificate.  You may also specify the OID fingerprint to use for mapping a certificate to a username.  It should be noted that the user account MUST already exist in Gitblit for this authentication mechanism to work; this mechanism can not be used to automatically create user accounts from a certificate.<br/>
+    **New:** *git.requireClientCertificates = false*<br/>
+    **New:** *git.enforceCertificateValidity = true*<br/>
+    **New:** *git.certificateUsernameOIDs = CN*
+- Revised clean install certificate generation to create a Gitblit GO Certificate Authority certificate; an SSL certificate signed by the CA certificate; and to create distinct server key and server trust stores.  <u>The store files have been renamed!</u>
+- Added support for Gitblit GO to require usage of client certificates to access the entire server.<br/>
+This is extreme and should be considered carefully since it affects every https access.  The default is to **want** client certificates.  Setting this value to *true* changes that to **need** client certificates.<br/>
+    **New:** *server.requireClientCertificates = false*
+- Added **Gitblit Certificate Authority**, an x509 PKI management tool for Gitblit GO to encourage use of x509 client certificate authentication.
+- Added setting to control length of shortened commit ids<br/>
+    **New:** *web.shortCommitIdLength=8*
+- Added alternate compressed download formats: tar.gz, tar.xz, tar.bzip2 (issue 174)<br/>
+    **New:** *web.compressedDownloads = zip gz*
+- Added simple project pages.  A project is a subfolder off the *git.repositoriesFolder*.
+- Added support for X-Forwarded-Context for Apache subdomain proxy configurations (issue 135)
+- Delete branch feature (issue 121, Github/ajermakovics)
+- Added line links to blob view (issue 130)
+- Added HTML sendmail hook script and Gitblit.sendHtmlMail method (github/sauthieg)
+- Added RedmineUserService (github/mallowlabs)
+- Support for committer verification.  Requires use of *--no-ff* when merging branches or pull requests.  See setup page for details.
+- Added Brazilian Portuguese translation (github/rafaelcavazin)
+
+#### changes
+
+- Added server setting to specify keystore alias for ssl certificate (issue 98)
+- Added optional global and per-repository activity page commit contribution throttle to help tame *really* active repositories (issue 173)
+- Added support for symlinks in tree page and commit page (issue 171)
+- All access restricted servlets (e.g. DownloadZip, RSS, etc) will try to authenticate using X509 certificates, container principals, cookies, and BASIC headers, in that order.
+- Added *groovy* and *scala* to *web.prettyPrintExtensions*
+- Added short commit id column to log and history tables (issue 168)
+- Teams can now specify the *admin*, *create*, and *fork* roles to simplify user administration
+- Use https Gravatar urls to avoid browser complaints
+- Added frm to default pretty print extensions (issue 156)
+- Expose ReceivePack to Groovy push hooks (issue 125)
+- Redirect to summary page when refreshing the empty repository page on a repository that is not empty (issue 129)
+- Emit a warning in the log file if running on a Tomcat-based servlet container which is unfriendly to %2F forward-slash url encoding AND Gitblit is configured to mount parameters with %2F forward-slash url encoding (Github/jpyeron, issue 126)
+- LDAP admin attribute setting is now consistent with LDAP teams setting and admin teams list.
+If *realm.ldap.maintainTeams==true* **AND** *realm.ldap.admins* is not empty, then User.canAdmin() is controlled by LDAP administrative team membership.  Otherwise, User.canAdmin() is controlled by Gitblit.
+- Support servlet container authentication for existing UserModels (issue 68)
+
+#### dependency changes
+
+- updated to Jetty 7.6.8
+- updated to JGit 2.2.0.201212191850-r
+- updated to Groovy 1.8.8
+- updated to Wicket 1.4.21
+- updated to Lucene 3.6.1
+- updated to BouncyCastle 1.47
+- updated to MarkdownPapers 1.3.2
+- added JCalendar 1.3.2
+- added Commons-Compress 1.4.1
+- added XZ for Java 1.0
+<hr/>
+
+<div class="alert alert-error">
+<h4>Update Note 1.1.0</h4>
+If you are updating from an earlier release AND you have indexed branches with the Lucene indexing feature, you need to be aware that this release will completely re-index your repositories.  Please be sure to provide ample heap resources as appropriate for your installation.
+</div>
+
+**1.1.0** *released 2012-08-25*
+
+#### fixes
+
+- Bypass Wicket's inability to handle direct url addressing of a view-restricted, grouped repository for new, unauthenticated sessions (e.g. click link from email or rss feed without having an active Wicket session)
+- Fixed MailExecutor's failure to cope with mail server connection troubles resulting in 100% CPU usage
+- Fixed generated urls in Groovy *sendmail* hook script for grouped repositories
+- Fixed generated urls in RSS feeds for grouped repositories
+- Fixed nullpointer exception in git servlet security filter (issue 123)
+- Eliminated an unnecessary repository enumeration call on the root page which should result in faster page loads (issue 103)
+- Gitblit could not delete a Lucene index in a working copy on index upgrade
+- Do not index submodule links (issue 119)
+- Restore original user or team object on failure to update (issue 118)
+- Fixes to relative path determination in repository search algorithm for symlinks (issue 116)
+- Fix to GitServlet to allow pushing to symlinked repositories (issue 116)
+- Repository URL now uses `X-Forwarded-Proto` and `X-Forwarded-Port`, if available, for reverse proxy configurations (issue 115)
+- Output real RAW content, not simulated RAW content (issue 114)
+- Fixed Lucene charset encoding bug when reindexing a repository (issue 112)
+- Fixed search box linking to Lucene page for grouped repository on Tomcat (issue 111)
+- Fixed null pointer in LdapUserSerivce if account has a null email address (issue 110)
+- Really fixed failure to update a GO setting from the manager (issue 85)
+
+#### additions
+
+- Identified repository list is now cached by default to reduce disk io and to improve performance (issue 103)<br/>
+    **New:** *git.cacheRepositoryList=true*
+- Preliminary bare repository submodule support<br/>
+    **New:** *git.submoduleUrlPatterns=*
+    - *git.submoduleUrlPatterns* is a space-delimited list of regular expressions for extracting a repository name from a submodule url.<br/>
+    For example, `git.submoduleUrlPatterns = .*?://github.com/(.*)` would extract *gitblit/gitblit.git* from *git://github.git/gitblit/gitblit.git*<br/>
+    **Note:** You may not need this control to work with submodules, but it is there if you do.
+    - If there are no matches from *git.submoduleUrlPatterns* then the repository name is assumed to be whatever comes after the last `/` character *(e.g. gitblit.git)*
+    - Gitblit will try to locate this repository relative to the current repository *(e.g. myfolder/myrepo.git, myfolder/mysubmodule.git)* and then at the root level *(mysubmodule.git)* if that fails.
+    - Submodule references in a working copy will be properly identified as gitlinks, but Gitblit will not traverse into the working copy submodule repository.
+- Added a repository setting to control authorization as AUTHENTICATED or NAMED. (issue 117)<br/>
+NAMED is the original behavior for authorizing against a list of permitted users or permitted teams.
+AUTHENTICATED allows restricted access for any authenticated user.  This is a looser authorization control.
+- Added default authorization control setting (AUTHENTICATED or NAMED)<br/>
+    **New:** *git.defaultAuthorizationControl=NAMED*
+- Added setting to control how deep Gitblit will recurse into *git.repositoriesFolder* looking for repositories (issue 103)<br/>
+    **New:** *git.searchRecursionDepth=-1*
+- Added setting to specify regex exclusions for repositories (issue 103)<br/>
+    **New:** *git.searchExclusions=*
+- Blob page now supports displaying images (issue 6)
+- Non-image binary files can now be downloaded using the RAW link
+- Support StartTLS in LdapUserService (Steffen Gebert, issue 122)
+- Added Korean translation
+
+#### changes
+
+- Line breaks inserted for readability in raw Markdown content display in the event of a parsing/transformation error.  An error message is now displayed prepended to the raw content.
+- Improve UTF-8 reading for Markdown files
+- Updated Polish translation
+- Updated Japanese translation
+- Updated Spanish translation
+
+<hr/>
+
+**1.0.0** *released 2012-07-14*
+
+#### fixes
+
+- Fixed bug in Lucene search where old/stale blobs were never properly deleted during incremental updates.  This resulted in duplicate blob entries in the index.
+- Fixed intermittent bug in identifying line numbers in Lucene search (issue 105)
+- Adjust repository identification algorithm to handle the scenario where a repository name collides with a group/folder name (e.g. foo.git and foo/bar.git) (issue 104)
+- Fixed bug where a repository set as *authenticated push* did not have anonymous clone access (issue 96)
+- Fixed bug in Basic authentication if passwords had a colon (Github/peterloron)
+- Fixed bug where the Gitblit Manager could not update a setting that was not referenced in reference.properties (issue 85)
+
+#### changes
+
+- **Updated Lucene index version which will force a rebuild of ALL your Lucene indexes**<br/>
+Make sure to properly set *web.blobEncodings* before starting Gitblit if you are updating!  (issue 97)
+- Changed default layout for web ui from Fixed-Width layout to Responsive layout (issue 101)
+- IUserService interface has changed to better accomodate custom authentication and/or custom authorization<br/>
+    The default `users.conf` now supports persisting display names and email addresses.
+- Updated Japanese translation (Github/zakki)
+
+#### additions
+
+- Added setting to allow specification of a robots.txt file (issue 99)<br/>
+    **New:** *web.robots.txt =*
+- Added setting to control Responsive layout or Fixed-Width layout (issue 101)<br/>
+    Responsive layout is now the default.  This layout gracefully scales the web ui from a desktop layout to a mobile layout by hiding page components.  It is easy to try, just resize your browser or point your Android/iOS device to the url of your Gitblit install.
+    **New:** *web.useResponsiveLayout = true*
+- Added setting to control charsets for blob string decoding.  Default encodings are UTF-8, ISO-8859-1, and server's default charset. (issue 97)<br/>
+    **New:** *web.blobEncodings = UTF-8 ISO-8859-1*
+- Exposed JGit's internal configuration settings in gitblit.properties/web.xml (issue 93)<br/>
+    Review your `gitblit.properties` or `web.xml` for detailed explanations of these settings.<br/>
+    **New:** *git.packedGitWindowSize = 8k*<br/>
+    **New:** *git.packedGitLimit = 10m*<br/>
+    **New:** *git.deltaBaseCacheLimit = 10m*<br/>
+    **New:** *git.packedGitOpenFiles = 128*<br/>
+    **New:** *git.streamFileThreshold = 50m*<br/>
+    **New:** *git.packedGitMmap = false*
+- Added default access restriction.  Applies to new repositories and repositories that have not been configured with Gitblit. (issue 88)<br/>
+    **New:** *git.defaultAccessRestriction = NONE*
+- Added Ivy 2.2.0 dependency which enables Groovy Grapes, a mechanism to resolve and retrieve library dependencies from a Maven 2 repository within a Groovy push hook script
+- Added setting to control Groovy Grape root folder (location where resolved dependencies are stored)<br/>
+    [Grape](http://groovy.codehaus.org/Grape) allows you to add Maven dependencies to your pre-/post-receive hook script classpath.<br/>
+    **New:** *groovy.grapeFolder = groovy/grape*
+- Added LDAP User Service with many new *realm.ldap* keys (Github/jcrygier)
+- Added support for custom repository properties for Groovy hooks (Github/jcrygier)<br/>
+    Custom repository properties complement hook scripts by providing text field prompts in the web ui and the Gitblit Manager for the defined properties.  This allows your push hooks to be parameterized.
+- Added script to facilitate proxy environment setup on Linux (Github/mragab)
+- Added Polish translation (Lukasz Jader)
+- Added Spanish translation (Eduardo Guervos Narvaez)
+
+#### dependency changes
+
+- updated to Bootstrap 2.0.4
+- updated to JGit 2.0.0.201206130900-r
+- updated to Groovy 1.8.6
+- updated to Gson 1.7.2
+- updated to Log4J 1.2.17
+- updated to SLF4J 1.6.6
+- updated to Apache Commons Daemon 1.0.10
+- added Ivy 2.2.0
+
+<hr/>
+
+**0.9.3** *released 2012-04-11*
+
+#### fixes
+
+- Fixed bug where you could not remove all selections from a RepositoryModel list (permitted users, permitted teams, hook scripts, federation sets, etc) (issue 81)
+- Automatically set *java.awt.headless=true* for Gitblit GO
+
+<hr/>
+
+**0.9.2** *released 2012-04-04*
+
+#### changes
+
+- Added *clientLogger* bound variable to Groovy hook mechanism to allow custom info and error messages to be returned to the client (Github/jcrygier)
+
+#### fixes
+
+- Fixed absolute path/canonical path discrepancy between Gitblit and JGit regarding use of symlinks (issue 78)
+- Fixed row layout on activity page (issue 79)
+- Fixed Centos service script (Github/mohamedmansour)
+- Fixed EditRepositoryPage for IE8; missing save button (issue 80, Github/jonnybbb)
+
+<hr/>
+
+**0.9.1** *released 2012-03-27*
+
+#### fixes
+
+- Lucene folder was stored in working copy instead of in .git folder
+
+<hr/>
+
+**0.9.0** *released 2012-03-27*
+
+#### security
+
+- Fixed session fixation vulnerability where the session identifier was not reset during the login process (issue 62)
+
+#### changes
+
+- Reject pushes to a repository with a working copy (i.e. non-bare repository) (issue-49)
+- Changed default web.datetimestampLongFormat from *EEEE, MMMM d, yyyy h:mm a z* to *EEEE, MMMM d, yyyy HH:mm Z* (issue 50)
+- Expanded commit age coloring from 2 days to 30 days (issue 57)
+
+#### additions
+
+- Added optional Lucene branch indexing (issue 16)<br/>
+    **New:** *web.allowLuceneIndexing = true*<br/>
+    **New:** *web.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*
+Repository branches may be optionally indexed by Lucene for improved searching.  To use this feature you must specify which branches to index within the *Edit Repository* page; _no repositories are automatically indexed_.  Gitblit will build or incrementally update enrolled repositories on a 2 minute cycle. (i.e you will have to wait 2-3 minutes after respecifying indexed branches or pushing new commits before Gitblit will build/update the repository's Lucene index.)
+If a repository has Lucene-indexed branches the *search* form on the repository pages will redirect to the root-level Lucene search page and only the content of those branches can be searched.<br/>
+If the repository does not specify any indexed branches then repository commit-traversal search is used.
+**Note:** Initial indexing of an existing repository can be memory-exhaustive. Be sure to provide your Gitblit server adequate heap space to index your repositories (e.g. -Xmx1024M).<br/>
+See the [setup](setup.html) page for additional details.
+- Allow specifying timezone to use for Gitblit which is independent of both the JVM and the system timezone (issue 54)<br/>
+    **New:** *web.timezone =*
+- Added a built-in AJP connector for integrating Gitblit GO into an Apache mod_proxy setup (issue 59)<br/>
+    **New:** *server.ajpPort = 0*<br/>
+    **New:** *server.ajpBindInterface = localhost*
+- On the Repositories page show a bang *!* character in the color swatch of a repository with a working copy (issue 49)<br/>
+Push requests to these repositories will be rejected.
+- On all non-bare Repository pages show *WORKING COPY* in the upper right corner (issue 49)
+- New setting to prevent display/serving non-bare repositories<br/>
+    **New:** *git.onlyAccessBareRepositories = false*
+- Added *protect-refs.groovy* (Github/plm)
+- Allow setting default branch (relinking HEAD) to a branch or a tag (Github/plm)
+- Added Ubuntu service init script (issue 72)
+- Added partial Japanese translation (Github/zakki)
+
+#### fixes
+
+- Ensure that Welcome message is parsed using UTF-8 encoding (issue 74)
+- Activity page chart layout broken by Google (issue 73)
+- Uppercase repositories not selectable in edit palettes (issue 71)
+- Not all git notes were properly displayed on the commit page (issue 70)
+- Activity page now displays all local branches (issue 65)
+- Fixed (harmless) nullpointer on pushing to an empty repository (issue 69)
+- Fixed possible nullpointer from the servlet container on startup (issue 67)
+- Fixed UTF-8 encoding bug on diff page (issue 66)
+- Fixed timezone bugs on the activity page (issue 54)
+- Prevent add/edit team with no selected repositories (issue 56)
+- Disallow browser autocomplete on add/edit user/team/repository pages
+- Fixed username case-sensitivity issues (issue 43)
+- Disregard searching a subfolder if Gitblit does not have filesystem permissions (Github/lemval issue 51)
+
+#### dependency changes
+
+- updated to Bootstrap 2.0.2
+- added GLYPHICONS (as bundled with Bootstrap 2.0.2)
+- updated to MarkdownPapers 1.2.7
+- updated to JGit 1.3.0.201202151440-r
+- updated to Wicket 1.4.20
+
+<hr/>
+
+**0.8.2** ([go](http://code.google.com/p/gitblit/downloads/detail?name=gitblit-0.8.2.zip) | [war](http://code.google.com/p/gitblit/downloads/detail?name=gitblit-0.8.2.war) | [express](http://code.google.com/p/gitblit/downloads/detail?name=express-0.8.2.zip) | [fedclient](http://code.google.com/p/gitblit/downloads/detail?name=fedclient-0.8.2.zip) | [manager](http://code.google.com/p/gitblit/downloads/detail?name=manager-0.8.2.zip) | [api](http://code.google.com/p/gitblit/downloads/detail?name=gbapi-0.8.2.zip)) based on [JGit 1.2.0 (201112221803-r)][jgit] &nbsp; *released 2012-01-13*
+
+#### fixes
+
+- Fixed bug when upgrading from users.properties to users.conf (issue 41)
+
+<hr/>
+
+**0.8.1** &nbsp; *released 2012-01-11*
+
+#### fixes
+
+- Include missing icon resource for the manager (issue 40)
+- Fixed sendmail.groovy message content with incorrect tag/branch labels
+
+<hr/>
+
+**0.8.0** &nbsp; *released 2012-01-11*
+
+#### additions
+
+- Platform-independent, Groovy push hook script mechanism.<br/>
+Hook scripts can be set per-repository, per-team, or globally for all repositories.<br/>
+    **New:** *groovy.scriptsFolder = groovy*<br/>
+    **New:** *groovy.preReceiveScripts =*<br/>
+    **New:** *groovy.postReceiveScripts =*
+- *sendmail.groovy* for optional email notifications on push.<br/>
+You must properly configure your SMTP server settings in `gitblit.properties` or `web.xml` to use *sendmail.groovy*.
+- New global key for mailing lists.  This is used in conjunction with the *sendmail.groovy* hook script.  All repositories that use the *sendmail.groovy* script will include these addresses in the notification process.  Please see the Setup page for more details about configuring sendmail.<br/>
+    **New:** *mail.mailingLists =*
+- *com.gitblit.GitblitUserService*.  This is a wrapper object for the built-in user service implementations.  For those wanting to only implement custom authentication it is recommended to subclass GitblitUserService and override the appropriate methods.  Going forward, this will help insulate custom authentication from new IUserService API and/or changes in model classes.
+- New default user service implementation: *com.gitblit.ConfigUserService* (`users.conf`)<br/>
+This user service implementation allows for serialization and deserialization of more sophisticated Gitblit User objects without requiring the encoding trickery now present in FileUserService (users.properties).  This will open the door for more advanced Gitblit features.
+For those upgrading from an earlier Gitblit version, a `users.conf` file will automatically be created for you from your existing `users.properties` file on your first launch of Gitblit <u>however</u> you will have to manually set *realm.userService=users.conf* to switch to the new user service.<br/>
+The original `users.properties` file and it's corresponding implementation are **deprecated**.<br/>
+    **New:** *realm.userService = users.conf*
+- Teams for specifying user-repository access in bulk.  Teams may also specify mailing lists addresses and pre- & post- receive hook scripts.
+- Gravatar integration<br/>
+    **New:** *web.allowGravatar = true*
+- Activity page for aggregated repository activity.  This is a timeline of commit activity over the last N days for one or more repositories.<br/>
+   **New:** *web.activityDuration = 14*<br/>
+   **New:** *web.timeFormat = HH:mm*<br/>
+   **New:** *web.datestampLongFormat = EEEE, MMMM d, yyyy*
+- *Filters* menu for the Repositories page and Activity page.  You can filter by federation set, team, and simple custom regular expressions.  Custom expressions can be stored in `gitblit.properties` or `web.xml` or directly defined in your url (issue 27)<br/>
+   **New:** *web.customFilters=*
+- Flash-based 1-step *copy to clipboard* of the primary repository url based on Clippy<br/>
+   **New:** *web.allowFlashCopyToClipboard = true*
+- JavaScript-based 3-step (click, ctrl+c, enter) *copy to clipboard* of the primary repository url in the event that you do not want to use Flash on your installation
+- Empty repositories now link to an *empty repository* page which gives some direction to the user for the next step in using Gitblit.  This page displays the primary push/clone url of the repository and gives sample syntax for the git command-line client. (issue 31)
+- Repositories with a *gh-pages* branch will now have a *pages* link which will serve the content of this branch.  All resource requests are against the repository, Gitblit does not checkout/export this branch to a temporary filesystem.  Jekyll templating is not supported.
+- Gitblit Express bundle to get started running Gitblit on RedHat's OpenShift cloud <span class="label label-warning">BETA</span>
+
+#### changes
+
+- Dropped display of trailing .git from repository names
+- Gitblit GO is now monolithic like the WAR build. (issue 30)<br/>
+This change helps adoption of GO in environments without an internet connection or with a restricted connection.
+- Unit testing framework has been migrated to JUnit4 syntax and the test suite has been redesigned to run all unit tests, including rpc, federation, and git push/clone tests
+
+#### fixes
+
+- Several a bugs in FileUserService related to cleaning up old repository permissions on a rename or delete
+- Renaming a repository into a new subfolder failed (issue 33)
+
+#### dependency changes
+
+- updated to JGit 1.2.0
+- added Groovy 1.8.5
+- added Clippy (bundled)
+
+<hr/>
+
+**0.7.0** &nbsp; *released 2011-11-11*
+
+- **security**: fixed security hole when cloning clone-restricted repository with TortoiseGit (issue 28)
+- improved: updated ui with Twitter's Bootstrap CSS toolkit<br/>
+    **New:** *web.loginMessage = gitblit*
+- improved: repositories list performance by caching repository sizes (issue 27)
+- improved: summary page performance by caching metric calculations (issue 25)
+- added: authenticated JSON RPC mechanism<br/>
+    **New:** *web.enableRpcServlet = true*<br/>
+    **New:** *web.enableRpcManagement = false*<br/>
+    **New:** *web.enableRpcAdministration = false*
+- added: Gitblit API RSS/JSON RPC library
+- added: Gitblit Manager (Java/Swing Application) for remote administration of a Gitblit server.
+- added: per-repository setting to skip size calculation (faster repositories page loading)
+- added: per-repository setting to skip summary metrics calculation (faster summary page loading)
+- added: IUserService.setup(IStoredSettings) for custom user service implementations
+- added: setting to control Gitblit GO context path for proxy setups *(Github/trygvis)*<br/>
+    **New:** *server.contextPath = /*
+- added: *combined-md5* password storage option which stores the hash of username+password as the password *(Github/alyandon)*
+- added: repository owners are automatically granted access for git, feeds, and zip downloads without explicitly selecting them *(Github/dadalar)*
+- added: RSS feeds now include regex substitutions on commit messages for bug trackers, etc
+- fixed: federation protocol timestamps.  dates are now serialized to the [iso8601](http://en.wikipedia.org/wiki/ISO_8601) standard.<br/>
+    **This breaks 0.6.0 federation clients/servers.**
+- fixed: collision on rename for repositories and users
+- fixed: Gitblit can now browse the Linux kernel repository (issue 25)
+- fixed: Gitblit now runs on Servlet 3.0 webservers (e.g. Tomcat 7, Jetty 8) (issue 23)
+- fixed: Set the RSS content type of syndication feeds for Firefox 4 (issue 22)
+- fixed: RSS feeds are now properly encoded to UTF-8
+- fixed: RSS feeds now properly generate parameterized links if *web.mountParameters=false*
+- fixed: Null pointer exception if did not set federation strategy (issue 20)
+- fixed: Gitblit GO allows SSL renegotiation if running on Java 1.6.0_22 or later
+- updated: MarkdownPapers 1.2.5
+- updated: Wicket 1.4.19
+
+<hr/>
+
+**0.6.0** &nbsp; *released 2011-09-27*
+
+- added: federation feature to allow gitblit instances (or gitblit federation clients) to pull repositories and, optionally, settings and accounts from other gitblit instances.  This is something like [svn-sync](http://svnbook.red-bean.com/en/1.5/svn.ref.svnsync.html) for gitblit.<br/>
+    **New:** *federation.name =*<br/>
+    **New:** *federation.passphrase =*<br/>
+    **New:** *federation.allowProposals = false*<br/>
+    **New:** *federation.proposalsFolder = proposals*<br/>
+    **New:** *federation.defaultFrequency = 60 mins*<br/>
+    **New:** *federation.sets =*<br/>
+    **New:** *mail.* settings for sending emails<br/>
+    **New:** user role *#notfederated* to prevent a user account from being pulled by a federated Gitblit instance
+- added: google-gson dependency
+- added: javamail dependency
+- updated: MarkdownPapers 1.1.1
+- updated: Wicket 1.4.18
+- updated: JGit 1.1.0
+- fixed: syndication urls for WAR deployments
+- fixed: authentication for zip downloads
+
+<hr/>
+
+**0.5.2** &nbsp; *released 2011-07-27*
+
+- fixed: active repositories with a HEAD that pointed to an empty branch caused internal errors (issue 14)
+- fixed: bare-cloned repositories were listed as (empty) and were not clickable (issue 13)
+- fixed: default port for Gitblit GO is now 8443 to be more linux/os x friendly (issue 12)
+- fixed: repositories can now be reliably deleted and renamed (issue 10)
+- fixed: users can now change their passwords (issue 1)
+- fixed: always show root repository group first, i.e. don't sort root group with other groups
+- fixed: tone-down repository group header color
+- added: optionally display repository on-disk size on repositories page<br/>
+    **New:** *web.showRepositorySizes = true*
+- added: forward-slashes ('/', %2F) can be encoded using a custom character to workaround some servlet container default security measures for proxy servers<br/>
+    **New:** *web.forwardSlashCharacter = /*
+- updated: MarkdownPapers 1.1.0
+- updated: Jetty 7.4.3
+
+<hr/>
+
+**0.5.1** &nbsp; *released 2011-06-28*
+
+- clarified SSL certificate generation and configuration for both server-side and client-side
+- added some more troubleshooting information to documentation
+- replaced JavaService with Apache Commons Daemon
+
+<hr/>
+
+**0.5.0** &nbsp; *released 2011-06-26*
+
+- initial release
+
+[jgit]: http://eclipse.org/jgit "Eclipse JGit Site"
diff --git a/docs/architecture.png b/src/site/resources/architecture.png
similarity index 100%
rename from docs/architecture.png
rename to src/site/resources/architecture.png
Binary files differ
diff --git a/docs/fed_aggregation.png b/src/site/resources/fed_aggregation.png
similarity index 100%
rename from docs/fed_aggregation.png
rename to src/site/resources/fed_aggregation.png
Binary files differ
diff --git a/docs/fed_mirror.png b/src/site/resources/fed_mirror.png
similarity index 100%
rename from docs/fed_mirror.png
rename to src/site/resources/fed_mirror.png
Binary files differ
diff --git a/docs/ldapSample.png b/src/site/resources/ldapSample.png
similarity index 100%
rename from docs/ldapSample.png
rename to src/site/resources/ldapSample.png
Binary files differ
diff --git a/docs/permissions_matrix.png b/src/site/resources/permissions_matrix.png
similarity index 100%
rename from docs/permissions_matrix.png
rename to src/site/resources/permissions_matrix.png
Binary files differ
diff --git a/docs/screenshots.js b/src/site/resources/screenshots.js
similarity index 100%
rename from docs/screenshots.js
rename to src/site/resources/screenshots.js
diff --git a/docs/stjude_150x150.gif b/src/site/resources/stjude_150x150.gif
similarity index 100%
rename from docs/stjude_150x150.gif
rename to src/site/resources/stjude_150x150.gif
Binary files differ
diff --git a/src/site/roadmap.mkd b/src/site/roadmap.mkd
new file mode 100644
index 0000000..8b3e31f
--- /dev/null
+++ b/src/site/roadmap.mkd
@@ -0,0 +1,17 @@
+## Roadmap
+
+This is not exactly a formal roadmap but it is a priority list of what might be implemented in future releases.  
+This list is volatile and may not reflect what will be in the next release.
+
+* Integrate an SSH daemon to complete the transport trifecta: http/https, git, and ssh
+* GitHub-style Pull Requests or Gerrit-style Patchsets (issue 215)
+* Clone/Mirror Repository feature (issue 5)
+    * optional scheduled pulls
+    * optional automatic push to origin/remotes?
+    * optional manual push to origin/remotes?
+* Re-use the EGit branch visualization table cell renderer as some sort of servlet (issue 194)
+* Diff should highlight inserted/removed fragment compared to original line
+* Respect Gerrit branch permissions, if found (issue 36)
+* Repository regex substitutions should be stored in .git/.config, not gitblit.properties
+* Editable settings page in GO/WAR
+* Create Eclipse plugin to enumerate repositories and delegate cloning to EGit
diff --git a/src/site/rpc.mkd b/src/site/rpc.mkd
new file mode 100644
index 0000000..26575d9
--- /dev/null
+++ b/src/site/rpc.mkd
@@ -0,0 +1,295 @@
+## Remote Management, Administration and Integration
+
+*SINCE 0.7.0*
+
+Gitblit optionally allows a remote client to administer the Gitblit server.  This client could be a Java-based tool or perhaps a tool written in another language.
+
+    web.enableRpcServlet=true
+    web.enableRpcManagement=false
+    web.enableRpcAdministration=false
+
+**https** is strongly recommended because passwords are insecurely transmitted form your browser/rpc client using Basic authentication!
+
+The Gitblit JSON RPC mechanism, like the Gitblit JGit servlet, syndication/feed servlet, etc, supports request-based authentication.  Making an *admin* request will trigger Gitblit's basic authentication mechanism.  Listing of repositories, generally, will not trigger this authentication mechanism unless *web.authenticateViewPages=true*.  That means its possible to allow anonymous enumeration of repositories that are not *view restricted* or *clone restricted*.  Of course, if credentials are provided then all private repositories that are available to the user account will be enumerated in the JSON response.
+
+### Gitblit Manager
+
+[Gitblit Manager](http://code.google.com/p/gitblit/downloads/detail?name=%MANAGER%) is an example Java/Swing application that allows remote management (repository and user objects) and administration (server settings) of a Gitblit server.
+  
+This application uses a combination of RSS feeds and the JSON RPC interface, both of which are part of the [Gitblit API](http://code.google.com/p/gitblit/downloads/detail?name=%API%) library, to present live information from a Gitblit server.  Some JSON RPC methods from the utility class `com.gitblit.utils.RpcUtils` are not currently used by the Gitblit Manager.
+
+**NOTE:**  
+Gitblit Manager stores your login credentials **INSECURELY** in homedir/.gitblit/config.
+
+### Eclipse/EGit "Import from Gitblit" Feature (Planning)
+
+One obvious goal of a Gitblit RPC mechanism would be to have an Eclipse/EGit Feature that allows authentication and enumeration of Gitblit repositories from the Eclipse *Import...* menu.  Batch cloning would be supported and delegated to EGit.
+
+This particular project should not be difficult as the only external dependency for `com.gitblit.utils.RpcUtils` is [google-gson](http://google-gson.googlecode.com) which is already a dependency of the EGit/GitHub Mylyn feature.
+
+One proposal from the EGit team is to define a common JSON RPC method for enumeration of repositories which can be implemented by Git hosts.  The EGit team would then implement the UI and the client-side enumeration code.  This idea was raised as part of this [feature request for EGit](https://bugs.eclipse.org/bugs/show_bug.cgi?id=361251).
+
+Currently this project is in the planning stage.
+
+## RSS Query Interface
+
+At present, Gitblit does not yet support retrieving Git objects (commits, etc) via the JSON RPC mechanism.  However, the repository/branch RSS feeds can be used to extract log/history information from a repository branch.
+
+The Gitblit API includes methods for retrieving and interpreting RSS feeds.  The Gitblit Manager uses these methods to allow branch activity monitoring and repository searching.
+
+<table class="table">
+<tr><th>url parameter</th><th>default</th><th>description</th></tr>
+<tr><td colspan='3'><b>standard query</b></td></tr>
+<tr><td><em>repository</em></td><td><em>required</em></td><td>repository name is part of the url (see examples below)</td></tr>
+<tr><td>h=</td><td><em>optional</em><br/>default: HEAD</td><td>starting branch, ref, or commit id</td></tr>
+<tr><td>l=</td><td><em>optional</em><br/>default: web.syndicationEntries</td><td>maximum return count</td></tr>
+<tr><td>pg=</td><td><em>optional</em><br/>default: 0</td><td>page number for paging<br/>(offset into history = pagenumber*maximum return count)</td></tr>
+<tr><td colspan='3'><b>search query</b></td></tr>
+<tr><td>s=</td><td><em>required</em></td><td>search string</td></tr>
+<tr><td>st=</td><td><em>optional</em><br/>default: COMMIT</td><td>search type</td></tr>
+</table>
+
+### Example RSS Queries
+
+    https://localhost:8443/feed/gitblit.git?l=50&h=refs/heads/master
+    https://localhost:8443/feed/gitblit.git?l=50&h=refs/heads/master&s=documentation
+    https://localhost:8443/feed/gitblit.git?l=50&h=refs/heads/master&s=james&st=author&pg=2
+
+## JSON Remote Procedure Call (RPC) Interface
+
+### RPC Protocol Versions
+<table class="table">
+<tbody>
+<tr><th>Release</th><th>Protocol Version</th></tr>
+<tr><td>Gitblit v0.7.0</td><td>1 (inferred version)</td></tr>
+<tr><td>Gitblit v0.8.0</td><td>2</td></tr>
+<tr><td>Gitblit v0.9.0 - v1.0.0</td><td>3</td></tr>
+<tr><td>Gitblit v1.1.0</td><td>4</td></tr>
+<tr><td>Gitblit v1.2.0+</td><td>5</td></tr>
+</tbody>
+</table>
+
+#### Protocol Version 5
+
+- *SET_REPOSITORY_MEMBERS* will reject all calls because this would elevate all discrete permissions to RW+  
+Use *SET_REPOSITORY_MEMBER_PERMISSIONS* instead.
+- *SET_REPOSITORY_TEAMS* will reject all calls because this would elevate all discrete permissions to RW+  
+Use *SET_REPOSITORY_TEAM_PERMISSIONS* instead.
+
+### RPC Request and Response Types
+<table class="table">
+<tr><th colspan='2'>url parameters</th><th rowspan='2'>required<br/>user<br/>permission</th><th rowspan='2'>protocol<br/>version</th><th colspan='2'>json</th></tr>
+<tr><th>req=</th><th>name=</th><th>post body</th><th>response body</th></tr>
+<tr><td colspan='6'><em>web.enableRpcServlet=true</em></td></tr>
+<tr><td>GET_PROTOCOL</td><td>-</td><td>-</td><td>2</td><td>-</td><td>Integer</td></tr>
+<tr><td>LIST_REPOSITORIES</td><td>-</td><td>-</td><td>1</td><td>-</td><td>Map&lt;String, RepositoryModel&gt;</td></tr>
+<tr><td>LIST_BRANCHES</td><td>-</td><td>-</td><td>1</td><td>-</td><td>Map&lt;String, List&lt;String&gt;&gt;</td></tr>
+<tr><td>LIST_SETTINGS</td><td>-</td><td><em>-</em></td><td>1</td><td>-</td><td>ServerSettings (basic keys)</td></tr>
+<tr><td colspan='6'><em>web.enableRpcManagement=true</em></td></tr>
+<tr><td>CREATE_REPOSITORY</td><td>repository name</td><td><em>admin</em></td><td>1</td><td>RepositoryModel</td><td>-</td></tr>
+<tr><td>EDIT_REPOSITORY</td><td>repository name</td><td><em>admin</em></td><td>1</td><td>RepositoryModel</td><td>-</td></tr>
+<tr><td>DELETE_REPOSITORY</td><td>repository name</td><td><em>admin</em></td><td>1</td><td>RepositoryModel</td><td>-</td></tr>
+<tr><td>LIST_USERS</td><td>-</td><td><em>admin</em></td><td>1</td><td>-</td><td>List&lt;UserModel&gt;</td></tr>
+<tr><td>CREATE_USER</td><td>user name</td><td><em>admin</em></td><td>1</td><td>UserModel</td><td>-</td></tr>
+<tr><td>EDIT_USER</td><td>user name</td><td><em>admin</em></td><td>1</td><td>UserModel</td><td>-</td></tr>
+<tr><td>DELETE_USER</td><td>user name</td><td><em>admin</em></td><td>1</td><td>UserModel</td><td>-</td></tr>
+<tr><td>LIST_TEAMS</td><td>-</td><td><em>admin</em></td><td>2</td><td>-</td><td>List&lt;TeamModel&gt;</td></tr>
+<tr><td>CREATE_TEAM</td><td>team name</td><td><em>admin</em></td><td>2</td><td>TeamModel</td><td>-</td></tr>
+<tr><td>EDIT_TEAM</td><td>team name</td><td><em>admin</em></td><td>2</td><td>TeamModel</td><td>-</td></tr>
+<tr><td>DELETE_TEAM</td><td>team name</td><td><em>admin</em></td><td>2</td><td>TeamModel</td><td>-</td></tr>
+<tr><td>LIST_REPOSITORY_MEMBERS</td><td>repository name</td><td><em>admin</em></td><td>1</td><td>-</td><td>List&lt;String&gt;</td></tr>
+<tr><td><s>SET_REPOSITORY_MEMBERS</s></td><td><s>repository name</s></td><td><em><s>admin</s></em></td><td><s>1</s></td><td><s>List&lt;String&gt;</s></td><td>-</td></tr>
+<tr><td>LIST_REPOSITORY_MEMBER_PERMISSIONS</td><td>repository name</td><td><em>admin</em></td><td>5</td><td>-</td><td>List&lt;String&gt;</td></tr>
+<tr><td>SET_REPOSITORY_MEMBER_PERMISSIONS</td><td>repository name</td><td><em>admin</em></td><td>5</td><td>List&lt;String&gt;</td><td>-</td></tr>
+<tr><td>LIST_REPOSITORY_TEAMS</td><td>repository name</td><td><em>admin</em></td><td>2</td><td>-</td><td>List&lt;String&gt;</td></tr>
+<tr><td><s>SET_REPOSITORY_TEAMS</s></td><td><s>repository name</s></td><td><em><s>admin</s></em></td><td><s>2</s></td><td><s>List&lt;String&gt;</s></td><td>-</td></tr>
+<tr><td>LIST_REPOSITORY_TEAM_PERMISSIONS</td><td>repository name</td><td><em>admin</em></td><td>5</td><td>-</td><td>List&lt;String&gt;</td></tr>
+<tr><td>SET_REPOSITORY_TEAM_PERMISSIONS</td><td>repository name</td><td><em>admin</em></td><td>5</td><td>List&lt;String&gt;</td><td>-</td></tr>
+<tr><td>LIST_SETTINGS</td><td>-</td><td><em>admin</em></td><td>1</td><td>-</td><td>ServerSettings (management keys)</td></tr>
+<tr><td>CLEAR_REPOSITORY_CACHE</td><td>-</td><td><em>-</em></td><td>4</td><td>-</td><td>-</td></tr>
+<tr><td colspan='6'><em>web.enableRpcAdministration=true</em></td></tr>
+<tr><td>LIST_FEDERATION_REGISTRATIONS</td><td>-</td><td><em>admin</em></td><td>1</td><td>-</td><td>List&lt;FederationModel&gt;</td></tr>
+<tr><td>LIST_FEDERATION_RESULTS</td><td>-</td><td><em>admin</em></td><td>1</td><td>-</td><td>List&lt;FederationModel&gt;</td></tr>
+<tr><td>LIST_FEDERATION_PROPOSALS</td><td>-</td><td><em>admin</em></td><td>1</td><td>-</td><td>List&lt;FederationProposal&gt;</td></tr>
+<tr><td>LIST_FEDERATION_SETS</td><td>-</td><td><em>admin</em></td><td>1</td><td>-</td><td>List&lt;FederationSet&gt;</td></tr>
+<tr><td>LIST_SETTINGS</td><td>-</td><td><em>admin</em></td><td>1</td><td>-</td><td>ServerSettings (all keys)</td></tr>
+<tr><td>EDIT_SETTINGS</td><td>-</td><td><em>admin</em></td><td>1</td><td>Map&lt;String, String&gt;</td><td>-</td></tr>
+<tr><td>LIST_STATUS</td><td>-</td><td><em>admin</em></td><td>1</td><td>-</td><td>ServerStatus (see example below)</td></tr>
+</table>
+
+### RPC/HTTP Response Codes
+<table class="table">
+<tr><th>code</th><th>name</th><th>description</th></tr>
+<tr><td>200</td><td>success</td><td>Gitblit processed the request successfully</td></tr>
+<tr><td>401</td><td>unauthorized</td><td>Gitblit requires user credentials to process the request</td></tr>
+<tr><td>403</td><td>forbidden</td><td>Gitblit can not process the request for the supplied credentials</td></tr>
+<tr><td>405</td><td>method not allowed</td><td>Gitblit has disallowed the processing the specified request</td></tr>
+<tr><td>500</td><td>server error</td><td>Gitblit failed to process the request likely because the input object created a conflict</td></tr>
+<tr><td>501</td><td>unknown request</td><td>Gitblit does not recognize the RPC request type</td></tr>
+</table>
+
+### Example: LIST_REPOSITORIES
+
+**url**: https://localhost/rpc?req=LIST_REPOSITORIES  
+**response body**: Map&lt;String, RepositoryModel&gt; where the map key is the clone url of the repository
+<pre>
+{
+  "https://localhost/git/libraries/xmlapache.git": {
+    "name": "libraries/xmlapache.git",
+    "description": "apache xmlrpc client and server",
+    "owner": "admin",
+    "lastChange": "2010-01-28T22:12:06Z",
+    "hasCommits": true,
+    "showRemoteBranches": false,
+    "useTickets": false,
+    "useDocs": false,
+    "accessRestriction": "VIEW",
+    "isFrozen": false,
+    "showReadme": false,
+    "federationStrategy": "FEDERATE_THIS",
+    "federationSets": [
+      "libraries"
+    ],
+    "isFederated": false,
+    "skipSizeCalculation": false,
+    "skipSummaryMetrics": false,
+    "size": "102 KB"
+  },
+  "https://localhost/git/libraries/smack.git": {
+    "name": "libraries/smack.git",
+    "description": "smack xmpp client",
+    "owner": "admin",
+    "lastChange": "2009-01-28T18:38:14Z",
+    "hasCommits": true,
+    "showRemoteBranches": false,
+    "useTickets": false,
+    "useDocs": false,
+    "accessRestriction": "VIEW",
+    "isFrozen": false,
+    "showReadme": false,
+    "federationStrategy": "FEDERATE_THIS",
+    "federationSets": [],
+    "isFederated": false,
+    "skipSizeCalculation": false,
+    "skipSummaryMetrics": false,
+    "size": "4.8 MB"
+  }
+}
+</pre>
+
+### Example: EDIT_REPOSITORY (rename)
+
+The original repository name is specified in the *name* url parameter.  The new name is set within the JSON object.
+
+**url**: https://localhost/rpc?req=EDIT_REPOSITORY&name=libraries/xmlapache.git  
+**post body**: RepositoryModel
+<pre>
+{
+    "name": "libraries/xmlapache-renamed.git",
+    "description": "apache xmlrpc client and server",
+    "owner": "admin",
+    "lastChange": "2010-01-28T22:12:06Z",
+    "hasCommits": true,
+    "showRemoteBranches": false,
+    "useTickets": false,
+    "useDocs": false,
+    "accessRestriction": "VIEW",
+    "isFrozen": false,
+    "showReadme": false,
+    "federationStrategy": "FEDERATE_THIS",
+    "federationSets": [
+      "libraries"
+    ],
+    "isFederated": false,
+    "skipSizeCalculation": false,
+    "skipSummaryMetrics": false,
+    "size": "102 KB"
+}
+</pre>
+
+### Example: LIST_USERS
+**url**: https://localhost/rpc?req=LIST_USERS  
+**response body**: List&lt;UserModel&gt;
+<pre>
+[
+  {
+    "username": "admin",
+    "password": "admin",
+    "canAdmin": true,
+    "excludeFromFederation": true,
+    "repositories": []
+  },
+  {
+    "username": "test",
+    "password": "test",
+    "canAdmin": false,
+    "excludeFromFederation": false,
+    "repositories": [
+      "libraries/xmlapache.git",
+      "libraries/smack.git"
+    ]
+  }
+]
+</pre>
+
+### Example: LIST_SETTINGS
+**url**: https://localhost/rpc?req=LIST_SETTINGS  
+**response body**: ServerSettings
+<pre>
+{
+  "settings": {
+      "web.siteName": {
+        "name": "web.siteName",
+        "currentValue": "",
+        "defaultValue": "",
+        "description": "Gitblit Web Settings\nIf blank Gitblit is displayed.",
+        "since": "0.5.0",
+        "caseSensitive": false,
+        "restartRequired": false,
+        "spaceDelimited": false
+      },
+      "web.summaryCommitCount": {
+        "name": "web.summaryCommitCount",
+        "currentValue": "16",
+        "defaultValue": "16",
+        "description": "The number of commits to display on the summary page\nValue must exceed 0 else default of 16 is used",
+        "since": "0.5.0",
+        "caseSensitive": false,
+        "restartRequired": false,
+        "spaceDelimited": false
+      }
+  }
+}
+</pre>
+
+### Example: LIST_STATUS
+**url**: https://localhost/rpc?req=LIST_STATUS  
+**response body**: ServerStatus
+<pre>
+{
+  "bootDate": "2011-10-22T12:13:00Z",
+  "version": "0.7.0-SNAPSHOT",
+  "releaseDate": "PENDING",
+  "isGO": true,
+  "systemProperties": {
+    "file.encoding": "Cp1252",
+    "java.home": "C:\\Program Files\\Java\\jdk1.6.0_26\\jre",
+    "java.io.tmpdir": "C:\\Users\\JAMESM~1\\AppData\\Local\\Temp\\",
+    "java.runtime.name": "Java(TM) SE Runtime Environment",
+    "java.runtime.version": "1.6.0_26-b03",
+    "java.vendor": "Sun Microsystems Inc.",
+    "java.version": "1.6.0_26",
+    "java.vm.info": "mixed mode",
+    "java.vm.name": "Java HotSpot(TM) 64-Bit Server VM",
+    "java.vm.vendor": "Sun Microsystems Inc.",
+    "java.vm.version": "20.1-b02",
+    "os.arch": "amd64",
+    "os.name": "Windows 7",
+    "os.version": "6.1"
+  },
+  "heapAllocated": 128057344,
+  "heapFree": 120399168,
+  "heapSize": 1899560960,
+  "servletContainer": "jetty/7.4.3.v20110701"
+}
+</pre>
\ No newline at end of file
diff --git a/src/site/screenshots.mkd b/src/site/screenshots.mkd
new file mode 100644
index 0000000..b18408a
--- /dev/null
+++ b/src/site/screenshots.mkd
@@ -0,0 +1,143 @@
+## Screenshots
+
+### Gitblit GO/WAR
+
+<ul class="thumbnails">
+<li class="span3">
+	<a class="thumbnail" rel="screenshots_group" href="screenshots/00.png" title="Repository List"><img alt="Repositories" src="thumbs/00.png" /></a>
+	<h5>Repository List</h5>
+</li>
+<li class="span3">
+	<a class="thumbnail" rel="screenshots_group" href="screenshots/00b.png" title="Repository List (Admin)"><img alt="Repositories (Admin)" src="thumbs/00b.png" /></a>
+	<h5>Repository List (Admin)</h5>
+</li>
+<li class="span3">
+	<a class="thumbnail" rel="screenshots_group" href="screenshots/00c.png" title="Activity"><img alt="Activity" src="thumbs/00c.png" /></a>
+	<h5>Activity</h5>
+</li>
+<li class="span3">
+	<a class="thumbnail" rel="screenshots_group" href="screenshots/00d.png" title="Lucene Search"><img alt="Lucene Search" src="thumbs/00d.png" /></a>
+	<h5>Lucene Search</h5>
+</li>
+<li class="span3">
+	<a class="thumbnail" rel="screenshots_group" href="screenshots/01c.png" title="Users &amp; Teams"><img alt="Users &amp; Teams" src="thumbs/01c.png" /></a>
+	<h5>Users &amp; Teams</h5>
+</li>
+<li class="span3">
+	<a class="thumbnail" rel="screenshots_group" href="screenshots/01.png" title="New User"><img alt="New User" src="thumbs/01.png" /></a>
+	<h5>New User</h5>
+</li>
+<li class="span3">
+	<a class="thumbnail" rel="screenshots_group" href="screenshots/01b.png" title="New Team"><img alt="New Team" src="thumbs/01b.png" /></a>
+	<h5>New Team</h5>
+</li>
+<li class="span3">
+	<a class="thumbnail" rel="screenshots_group" href="screenshots/02.png" title="Edit Repository"><img alt="Edit Repository" src="thumbs/02.png" /></a>
+	<h5>Edit Repository</h5>
+</li>
+<li class="span3">
+	<a class="thumbnail" rel="screenshots_group" href="screenshots/03.png" title="Repository Summary"><img alt="Summary" src="thumbs/03.png" /></a>
+	<h5>Repository Summary</h5>
+</li>
+<li class="span3">
+	<a class="thumbnail" rel="screenshots_group" href="screenshots/04.png" title="Repository Log"><img alt="Log" src="thumbs/04.png" /></a>
+	<h5>Repository Log</h5>
+</li>
+<li class="span3">
+	<a class="thumbnail" rel="screenshots_group" href="screenshots/05.png" title="Repository Tree"><img alt="Tree" src="thumbs/05.png" /></a>
+	<h5>Repository Tree</h5>
+</li>
+<li class="span3">
+	<a class="thumbnail" rel="screenshots_group" href="screenshots/06.png" title="Commit Page"><img alt="Commit Page" src="thumbs/06.png" /></a>
+	<h5>Commit Page</h5>
+</li>
+<li class="span3">
+	<a class="thumbnail" rel="screenshots_group" href="screenshots/07.png" title="Commit Diff"><img alt="Commit Diff" src="thumbs/07.png" /></a>
+	<h5>Commit Diff</h5>
+</li>
+<li class="span3">
+	<a class="thumbnail" rel="screenshots_group" href="screenshots/09.png" title="Branch Metrics"><img alt="Metrics" src="thumbs/09.png" /></a>
+	<h5>Branch Metrics</h5>
+</li>
+<li class="span3">
+	<a class="thumbnail" rel="screenshots_group" href="screenshots/08.png" title="Blob View with Syntax Highlighting"><img alt="Blob" src="thumbs/08.png" /></a>
+	<h5>Blob View with Syntax Highlighting</h5>
+</li>
+<li class="span3">
+	<a class="thumbnail" rel="screenshots_group" href="screenshots/11.png" title="Blame"><img alt="Blame" src="thumbs/11.png" /></a>
+	<h5>Blame</h5>
+</li>
+<li class="span3">
+	<a class="thumbnail" rel="screenshots_group" href="screenshots/12.png" title="Federation Panels"><img alt="Federation Panels" src="thumbs/12.png" /></a>
+	<h5>Federation Panels</h5>
+</li>
+<li class="span3">
+	<a class="thumbnail" rel="screenshots_group" href="screenshots/13.png" title="Detailed Status of a Registration"><img alt="Registration Status" src="thumbs/13.png" /></a>
+	<h5>Detailed Status of a Registration</h5>
+</li>
+<li class="span3">
+	<a class="thumbnail" rel="screenshots_group" href="screenshots/14.png" title="Send Proposal"><img alt="Propose" src="thumbs/14.png" /></a>
+	<h5>Send Proposal</h5>
+</li>
+<li class="span3">
+	<a class="thumbnail" rel="screenshots_group" href="screenshots/15.png" title="Empty Repository"><img alt="Empty Repository" src="thumbs/15.png" /></a>
+	<h5>Empty Repository</h5>
+</li>
+</ul>
+
+### Gitblit Manager
+
+<ul class="thumbnails">
+<li class="span3">
+	<a class="thumbnail" rel="screenshots_group" href="screenshots/m00.png" title="Repositories Panel"><img alt="Repositories Panel" src="thumbs/m00.png" /></a>
+	<h5>Repositories Panel</h5>
+</li>
+<li class="span3">
+	<a class="thumbnail" rel="screenshots_group" href="screenshots/m01.png" title="Search Dialog"><img alt="Search Dialog" src="thumbs/m01.png" /></a>
+	<h5>Search Dialog</h5>
+</li>
+<li class="span3">
+	<a class="thumbnail" rel="screenshots_group" href="screenshots/m02.png" title="Activity Panel"><img alt="Activity Panel" src="thumbs/m02.png" /></a>
+	<h5>Activity Panel</h5>
+</li>
+<li class="span3">
+	<a class="thumbnail" rel="screenshots_group" href="screenshots/m03.png" title="Subscriptions Dialog"><img alt="Subscriptions Dialog" src="thumbs/m03.png" /></a>
+	<h5>Subscriptions Dialog</h5>
+</li>
+<li class="span3">
+	<a class="thumbnail" rel="screenshots_group" href="screenshots/m04.png" title="Users Panel"><img alt="Users Panels" src="thumbs/m04.png" /></a>
+	<h5>Users Panel</h5>
+</li>
+<li class="span3">
+	<a rel="screenshots_group" href="screenshots/m05.png" title="Settings Panel"><img alt="Settings Panel" src="thumbs/m05.png" /></a>
+	<h5>Settings Panel</h5>
+</li>
+<li class="span3">
+	<a class="thumbnail" rel="screenshots_group" href="screenshots/m06.png" title="Status Panel"><img alt="Status Panel" src="thumbs/m06.png" /></a>
+	<h5>Status Panel</h5>
+</li>
+</ul>
+
+<ul class="thumbnails">
+<li class="span3">
+	<a class="thumbnail" rel="screenshots_group" href="screenshots/m07.png" title="Edit Repository Settings"><img alt="Repository Settings" src="thumbs/m07.png" /></a>
+	<h5>Edit Repository Settings</h5>
+</li>
+<li class="span3">
+	<a class="thumbnail" rel="screenshots_group" href="screenshots/m08.png" title="Edit Repository Access Restrictions"><img alt="Access Restrictions" src="thumbs/m08.png" /></a>
+	<h5>Edit Repository Access Restriction</h5>
+</li>
+<li class="span3">
+	<a class="thumbnail" rel="screenshots_group" href="screenshots/m09.png" title="Edit Repository Federation Settings"><img alt="Federation Settings" src="thumbs/m09.png" /></a>
+	<h5>Edit Repository Federation Settings</h5>
+</li>
+<li class="span3">
+	<a class="thumbnail" rel="screenshots_group" href="screenshots/m10.png" title="Edit User Dialog"><img alt="Edit User Dialog" src="thumbs/m10.png" /></a>
+	<h5>Edit User Dialog</h5>
+</li>
+</ul>
+
+<script type="text/javascript" src="./fancybox/jquery.mousewheel-3.0.4.pack.js"> </script>
+<script type="text/javascript" src="./fancybox/jquery.fancybox-1.3.4.pack.js"> </script>
+<link rel="stylesheet" type="text/css" href="./fancybox/jquery.fancybox-1.3.4.css" media="screen" />
+<script type="text/javascript" src="./screenshots.js"> </script>	
diff --git a/docs/screenshots/00.png b/src/site/screenshots/00.png
similarity index 100%
rename from docs/screenshots/00.png
rename to src/site/screenshots/00.png
Binary files differ
diff --git a/docs/screenshots/00b.png b/src/site/screenshots/00b.png
similarity index 100%
rename from docs/screenshots/00b.png
rename to src/site/screenshots/00b.png
Binary files differ
diff --git a/docs/screenshots/00c.png b/src/site/screenshots/00c.png
similarity index 100%
rename from docs/screenshots/00c.png
rename to src/site/screenshots/00c.png
Binary files differ
diff --git a/docs/screenshots/00d.png b/src/site/screenshots/00d.png
similarity index 100%
rename from docs/screenshots/00d.png
rename to src/site/screenshots/00d.png
Binary files differ
diff --git a/docs/screenshots/01.png b/src/site/screenshots/01.png
similarity index 100%
rename from docs/screenshots/01.png
rename to src/site/screenshots/01.png
Binary files differ
diff --git a/docs/screenshots/01b.png b/src/site/screenshots/01b.png
similarity index 100%
rename from docs/screenshots/01b.png
rename to src/site/screenshots/01b.png
Binary files differ
diff --git a/docs/screenshots/01c.png b/src/site/screenshots/01c.png
similarity index 100%
rename from docs/screenshots/01c.png
rename to src/site/screenshots/01c.png
Binary files differ
diff --git a/docs/screenshots/02.png b/src/site/screenshots/02.png
similarity index 100%
rename from docs/screenshots/02.png
rename to src/site/screenshots/02.png
Binary files differ
diff --git a/docs/screenshots/03.png b/src/site/screenshots/03.png
similarity index 100%
rename from docs/screenshots/03.png
rename to src/site/screenshots/03.png
Binary files differ
diff --git a/docs/screenshots/04.png b/src/site/screenshots/04.png
similarity index 100%
rename from docs/screenshots/04.png
rename to src/site/screenshots/04.png
Binary files differ
diff --git a/docs/screenshots/05.png b/src/site/screenshots/05.png
similarity index 100%
rename from docs/screenshots/05.png
rename to src/site/screenshots/05.png
Binary files differ
diff --git a/docs/screenshots/06.png b/src/site/screenshots/06.png
similarity index 100%
rename from docs/screenshots/06.png
rename to src/site/screenshots/06.png
Binary files differ
diff --git a/docs/screenshots/07.png b/src/site/screenshots/07.png
similarity index 100%
rename from docs/screenshots/07.png
rename to src/site/screenshots/07.png
Binary files differ
diff --git a/docs/screenshots/08.png b/src/site/screenshots/08.png
similarity index 100%
rename from docs/screenshots/08.png
rename to src/site/screenshots/08.png
Binary files differ
diff --git a/docs/screenshots/09.png b/src/site/screenshots/09.png
similarity index 100%
rename from docs/screenshots/09.png
rename to src/site/screenshots/09.png
Binary files differ
diff --git a/docs/screenshots/10.png b/src/site/screenshots/10.png
similarity index 100%
rename from docs/screenshots/10.png
rename to src/site/screenshots/10.png
Binary files differ
diff --git a/docs/screenshots/11.png b/src/site/screenshots/11.png
similarity index 100%
rename from docs/screenshots/11.png
rename to src/site/screenshots/11.png
Binary files differ
diff --git a/docs/screenshots/12.png b/src/site/screenshots/12.png
similarity index 100%
rename from docs/screenshots/12.png
rename to src/site/screenshots/12.png
Binary files differ
diff --git a/docs/screenshots/13.png b/src/site/screenshots/13.png
similarity index 100%
rename from docs/screenshots/13.png
rename to src/site/screenshots/13.png
Binary files differ
diff --git a/docs/screenshots/14.png b/src/site/screenshots/14.png
similarity index 100%
rename from docs/screenshots/14.png
rename to src/site/screenshots/14.png
Binary files differ
diff --git a/docs/screenshots/15.png b/src/site/screenshots/15.png
similarity index 100%
rename from docs/screenshots/15.png
rename to src/site/screenshots/15.png
Binary files differ
diff --git a/docs/screenshots/image_processing.txt b/src/site/screenshots/image_processing.txt
similarity index 100%
rename from docs/screenshots/image_processing.txt
rename to src/site/screenshots/image_processing.txt
diff --git a/docs/screenshots/m00.png b/src/site/screenshots/m00.png
similarity index 100%
rename from docs/screenshots/m00.png
rename to src/site/screenshots/m00.png
Binary files differ
diff --git a/docs/screenshots/m01.png b/src/site/screenshots/m01.png
similarity index 100%
rename from docs/screenshots/m01.png
rename to src/site/screenshots/m01.png
Binary files differ
diff --git a/docs/screenshots/m02.png b/src/site/screenshots/m02.png
similarity index 100%
rename from docs/screenshots/m02.png
rename to src/site/screenshots/m02.png
Binary files differ
diff --git a/docs/screenshots/m03.png b/src/site/screenshots/m03.png
similarity index 100%
rename from docs/screenshots/m03.png
rename to src/site/screenshots/m03.png
Binary files differ
diff --git a/docs/screenshots/m04.png b/src/site/screenshots/m04.png
similarity index 100%
rename from docs/screenshots/m04.png
rename to src/site/screenshots/m04.png
Binary files differ
diff --git a/docs/screenshots/m05.png b/src/site/screenshots/m05.png
similarity index 100%
rename from docs/screenshots/m05.png
rename to src/site/screenshots/m05.png
Binary files differ
diff --git a/docs/screenshots/m06.png b/src/site/screenshots/m06.png
similarity index 100%
rename from docs/screenshots/m06.png
rename to src/site/screenshots/m06.png
Binary files differ
diff --git a/docs/screenshots/m07.png b/src/site/screenshots/m07.png
similarity index 100%
rename from docs/screenshots/m07.png
rename to src/site/screenshots/m07.png
Binary files differ
diff --git a/docs/screenshots/m08.png b/src/site/screenshots/m08.png
similarity index 100%
rename from docs/screenshots/m08.png
rename to src/site/screenshots/m08.png
Binary files differ
diff --git a/docs/screenshots/m09.png b/src/site/screenshots/m09.png
similarity index 100%
rename from docs/screenshots/m09.png
rename to src/site/screenshots/m09.png
Binary files differ
diff --git a/docs/screenshots/m10.png b/src/site/screenshots/m10.png
similarity index 100%
rename from docs/screenshots/m10.png
rename to src/site/screenshots/m10.png
Binary files differ
diff --git a/src/site/setup_authentication.mkd b/src/site/setup_authentication.mkd
new file mode 100644
index 0000000..7f60618
--- /dev/null
+++ b/src/site/setup_authentication.mkd
@@ -0,0 +1,125 @@
+## Built-in Authentication
+
+By default, Gitblit stores and authenticates all users against `users.conf`.  However, you may wish to integrate Gitblit into an existing user account infrastructure.
+
+Gitblit supports additional authentication mechanisms aside from it's internal one.
+
+* LDAP authentication
+* Windows authentication
+* Redmine auhentication
+* Salesforce.com authentication
+* Servlet container authentication
+
+### LDAP Authentication
+*SINCE 1.0.0*
+
+LDAP can be used to authenticate Users and optionally control Team memberships.  When properly configured, Gitblit will delegate authentication to your LDAP server and will cache some user information in the usual users file (.conf or .properties).
+
+When using the LDAP User Service, new user accounts can not be manually created from Gitblit.  Gitblit user accounts are automatically created for new users on their first succesful authentication through Gitblit against the LDAP server.  It is also important to note that the LDAP User Service does not retrieve or store user passwords nor does it implement any LDAP-write functionality.
+
+To use the *LdapUserService* set *realm.userService=com.gitblit.LdapUserService* in your `gitblit.properties` file or your `web.xml` file and then configure the *realm.ldap* settings appropriately for your LDAP environment.
+
+#### Example LDAP Layout
+![block diagram](ldapSample.png "LDAP Sample")
+
+Please see [ldapUserServiceSampleData.ldif](https://github.com/gitblit/gitblit/blob/master/tests/com/gitblit/tests/resources/ldapUserServiceSampleData.ldif) to see the data in LDAP that reflects the above picture.
+
+#### Gitblit Settings for Example LDAP Layout
+The following are the settings required to configure Gitblit to authenticate against the example LDAP server with LDAP-controlled team memberships.
+
+<table class="table">
+<thead>
+<tr><th>parameter</th><th>value</th><th>description</th></tr>
+</thead>
+<tbody>
+<tr>
+  <th>realm.ldap.server</th><td>ldap://localhost:389</td>
+  <td>Tells Gitblit to connect to the LDAP server on localhost port 389.  The URL Must be of form ldap(s)://&lt;server&gt;:&lt;port&gt; with port being optional (389 for ldap, 636 for ldaps).</td>
+</tr>
+<tr>
+  <th>realm.ldap.username</th><td>cn=Directory Manager</td>
+  <td>The credentials that will log into the LDAP server</td>
+</tr>
+<tr>
+  <th>realm.ldap.password</th><td>password</td>
+  <td>The credentials that will log into the LDAP server</td>
+</tr>
+<tr>
+  <th>realm.ldap.backingUserService</th><td>users.conf</td>
+  <td>Where to store all information that is used by Gitblit.  All information will be synced here upon user login.</td>
+</tr>
+<tr>
+  <th>realm.ldap.maintainTeams</th><td>true</td>
+  <td>Are team memberships maintained in LDAP (<em>true</em>) or manually in Gitblit (<em>false</em>).</td>
+</tr>
+<tr>
+  <th>realm.ldap.accountBase</th><td>OU=Users,OU=UserControl,OU=MyOrganization,DC=MyDomain</td>
+  <td>What is the root node for all users in this LDAP system.  Subtree searches will start from this node.</td>
+</tr>
+<tr>
+  <th>realm.ldap.accountPattern</th><td>(&(objectClass=person)(sAMAccountName=${username}))</td><td>The LDAP search filter that will match a particular user in LDAP.  ${username} will be replaced with whatever the user enters as their username in the Gitblit login panel.</td>
+</tr>
+<tr>
+  <th>realm.ldap.groupBase</th><td>OU=Groups,OU=UserControl,OU=MyOrganization,DC=MyDomain</td>
+  <td>What is the root node for all teams in this LDAP system.  Subtree searches will start from this node.</td>
+</tr>
+<tr>
+  <th>realm.ldap.groupMemberPattern</th><td>(&(objectClass=group)(member=${dn}))</td><td>The LDAP search filter that will match all teams for the authenticating user.  ${username} will be replaced with whatever the user enters as their username in the Gitblit login panel.  Anything else in ${} will be replaced by Attributes from the User node.</td>
+</tr>
+<tr>
+  <th>realm.ldap.admins</th><td>@Git_Admins</td><td>A space-delimited list of usernames and/or teams that indicate admin status in Gitblit.  Teams are referenced with a leading <em>@</em> character.</td>
+</tr>
+</tbody>
+</table>
+
+#### LDAP In-Memory Server
+
+You can start Gitblit GO with an in-memory LDAP server by specifying the *--ldapLdifFile* command-line argument.  The LDAP server will listen on localhost of the port specified in *realm.ldap.url* of `gitblit.properties`.  Additionally, a root user record is automatically created for *realm.ldap.username* and *realm.ldap.password*.  Please note that the ldaps:// protocol is not supported for the in-memory server.
+
+### Windows Authentication
+
+Windows authentication is based on the use of Waffle and JNA.  It is known to work properly for authenticating against the local Windows machine, but it is unclear if it works properly with a domain controller and Active Directory.  To use this service, your Gitblit server must be installed on a Windows machine.
+
+    realm.userService = com.gitblit.WindowsUserService
+    realm.windows.defaultDomain =
+
+### Redmine Authentication
+
+You may authenticate your users against a Redmine installation as long as your Redmine install has properly enabled [API authentication](http://www.redmine.org/projects/redmine/wiki/Rest_Api#Authentication).  This user service only supports user authentication; it does not support team creation based on Redmine groups.  Redmine administrators will also be Gitblit administrators.
+
+    realm.userService = com.gitblit.RedmineUserService
+    realm.redmine.url = http://example.com/redmine
+
+### Salesforce.com Authentication
+
+You may authenticate your users against Salesforce.com.  You can require that user's belong to a particular organization by specifying a non-zero organization id.
+
+    realm.userService = com.gitblit.SalesforceUserService
+    realm.salesforce.orgId = 0
+
+### Container Authentication
+
+If you are using the WAR variant and deploying into your own servlet container which has a pre-defined authentication mechanism protecting the Gitblit webapp, then you may instruct Gitblit to automatically create Gitblit accounts for container-authenticated user principals.
+
+    realm.container.autoCreateAccounts = true
+
+## Custom Authentication
+
+This is the simplest choice where you implement custom authentication and delegate all other standard user and team operations to one of Gitblit's user service implementations.  This choice insulates your customization from changes in User and Team model classes and additional API that may be added to IUserService.
+
+Please subclass [com.gitblit.GitblitUserService](https://github.com/gitblit/gitblit/blob/master/src/main/java/com/gitblit/GitblitUserService.java) and override the *setup()* and *authenticate()* methods.  
+Make sure to set the *serviceImpl* field in your *setup()* method.
+
+You may use your subclass by specifying its fully qualified classname in the *realm.userService* setting.
+
+Your subclass must be on Gitblit's classpath and must have a public default constructor.  
+
+### Custom Everything
+
+Instead of maintaining a `users.conf` file, you may want to integrate Gitblit into an existing environment.
+
+You may use your own custom *com.gitblit.IUserService* implementation by specifying its fully qualified classname in the *realm.userService* setting.
+
+Your user service class must be on Gitblit's classpath and must have a public default constructor.  
+Please see the following interface definition [com.gitblit.IUserService](https://github.com/gitblit/gitblit/blob/master/src/main/java/com/gitblit/IUserService.java).
+
diff --git a/src/site/setup_client.mkd b/src/site/setup_client.mkd
new file mode 100644
index 0000000..684cba2
--- /dev/null
+++ b/src/site/setup_client.mkd
@@ -0,0 +1,46 @@
+
+## Client Setup and Configuration
+### Https with Self-Signed Certificates
+You must tell Git/JGit not to verify the self-signed certificate in order to perform any remote Git operations.
+
+**NOTE:**  
+The default self-signed certificate generated by Gitlbit GO is bound to *localhost*.  
+If you are using Eclipse/EGit/JGit clients, you will have to generate your own certificate that specifies the exact hostname used in your clone/push url.  
+You must do this because Eclipse/EGit/JGit (< 3.0) always verifies certificate hostnames, regardless of the *http.sslVerify=false* client-side setting. 
+ 
+- **Eclipse/EGit/JGit**
+    1. Window->Preferences->Team->Git->Configuration
+    2. Click the *New Entry* button
+    3. <pre>Key = <em>http.sslVerify</em>
+Value = <em>false</em></pre>
+- **Command-line Git** ([Git-Config Manual Page](http://www.kernel.org/pub/software/scm/git/docs/git-config.html))  
+<pre>git config --global --bool --add http.sslVerify false</pre>
+
+### Http Post Buffer Size
+You may find the default post buffer of your git client is too small to push large deltas to Gitblit.  Sometimes this can be observed on your client as *hanging* during a push.  Other times it can be observed by git erroring out with a message like: error: RPC failed; result=52, HTTP code = 0.
+
+This can be adjusted on your client by changing the default post buffer size:
+<pre>git config --global http.postBuffer 524288000</pre>
+
+### Disabling SNI
+
+You may run into SNI alerts (Server Name Indication).  These will manifest as failures to clone or push to your Gitblit instance.
+
+#### Java-based Clients
+
+When using Java 7-based clients, SNI is enabled by default.  You can disable SNI by specifying the JVM system parameter `-Djsse.enableSNIExtension=false` when your Java-based client launches.
+
+For Eclipse, you can append `-Djsse.enableSNIExtension=false` to your *eclipse.ini* file.
+
+#### Native Clients
+
+Native clients may display an error when attempting to clone or push that looks like this:
+---FIXED---
+C:\projects\git\gitblit>git push rhcloud master
+error: error:14077458:SSL routines:SSL23_GET_SERVER_HELLO:reason(1112) while accessing https://demo-gitblit.rhcloud.com/git/gitblit.git/info/refs?service=git-receive-pack
+fatal: HTTP request failed
+---FIXED---
+
+### Cloning a Repository 
+
+Gitblit provides many custom clone links for popular Git clients on the Summary page of each repository.  If you have one or more of those clients installed, you should be able to click the link to initiate cloning with the selected tool.
diff --git a/src/site/setup_clientmenus.mkd b/src/site/setup_clientmenus.mkd
new file mode 100644
index 0000000..c40c3b2
--- /dev/null
+++ b/src/site/setup_clientmenus.mkd
@@ -0,0 +1,48 @@
+## Client App Menus
+
+*SINCE 1.3.0*
+
+Gitblit supports defining menus for native platform git client clone urls.  By default, Gitblit ships with client definitions for Git, SmartGit/Hg, SourceTree, Tower, and Github for Mac & Windows.  Gitblit uses the browser's user-agent to help filter the list of available clients in addition to served transports and user access permissions.
+
+You can define new client integrations and deactivate/remove the default integrations by creating a file *$(baseFolder}/clientapps.json*.
+
+### Example definitions
+---JSON---
+[
+    {
+        "name": "SmartGit/Hg",
+        "title": "syntevo SmartGit/Hg\u2122",
+        "description": "a Git client for Windows, Mac, & Linux",
+        "legal": "\u00a9 2013 syntevo GmbH. All rights reserved.",
+        "cloneUrl": "smartgit://cloneRepo/${repoUrl}",
+        "productUrl": "http://www.syntevo.com/smartgithg",
+        "platforms": [ "windows", "macintosh", "linux" ],
+        "icon": "smartgithg_32x32.png",
+        "isActive": true
+    },
+    {
+        "name": "GitHub",
+        "title": "GitHub\u2122 for Windows",
+        "description": "a free Git client for Windows",
+        "legal": "\u00a9 2013 GitHub. All rights reserved.",
+        "cloneUrl": "github-windows://openRepo/${repoUrl}",
+        "productUrl": "http://windows.github.com",
+        "transports": [ "http", "https" ],
+        "platforms": [ "windows" ],
+        "icon": "github_32x32.png",
+        "isActive": true
+    },
+    {
+        "name": "SparkleShare",
+        "title": "SparkleShare\u2122",
+        "description": "an open source collaboration and sharing tool",
+        "legal": "released under the GPLv3 open source license",
+        "cloneUrl": "sparkleshare://addProject/${baseUrl}/sparkleshare/${repoUrl}.xml",
+        "productUrl": "http://sparkleshare.org",
+        "platforms": [ "windows", "macintosh", "linux" ],
+        "icon": "sparkleshare_32x32.png",
+        "minimumPermission" : "RW+",
+        "isActive": false
+    }
+]
+---JSON---
\ No newline at end of file
diff --git a/src/site/setup_express.mkd b/src/site/setup_express.mkd
new file mode 100644
index 0000000..6753542
--- /dev/null
+++ b/src/site/setup_express.mkd
@@ -0,0 +1,56 @@
+## Gitblit on RedHat's OpenShift Cloud Service
+
+The Gitblit Express distribution can be copied to the root of your RedHat OpenShift
+application repository.  Gitblit Express is an exploded WAR file with all appropriate
+dependencies bundled.
+
+You should delete the `pom.xml` file and the `src` folder from your application repository
+as Gitblit Express is not a source distribution to be built with Maven on OpenShift.
+
+Gitblit automatically adjusts itself to running on OpenShift.  Repositories, users,
+federation proposals, setting overrides, and Groovy push scripts are stored in *OPENSHIFT_DATA_DIR*.
+
+It is recommended to enable all RPC settings in the `web.xml` file to allow remote
+administration and, more importantly, configuration of your Gitblit Express
+installation using the Gitblit Manager.
+
+It is also recommended to set *web.forwardSlashCharacter* to ! because OpenShift
+runs on JBoss/Tomcat behind a proxy, neither of which are friendly to embedded
+forward-slashes.
+
+Please do not change the following settings unless you know exactly what you are
+doing:
+
+- *git.repositoriesFolder*
+- *groovy.scriptsFolder*
+- *federation.proposalsFolder*
+- *realm.userService* (for standard users.conf)
+ 
+Additionally, it is recommended to force your Gitblit installation to cleanup up
+older versions on your OpenShift filesystem to maximize available space for your
+repositories.
+
+Append the following command to your ./openshift/action_hooks/build file:
+
+    rm -fr $OPENSHIFT_APP_DIR/jbossas-7.0/standalone/tmp/vfs/*
+
+Lastly, you may want to play with the heap and permgen settings of your Gitblit
+instance because the default heap for the JVM is 95 MB, which may be a little
+tight.
+
+To do that you will have to login to your account via ssh:
+
+    ssh hashcode@app-domain.rhcloud.com
+
+and then you will have to manipulate the -Xmx and -XX:MaxPermSize values.
+
+    vi $OPENSHIFT_APP_DIR/jbossas-7.0/bin/standalone.conf
+    ctl_app restart
+
+OpenShift currently allows 300MB of memory per application which includes ssh access, JVM, etc.
+The Gitblit demo hosted on OpenShift Express operates with -Xmx160m and -XX:MaxPermSize=90m.
+
+For more detailed instructions on how to setup and deploy an OpenShift application
+please see this excellent turorial:
+
+[Deploying to OpenShift](https://github.com/opensas/play-demo/wiki/Step-12.5---deploy-to-openshift)
diff --git a/src/site/setup_go.mkd b/src/site/setup_go.mkd
new file mode 100644
index 0000000..6625baa
--- /dev/null
+++ b/src/site/setup_go.mkd
@@ -0,0 +1,139 @@
+## Gitblit GO Installation & Setup
+
+1. Download and unzip Gitblit GO [${project.releaseVersion} (Windows)](%GCURL%gitblit-${project.releaseVersion}.zip) or [${project.releaseVersion} (Linux/OSX)](%GCURL%gitblit-${project.releaseVersion}.tar.gz).  
+*Its best to eliminate spaces in the path name.* 
+2. The server itself is configured through a simple text file.<br/>
+Open `data/gitblit.properties` in your favorite text editor and make sure to review and set:
+    - *server.httpPort* and *server.httpsPort*
+    - *server.httpBindInterface* and *server.httpsBindInterface*  
+	- *server.storePassword*
+    **https** is strongly recommended because passwords are insecurely transmitted form your browser/git client using Basic authentication!
+    - *git.packedGitLimit* (set larger than the size of your largest repository)
+    - *git.streamFileThreshold* (set larger than the size of your largest committed file)
+3. Execute `authority.cmd` or `java -cp gitblit.jar com.gitblit.authority.Launcher --baseFolder data` from a command-line<br/>**NOTE:** The Authority is a Swing GUI application.  Use of this tool is not required as Gitblit GO will startup and create SSL certificates itself, BUT use of this tool allows you to control the identification metadata used in the generated certificates.  Skipping this step will result in certificates with default metadata.
+    1. fill out the fields in the *new certificate defaults* dialog
+	2. enter the store password used in *server.storePassword* when prompted.  This generates an SSL certificate for **localhost**.
+	3. you may want to generate an SSL certificate for the hostname or ip address hostnames you are serving from<br/>**NOTE:** You can only have **one** SSL certificate specified for a port.
+	5. exit the authority app
+4. Execute `gitblit.cmd` or `java -jar gitblit.jar --baseFolder data` from a command-line
+5. Open your browser to <http://localhost:8080> or <https://localhost:8443> depending on your chosen configuration.
+6. Enter the default administrator credentials: **admin / admin** and click the *Login* button    
+    **NOTE:** Make sure to change the administrator username and/or password!! 
+
+### GO Data Location
+
+By default, Gitblit GO stores all data (users, settings, repositories, etc) in the `data` subfolder of your GO installation.  You may specify an external location for your data on the command-line by setting the *--baseFolder* argument.  If you relocate the data folder then you must supply the *--baseFolder* argument to both GO and the Certificate Authority.
+
+If you are deploying Gitblit to a *nix platform, you might consider moving the data folder out of the GO installation folder and then creating a symlink named "data" that points to your moved folder.
+
+### Creating your own Self-Signed SSL Certificate
+Gitblit GO (and Gitblit Certificate Authority) automatically generates a Certificate Authority (CA) certificate and an ssl certificate signed by this CA certificate that is bound to *localhost*.
+
+Remote Eclipse/EGit/JGit clients (< 3.0) will fail to communicate using this certificate because JGit always verifies the hostname of the certificate, regardless of the *http.sslVerify=false* client-side setting.
+
+The EGit failure message is something like:
+
+	Cannot get remote repository refs.
+	Reason: https:/myserver.com/git/myrepo.git: cannot open git-upload-pack
+
+If you want to serve your repositories to another machine over https then you will want to generate a new certificate for the hostname or ip address you are serving from.
+
+1. `authority.cmd` or `java -jar authority.jar --baseFolder data`
+2. Click the *new ssl certificate* button (red rosette in the toolbar in upper left of window)
+3. Enter the hostname or ip address
+4. Make sure the checkbox *serve https with this certificate* is checked
+5. In the keystore password prompt, enter the *server.storePassword* password
+ 
+If you decide to change the value of *server.storePassword* (recommended) <u>after</u> you have already started Gitblit or Gitblit Certificate Authority, then you will have to delete the following files and then restart the Gitblit Certificate Authority app:
+
+1. data/serverKeyStore.jks
+2. data/serverTrustStore.jks
+3. data/certs/caKeyStore.jks
+4. data/certs/ca.crt
+5. data/certs/caRevocationList.crl (optional)
+
+### Client SSL Certificates
+SINCE 1.2.0
+
+Gitblit supports X509 certificate authentication.  This authentication method relies on your servlet container to validate/verify/trust your client certificate and can be used by your browser and your git client.
+
+All X509 certificates have a *distinguished name (DN)* which is a signature of several fields like:
+
+    C=US,O=Gitblit,OU=Gitblit,CN=james
+	
+Gitblit must be able to map the DN of the certificate to an *existing* account username.  The default mapping is to extract the *common name (CN)* value from the DN and use that as the account name.  If the CN is a valid account, then the user is authenticated.  The servlet container which runs Gitblit validates, verifies, and trusts the certificate passed to Gitblit.  If you need to specify an alternative DN mapping you may do so with the *git.certificateUsernameOIDs* setting, but this mapping must be matched to the user account name.
+
+How do you make your servlet container trust a client certificate?
+
+In the WAR variant, you will have to manually setup your servlet container to:
+
+1. want/need client certificates
+2. trust a CA certificate used to sign your client certificates
+3. generate client certificates signed by your CA certificate
+
+Alternatively, Gitblit GO is designed to facilitate use of client certificate authentication.  Gitblit GO ships with a tool that simplifies creation and management of client certificates, Gitblit Certificate Authority.
+
+#### Creating SSL Certificates with Gitblit Certificate Authority
+
+When you generate a new client certificate, a zip file bundle is created which includes a P12 keystore for browsers and a PEM keystore for Git.  Both of these are password-protected.  Additionally, a personalized README file is generated with setup instructions for popular browsers and Git.  The README is generated from `data\certs\instructions.tmpl` and can be modified to suit your needs.
+
+1. `authority.cmd` or `java -jar authority.jar --baseFolder data`
+2. Select the user for which to generate the certificate
+3. Click the *new certificate* button and enter the expiration date of the certificate.  You must also enter a password for the generated keystore.  This password is *not* the same as the user's login password.  This password is used to protect the privatekey and public certificate you will generate for the selected user.  You must also enter a password hint for the user.
+4. If your mail server settings are properly configured you will have a *send email* checkbox which you can use to immediately send the generated certificate bundle to the user.
+
+#### Certificate Inspection and Advanced Troubleshooting
+
+X509 certificates can be confusing and tricky even with the simplified Gitblit Certificate Authority tool.  If you find you need more tooling to understand your keystores, certificates, and certificate revocation lists (CRLs), I highly recommend [Portecle](http://portecle.sourceforge.net) which can be conveniently launched as a [Java Web Start app](http://portecle.sourceforge.net/webstart/portecle.jnlp).
+
+### Running as a Windows Service
+Gitblit uses [Apache Commons Daemon](http://commons.apache.org/daemon) to install and configure its Windows service.
+
+1. **Review the contents** of the `installService.cmd` where you may have to change the <u>default keystore password</u>.
+2. Set the *ARCH* value as appropriate for your installed Java Virtual Machine.
+3. Add any necessary *--StartParams* as enumerated below in **Command-Line Parameters**.
+4. Execute the script.
+
+After service installation you can use the `gitblitw.exe` utility to control and modify the runtime settings of the service.<br/>
+Additional service definition options and runtime capabilities of `gitblitw.exe` (prunmgr.exe) are documented [here](http://commons.apache.org/daemon/procrun.html).
+
+**NOTE:**<br/>
+If you change the name of the service from *gitblit* you must also change the name of `gitblitw.exe` to match the new service name otherwise the connection between the service and the utility is lost, at least to double-click execution. 
+
+#### VM Considerations
+By default, the service installation script configures your Windows service to use your default JVM.  This setup usually defaults to a client VM.<br/>
+If you have installed a JDK, you might consider using the `gitblitw.exe` utility to manually specify the *server* VM.
+
+1. Execute `gitblitw.exe`
+2. On the *Java* tab uncheck *Use default*.
+3. Manually navigate your filesystem and specify the server VM with the `...` button<br/><pre>
+Java Virtual Machine:
+C:\Program Files\Java\jre6\bin\server\jvm.dll</pre>
+
+#### Command-Line Parameters
+Command-Line parameters override the values in `gitblit.properties` at runtime.
+
+    --baseFolder           The default base folder for all relative file reference settings
+    --repositoriesFolder   Git Repositories Folder
+    --userService          Authentication and Authorization Service (filename or fully qualified classname)
+    --useNio               Use NIO Connector else use Socket Connector.
+    --httpPort             HTTP port for to serve. (port <= 0 will disable this connector)
+    --httpsPort            HTTPS port to serve.  (port <= 0 will disable this connector)
+    --ajpPort              AJP port to serve.  (port <= 0 will disable this connector)
+    --alias                Alias in keystore of SSL cert to use for https serving
+    --storePassword        Password for SSL (https) keystore.
+    --shutdownPort         Port for Shutdown Monitor to listen on. (port <= 0 will disable this monitor)
+    --tempFolder           Folder for server to extract built-in webapp
+    
+**Example**
+
+    java -jar gitblit.jar --userService c:/myrealm.config --storePassword something --baseFolder c:/data
+
+#### Overriding Gitblit GO's Log4j Configuration
+
+You can override Gitblit GO's default Log4j configuration with a command-line parameter to the JVM.
+
+    java -Dlog4j.configuration=file:///home/james/log4j.properties -jar gitblit.jar <optional_gitblit_args>
+    
+For reference, here is [Gitblit's default Log4j configuration](https://github.com/gitblit/gitblit/blob/master/src/log4j.properties).  It includes some file appenders that are disabled by default. 
+
diff --git a/src/site/setup_hooks.mkd b/src/site/setup_hooks.mkd
new file mode 100644
index 0000000..b3cb9c0
--- /dev/null
+++ b/src/site/setup_hooks.mkd
@@ -0,0 +1,96 @@
+## Groovy Hook Scripts
+
+Gitblit uses Groovy for its push hook mechanism.  This mechanism only executes when pushing to Gitblit, not when pushing to some other Git tooling in your stack.
+
+The Groovy hook mechanism allows for dynamic extension of Gitblit to execute custom tasks on receiving and processing push events.  The scripts run within the context of your Gitblit instance and therefore have access to Gitblit's internals at runtime.
+
+### Rules, Requirements, & Behaviors
+1. Your Groovy scripts must be stored in the *groovy.scriptsFolder* as specified in `gitblit.properties` or `web.xml`.
+2. All script files must have the *.groovy* extension. Because of this you may omit the extension when specifying the script.
+3. Script filenames must not have spaces!
+4. Scripts must be explicitly specified to be executed, no scripts are *automatically* executed by name or extension.
+5. A script can be specified to run on *all repositories* by adding the script file name to *groovy.preReceiveScripts* or *groovy.postReceiveScripts* in `gitblit.properties` or `web.xml`.
+6. Scripts can be specified for a team.
+7. Scripts may also be specified per-repository in the repository's settings.
+8. Globally-specified scripts and team-specified scripts are excluded from the list of available scripts in a repository's settings 
+9. Globally-specified scripts are executed first, in their listed order; followed by team-specified scripts in their listed order by alphabetical team order; followed by per-repository scripts, in their listed order.
+10. A script may only be defined once in a pre-receive chain and once in a post-receive chain.  
+You may execute the same script on pre-receive and post-receive, just not multiple times within a pre-receive or post-receive event.
+11. Gitblit does not differentiate between what can be a pre-receive script and what can be a post-receive script.
+12. If a script *returns false* then the hook chain is aborted and none of the subsequent scripts will execute.
+
+Some sample scripts are included in the GO and WAR distributions to show you how you can tap into Gitblit with the provided bound variables.  Additional implementation details may be specified in the header comment of these examples.
+
+Hook contributions and improvements are welcome.
+
+### Grapes
+
+*SINCE 1.0.0*
+
+[Grape](http://groovy.codehaus.org/Grape) lets you quickly add maven repository dependencies to your Groovy hook script.  
+
+<blockquote>Grape (The Groovy Adaptable Packaging Engine or Groovy Advanced Packaging Engine) is the infrastructure enabling the grab() calls in Groovy, a set of classes leveraging <a href="http://ant.apache.org/ivy">Ivy</a> to allow for a repository driven module system for Groovy. This allows a developer to write a script with an essentially arbitrary library requirement, and ship just the script. Grape will, at runtime, download as needed and link the named libraries and all dependencies forming a transitive closure when the script is run from existing repositories such as Ibiblio, Codehaus, and java.net.</blockquote>
+
+---JAVA---
+// create and use a primitive array
+import org.apache.commons.collections.primitives.ArrayIntList
+
+@Grab(group='commons-primitives', module='commons-primitives', version='1.0')
+def createEmptyInts() { new ArrayIntList() }
+
+def ints = createEmptyInts()
+ints.add(0, 42)
+assert ints.size() == 1
+assert ints.get(0) == 42
+---JAVA---
+
+### Custom Fields
+
+*SINCE 1.0.0*
+
+Gitblit allows custom repository string fields to be defined in `gitblit.properties` or `web.xml`.  Entry textfields are automatically created for these fields in the Edit Repository page of Gitblit and the Edit Repository dialog of the Gitblit Manager.  These fields are accessible from your Groovy hook scripts as
+
+    repository.customFields.myField
+
+This feature allows you to customize the behavior of your hook scripts without hard-coding values in the hook scripts themselves.
+
+### Pre-Receive
+
+Pre-Receive scripts execute after the pushed objects have all been written to the Git repository but before the refs have been updated to point to these new objects.
+
+This is the appropriate point to block a push and is how many Git tools implement branch-write permissions.
+
+### Post-Receive
+
+Post-Receive scripts execute after all refs have been updated.
+
+This is the appropriate point to trigger continuous integration builds or send email notifications, etc.
+
+## Push Email Notifications
+
+Gitblit implements email notifications in *sendmail.groovy* which uses the Groovy Hook Script mechanism.  This allows for dynamic customization of the notification process at the installation site and serves as an example push script.
+
+### Enabling Push Notifications
+
+In order to send email notifications on a push to Gitblit, this script must be specified somewhere in the *post-receive* script chain.  
+You may specify *sendmail* in one of three places:
+
+1. *groovy.postReceiveScripts* in `gitblit.properties` or `web.xml`, globally applied to all repositories
+2. post-receive scripts of a Team definition
+3. post-receive scripts of a Repository definition
+
+### Destination Addresses
+
+Gitblit does not currently support individual subscriptions to repositories; i.e. a *user* can not subscribe or unsubscribe from push notifications.
+
+However, Repository Managers and Administrators can specify subscribed email addresses in one of three places:
+
+1. *mail.mailingLists* in `gitblit.properties` or `web.xml`, globally applied to all push-notified repositories
+2. mailing lists in a Team definition, applied to all repositories that are part of the team definition
+3. mailing lists in a Repository definition
+
+All three sources are checked and merged into a unique list of destination addresses for push notifications.
+
+**NOTE:**  
+Care should be taken when devising your notification scheme as it relates to any VIEW restricted repositories you might have.  Setting a global mailing list and activating push notifications for a VIEW restricted repository may send unwanted emails.
+
diff --git a/src/site/setup_lucene.mkd b/src/site/setup_lucene.mkd
new file mode 100644
index 0000000..8e17ebd
--- /dev/null
+++ b/src/site/setup_lucene.mkd
@@ -0,0 +1,50 @@
+## Lucene Search Integration
+
+Repositories may optionally be indexed using the Lucene search engine.  The Lucene search offers several advantages over commit-traversal search:
+
+1. very fast commit and blob searches
+2. multi-term searches
+3. term-highlighted and syntax-highlighted fragment matches
+4. multi-repository searches
+
+### How do I use it?
+
+First you must ensure that *web.allowLuceneIndexing* is set *true* in `gitblit.properties` or `web.xml`.  Then you must understand that Lucene indexing is an opt-in feature which means that no repositories are automatically indexed.  
+Like anything else, this design has pros and cons.
+
+#### Pros
+1. no wasted cycles indexing repositories you will never search
+2. you specify exactly what branches are indexed; experimental/dead/personal branches can be ignored
+
+#### Cons
+1. you specify exactly what branches are indexed
+
+#### I have 300 repositories and you want me to specify indexed branches on each one??
+
+Yeah, I agree that is inconvenient.
+
+If you are using Gitblit GO there is a utility script `add-indexed-branch.cmd` which allows you to specify an indexed branch for many repositories in one step.
+
+If you are using Gitblit WAR then, at present, you are out of luck unless you write your own script to traverse your repositories and use native Git to manipulate each repository config.
+
+    git config --add gitblit.indexBranch "default"
+    git config --add gitblit.indexBranch "refs/heads/master"
+
+#### Indexing Branches
+You may specify which branches should be indexed per-repository in the *Edit Repository* page.  New/empty repositories may only specify the *default* branch which will resolve to whatever commit HEAD points to or the most recently updated branch if HEAD is unresolvable.
+
+Indexes are built and incrementally updated on a 2 minute cycle so you may have to wait a few minutes before your index is built or before your latest pushes get indexed.
+
+**NOTE:**  
+After specifying branches, only the content from those branches can be searched via Gitblit.  Gitblit will automatically redirect any queries entered on a repository's search box to the Lucene search page. Repositories that do not specify any indexed branches will use the traditional commit-traversal search.
+
+#### Adequate Heap
+
+The initial indexing of an existing repository can potentially exhaust the memory allocated to your Java instance and may throw OutOfMemory exceptions.  Be sure to provide your Gitblit server adequate heap space to index your repositories.  The heap is set using the *-Xmx* JVM parameter in your Gitblit launch command (e.g. -Xmx1024M).
+
+#### Why does Gitblit check every 2 mins for repository/branch changes?
+
+Gitblit has to balance its design as a complete, integrated Git server and its utility as a repository viewer in an existing Git setup.
+
+Gitblit could build indexes immediately on *edit repository* or on *receiving pushes*, but that design would not work if someone is pushing via ssh://, git://, or file:// (i.e. not pushing to Gitblit http(s)://).  For this reason Gitblit has a polling mechanism to check for ref changes every 2 mins.  This design works well for all use cases, aside from adding a little lag in updating the index.
+
diff --git a/src/site/setup_proxy.mkd b/src/site/setup_proxy.mkd
new file mode 100644
index 0000000..b169cef
--- /dev/null
+++ b/src/site/setup_proxy.mkd
@@ -0,0 +1,91 @@
+## Running Gitblit behind Apache
+
+Gitblit runs fine behind Apache.  You may use either *mod_proxy* (GO or WAR) or *mod_proxy_ajp* (GO).
+
+Each Linux distribution may vary on the exact configuration of Apache 2.2.  
+Here is a sample configuration that works on Debian 7.0 (Wheezy), your distribution may be different.
+
+1. First we need to make sure we have Apache's proxy modules available.  
+---FIXED---
+sudo su
+cd /etc/apache2/mods-enabled
+ln -s ../mods-available/proxy.load proxy.load
+ln -s ../mods-available/proxy_balancer.load proxy_balancer.load
+ln -s ../mods-available/proxy_http.load proxy_http.load
+ln -s ../mods-available/proxy_ajp.load proxy_ajp.load
+---FIXED---
+2. Then we need to make sure we are configuring Apache to use the proxy modules and to setup the proxied connection from Apache to Gitblit GO or from Apache to your chosen servlet container.  The following snippet is stored as `/etc/apache2/conf.d/gitblit`.  
+---FIXED---
+# Turn off support for true Proxy behaviour as we are acting as 
+# a transparent proxy
+ProxyRequests Off
+
+# Turn off VIA header as we know where the requests are proxied
+ProxyVia Off
+ 
+# Turn on Host header preservation so that the servlet container
+# can write links with the correct host and rewriting can be avoided.
+#
+# This is important for all git push/pull/clone operations.
+ProxyPreserveHost On
+ 
+# Set the permissions for the proxy
+<Proxy>
+	AddDefaultCharset off
+	Order deny,allow
+	Allow from all
+</Proxy>
+ 
+# The proxy context path must match the Gitblit context path.
+# For Gitblit GO, see server.contextPath in gitblit.properties.
+
+#ProxyPass /gitblit http://localhost:8080/gitblit
+#ProxyPassreverse /gitblit http://localhost:8080/gitblit
+
+# If your httpd frontend is https but you are proxying http Gitblit WAR or GO
+#Header edit Location &#94;http://([&#94;&#8260;]+)/gitblit/ https://&#36;1/gitblit/
+
+# Additionally you will want to tell Gitblit the original scheme and port
+#RequestHeader set X-Forwarded-Proto https
+#RequestHeader set X-Forwarded-Port 443
+
+# If you are using subdomain proxying then you will want to tell Gitblit the appropriate
+# context path for your repository url.
+# If you are not using subdomain proxying, then ignore this setting.
+#RequestHeader set X-Forwarded-Context /
+
+#ProxyPass /gitblit ajp://localhost:8009/gitblit
+---FIXED---  
+**Please** make sure to:  
+    1. Review the security of these settings as appropriate for your deployment
+    2. Uncomment the *ProxyPass* setting for whichever connection you prefer (http/ajp)
+    3. Correctly set the ports and context paths both in the *ProxyPass* definition and your Gitblit installation  
+    If you are using Gitblit GO you can easily configure the AJP connector by specifying a non-zero AJP port.  
+    Please remember that on Linux/UNIX, ports < 1024 require root permissions to open.
+    4. Set *web.mountParameters=false* in `gitblit.properties` or `web.xml` this will use parameterized URLs.  
+    Alternatively, you can respecify *web.forwardSlashCharacter*.
+
+### Controlling Advertised Repository URLs
+
+In some reverse-proxy configurations you may be running Gitblit using an http interface with an https reverse-proxy proxy.  This will lead to Gitblit generating incorrect repository urls.
+
+You can control the url that Gitblit generates by setting X-Forwarded headers in your proxy server.
+
+*X-Forwarded-Proto*://servername(:*X-Forwarded-Port*)(/*X-Forwarded-Context*)
+
+---X:MEDIAWIKI---
+{| class="table table-bordered"
+! Header
+! Description
+|-
+| X-Forwarded-Port
+| The port to use in generated repository http/https urls
+|-
+| X-Forwarded-Proto
+| The protocol/scheme to use in generated repository http/https urls
+|-
+| X-Forwarded-Context
+| The context to use in generated repository http/https urls
+|}
+---X:MEDIAWIKI---
+
diff --git a/src/site/setup_viewer.mkd b/src/site/setup_viewer.mkd
new file mode 100644
index 0000000..03aa349
--- /dev/null
+++ b/src/site/setup_viewer.mkd
@@ -0,0 +1,47 @@
+## Gitblit as a Viewer
+
+Gitblit is designed to be a complete Git server solution, however you may already have a Git serving solution such as ssh+gitolite or Gerrit.  For these scenarios, you may configure Gitblit to be just a repository viewer.
+
+### Lock-down your Viewer
+
+Here is an example configuration that disables all administration, all Git serving features, and requires an authenticated user to view anything.
+
+    git.repositoriesFolder = ${baseFolder}/git
+    git.cacheRepositoryList = true
+    git.searchRepositoriesSubfolders = true
+    git.searchRecursionDepth = -1
+    git.searchExclusions =
+    git.daemonPort = 0
+    git.enableGitServlet = false
+    git.enableGarbageCollection = false
+    git.defaultAccessRestriction = VIEW
+    web.authenticateViewPages = true
+    web.allowAdministration = false
+    web.enableRpcServlet = false
+    web.enableRpcManagement = false
+    web.enableRpcAdministration = false
+    web.allowForking = false
+
+###  Tomcat or Reverse-Proxy Servers
+
+If you are running Gitblit on a Tomcat-based container you will likely run into forward-slash character troubles.
+
+To resolve this either set:
+
+    web.mountParameters = false
+
+or
+
+    web.forwardSlashCharacter = !
+
+
+### Advertised Repository Urls
+
+You may also want to advertise repository urls for your other Git serving solution from Gitblit.
+
+- *{0}* is the token for the repository name
+- *{1}* is the token for the username
+
+The username is only practical if you have setup the account names for your other git serving solution to match the Gitblit account.
+
+    web.otherUrls = ssh://localhost/git/{0} git://localhost:29418/git/{0} https://{1}@localhost/r/{0}
diff --git a/src/site/setup_war.mkd b/src/site/setup_war.mkd
new file mode 100644
index 0000000..18a0799
--- /dev/null
+++ b/src/site/setup_war.mkd
@@ -0,0 +1,20 @@
+## Gitblit WAR Installation & Setup
+
+1. Download Gitblit WAR [${project.releaseVersion}](%GCURL%gitblit-${project.releaseVersion}.war) to the webapps folder of your servlet container.  
+2. You may have to manually extract the WAR (zip file) to a folder within your webapps folder.
+3. By default, the Gitblit webapp is configured through `WEB-INF/data/gitblit.properties`.<br/>
+Open `WEB-INF/data/gitblit.properties` in your favorite text editor and make sure to review and set:
+    - &lt;context-parameter&gt; *git.packedGitLimit* (set larger than the size of your largest repository)
+    - &lt;context-parameter&gt; *git.streamFileThreshold* (set larger than the size of your largest committed file)
+4. You may have to restart your servlet container. 
+5. Open your browser to <http://localhost/gitblit> or whatever the url should be.
+6. Enter the default administrator credentials: **admin / admin** and click the *Login* button  
+    **NOTE:** Make sure to change the administrator username and/or password!! 
+
+### WAR Data Location
+By default, Gitblit WAR stores all data (users, settings, repositories, etc) in `${contextFolder}/WEB-INF/data`.  This is fine for a quick setup, but there are many reasons why you don't want to keep your data within the webapps folder of your servlet container.  You may specify an external location for your data by editing `WEB-INF/web.xml` and manipulating the *baseFolder* context parameter.  Choose a location that is writeable by your servlet container.  Your servlet container may be smart enough to recognize the change and to restart Gitblit.
+
+On the next restart of Gitblit, Gitblit will copy the contents of the `WEB-INF/data` folder to your specified *baseFolder* **IF** the file `${baseFolder}/gitblit.properties` does not already exist.  This allows you to get going with minimal fuss.
+
+Specifying an alternate *baseFolder* also allows for simpler upgrades in the future.
+
diff --git a/src/site/siteindex.mkd b/src/site/siteindex.mkd
new file mode 100644
index 0000000..f72e9c1
--- /dev/null
+++ b/src/site/siteindex.mkd
@@ -0,0 +1,89 @@
+<div class="well" style="margin-left:5px;float:right;width:275px;padding: 10px 10px;">
+<div style="text-align:center">
+<b>Current Release ${project.releaseVersion} (${project.releaseDate})</b><br/><a href="releasenotes.html">release notes</a>
+<div style="padding:5px;"><a style="width:175px;text-decoration:none;" class="btn btn-success" href="%GCURL%gitblit-${project.releaseVersion}.zip">Download Gitblit GO (Windows)</a></div>
+<div style="padding:5px;"><a style="width:175px;text-decoration:none;" class="btn btn-success" href="%GCURL%gitblit-${project.releaseVersion}.tar.gz">Download Gitblit GO (Linux/OSX)</a></div>
+<div style="padding:5px;"><a style="width:175px;text-decoration:none;" class="btn btn-danger" href="%GCURL%gitblit-${project.releaseVersion}.war">Download Gitblit WAR</a></div>
+<div style="padding:5px;"><a style="width:175px;text-decoration:none;" class="btn btn-info" href="%GCURL%express-${project.releaseVersion}.zip">Download Gitblit Express</a></div>
+<div style="padding:5px;"><a style="width:175px;text-decoration:none;" class="btn btn-primary" href="%GCURL%manager-${project.releaseVersion}.zip">Download Gitblit Manager</a></div>
+		<a href="screenshots.html" title="Screenshots"><img style="margin-top:5px;border:1px solid #ccc;" src="thumbs/00.png" alt="Screenshots" /></a>
+	</div>
+
+	<div style="padding-top:5px;">
+	<table class="table condensed-table">
+		<tbody>
+		<tr><th>License</th><td><a href="http://www.apache.org/licenses/LICENSE-2.0">Apache License 2.0</a></td></tr>
+		<tr><th>Sources</th><td><a href="${project.scmUrl}">GitHub</a> &amp; <a href="http://code.google.com/p/gitblit/source/list">GoogleCode</a></td></tr>		
+		<tr><th>Issues</th><td><a href="${project.issuesUrl}">GoogleCode</a></td></tr>
+		<tr><th>Discussion</th><td><a href="${project.forumUrl}">Gitblit Group</a></td></tr>
+		<tr><th>Google+</th><td><a href="${project.socialNetworkUrl}">Gitblit+</a></td></tr>
+		<tr><th>Ohloh</th><td><a target="_top" href="http://www.ohloh.net/p/gitblit"><img border="0" width="100" height="16" src="http://www.ohloh.net/p/gitblit/widgets/project_thin_badge.gif" alt="Ohloh project report for Gitblit" /></a></td></tr>
+		<tr><th>Donations</th><td>If you enjoy Gitblit and want to support its development, please consider making a donation to <a href="http://www.stjude.org">St. Jude Children's Research Hospital</a>.
+		<a href="http://www.stjude.org" alt="St. Jude Children's Research Hospital"><img style="padding-top:10px;" src="stjude_150x150.gif"/></a></td></tr>
+		</tbody>
+		</table>
+	</div>
+</div>
+
+## What is Gitblit?
+
+Gitblit is an open-source, pure Java stack for managing, viewing, and serving [Git][git] repositories.  
+It's designed primarily as a tool for small workgroups who want to host centralized repositories.
+
+You can browse the [stable demo site](https://demo-gitblit.rhcloud.com) or the [snapshot demo site](https://next-gitblit.rhcloud.com), both are hosted on [RedHat's OpenShift][rhcloud] cloud service.
+
+### GO: Single-Stack Solution
+
+*Gitblit GO* is an integrated, single-stack solution based on Jetty.
+
+You do not need Apache httpd, Perl, Git, or Gitweb.  Should you want to use some or all of those, you still can; Gitblit plays nice with the other kids on the block.
+
+This is what you should download if you want to go from zero to Git in less than 5 mins.
+
+All dependencies are bundled.
+
+### WAR: For Your Servlet Container
+*Gitblit WAR* is what you should download if you already have a servlet container available that you wish to use.  Jetty 6/7/8 and Tomcat 6/7 are known to work.  Generally, any Servlet 2.5 or Servlet 3.0 container should work.
+
+All dependencies are bundled.
+
+### Express: For the Cloud
+*Gitblit Express* is a prepared distribution for [RedHat's OpenShift][rhcloud] cloud service.
+
+All dependencies are bundled.
+
+### You decide how to use Gitblit
+
+Gitblit can be used as a dumb repository viewer with no administrative controls or user accounts.  
+Gitblit can be used as a complete Git stack for cloning, pushing, and repository access control.  
+Gitblit can be used without any other Git tooling (including actual Git) or it can cooperate with your established tools.
+
+### Easy Remote Management
+
+Administrators can create and manage all repositories, user accounts, and teams from the *Web UI*.  
+Administrators can create and manage all repositories, user accounts, and teams from the *JSON RPC interface* using the [Gitblit Manager](http://code.google.com/p/gitblit/downloads/detail?name=%MANAGER%) or your own custom tooling. 
+
+### Integration with Your Infrastructure
+
+- Groovy push hook scripts
+- Pluggable user service mechanism
+    - LDAP authentication with optional LDAP-controlled Team memberships
+	- Redmine authentication
+	- SalesForce.com authentication
+	- Windows authentication
+    - Custom authentication, authorization, and user management
+- Rich RSS feeds
+- JSON-based RPC mechanism
+- [Java Client RSS/JSON API library](http://code.google.com/p/gitblit/downloads/detail?name=%API%) for custom integration
+
+### Backup Strategy
+
+Gitblit includes a backup mechanism (*federation*) which can be used to backup repositories and, optionally, user accounts, team definitions, server settings, & Groovy push hook scripts from your Gitblit instance to another Gitblit instance or to a [Gitblit Federation Client](http://code.google.com/p/gitblit/downloads/detail?name=%FEDCLIENT%).  Similarly, you can use the federation mechanism to aggregate individual workspace Gitblit instances to a common, centralized server.
+
+### Java Runtime Requirement
+
+Gitblit requires a Java 6 Runtime Environment (JRE) or a Java 6 Development Kit (JDK).
+
+[jgit]: http://eclipse.org/jgit "Eclipse JGit Site"
+[git]: http://git-scm.com "Official Git Site"
+[rhcloud]: https://openshift.redhat.com/app "RedHat OpenShift"
diff --git a/src/site/templates/atom.ftl b/src/site/templates/atom.ftl
new file mode 100644
index 0000000..06d28da
--- /dev/null
+++ b/src/site/templates/atom.ftl
@@ -0,0 +1,2 @@
+<#include "macros.ftl">
+<@AtomMacro posts=releases posturl="${project.url}/history.html#" />
\ No newline at end of file
diff --git a/src/site/templates/macros.ftl b/src/site/templates/macros.ftl
new file mode 100644
index 0000000..e7c275b
--- /dev/null
+++ b/src/site/templates/macros.ftl
@@ -0,0 +1,147 @@
+<#macro LogMacro title version date description log logTitle="">
+	<#if log??>
+	<h3 id="${version}" class="section"><a href="#${version}" class="sectionlink"><i class="icon-share-alt"> </i></a>${title} (${version}) <small>${description}</small></h3>
+	<table class="table">
+		<tbody>
+			<tr>
+				<td style="background-color:inherit;width:100px">${date}</td>
+				<td style="background-color:inherit;"><@LogDescriptionMacro log=log title=logTitle /></td>
+			</tr>
+		</tbody>
+	</table>
+	</#if>
+</#macro>
+
+<#macro LogDescriptionMacro log title=log.title>
+	<#if (title!?length > 0)>
+		<p class="lead">${title}</p>		
+	</#if>
+	
+	<#if (log.html!?length > 0)>
+		<p>${log.html}</p>
+	</#if>
+	
+	<#if (log.text!?length > 0)>
+		<blockquote><p>${log.text!?html?replace("\n", "<br />")}</p></blockquote>		
+	</#if>
+
+	<#if (log.note!?length > 0)>
+		<div class="alert alert-info">
+			<h4>Note</h4>
+			${log.note?html?replace("\n", "<p />")}
+		</div>
+	</#if>
+
+	<#if (log.security!?size > 0)>
+		<@SecurityListMacro title="security" list=log.security/>
+	</#if>
+	<#if (log.fixes!?size > 0)>
+		<@UnorderedListMacro title="fixes" list=log.fixes />
+	</#if>
+	<#if (log.changes!?size > 0)>
+		<@UnorderedListMacro title="changes" list=log.changes />
+	</#if>
+	<#if (log.additions!?size > 0)>
+		<@UnorderedListMacro title="additions" list=log.additions />
+	</#if>
+	<#if (log.settings!?size > 0)>
+		<@SettingsTableMacro title="new settings" list=log.settings />		
+	</#if>
+	<#if (log.dependencyChanges!?size > 0)>
+		<@UnorderedListMacro title="dependency changes" list=log.dependencyChanges />
+	</#if>
+	<#if (log.contributors!?size > 0)>
+		<@UnorderedListMacro title="contributors" list=log.contributors?sort />
+	</#if>	
+</#macro>
+
+<#macro SecurityListMacro list title>
+   	<h4 style="color:red;">${title}</h4>
+	<ul>
+	<#list list as item>
+		<li>${item?html?replace("\n", "<br/>")}</li>
+	</#list>
+	</ul>
+</#macro>
+
+<#macro UnorderedListMacro list title>
+   	<h4>${title}</h4>
+	<ul>
+	<#list list as item>
+		<li>${item?html?replace("\n", "<br/>")}</li>
+	</#list>
+	</ul>
+</#macro>
+
+<#macro SettingsTableMacro list title>
+   	<h4>${title}</h4>
+	<table class="table">
+		<#list list as item>
+		<tr>
+			<td><em>${item.name}</em></td><td>${item.defaultValue}</td>
+		</tr>
+		</#list>
+	</table>
+</#macro>
+
+<#macro RssMacro posts posturl>
+<?xml version="1.0" standalone='yes'?>
+<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/">
+	<channel>
+		<title><![CDATA[${project.name}]]></title>
+		<link>${project.url}</link>
+		<description><![CDATA[${project.description}]]></description>
+		<generator>Moxie Toolkit</generator>
+		<#list posts as post>
+  		<item>
+    		<title><![CDATA[${post.title}]]></title>
+    		<link><![CDATA[${posturl}${post.id}]]></link>
+    		<guid isPermaLink="true">${posturl}${post.id}</guid>
+	   		<#if (post.text!?length > 0)>
+    		<description><![CDATA[${post.text}]]></description>
+    		</#if>
+    		<#if (post.keywords!?size > 0)>
+   			<#list post.keywords as keyword>
+			<category><![CDATA[${keyword}]]></category>
+   			</#list>
+    		</#if>
+    		<#if (post.author!?length > 0)>
+  			<dc:creator><![CDATA[${post.author}]]></dc:creator>
+    		<#else>
+   			<dc:creator><![CDATA[${project.name}]]></dc:creator>
+    		</#if>
+    		<pubDate>${post.date?string("EEE, dd MMM yyyy HH:mm:ss Z")}</pubDate>
+  		</item>
+  		</#list>
+  	</channel>
+</rss>
+</#macro>
+
+<#macro AtomMacro posts posturl>
+<?xml version="1.0" standalone='yes'?>
+<feed xmlns="http://www.w3.org/2005/Atom">
+	<generator uri="${project.url}" version="${project.version}">${project.name}</generator>
+	<title><![CDATA[${project.name}]]></title>
+	<updated>${project.releaseDate}</updated>
+	<#list posts as post>
+	<entry>
+		<content type="text/plain" />
+   		<title type="text"><![CDATA[${post.title}]]></title>
+   		<#if (post.text!?length > 0)>
+   		<summary type="text"><![CDATA[${post.text}]]></summary>
+   		</#if>
+   		<link href="${posturl}${post.id}" rel="via" />
+   		<guid isPermaLink="true">${posturl}${post.id}</guid>
+   		<#if (post.text!?length > 0)>
+   		<content><![CDATA[${post.text}]]></content>
+   		</#if>
+   		<#if (post.keywords!?size > 0)>
+		<#list post.keywords as keyword>
+		<category label="<![CDATA[${keyword}]]>" />
+		</#list>
+   		</#if>
+   		<published>${post.date?string("yyyy-MM-dd'T'HH:mm:ssZ")}</published>
+	</entry>
+	</#list>
+</feed>
+</#macro>
\ No newline at end of file
diff --git a/src/site/templates/releasecurrent.ftl b/src/site/templates/releasecurrent.ftl
new file mode 100644
index 0000000..3bfd709
--- /dev/null
+++ b/src/site/templates/releasecurrent.ftl
@@ -0,0 +1,25 @@
+<#include "macros.ftl" >
+
+<!-- CURRENT RELEASE -->
+<@LogMacro 
+	title="Current Release"
+	log=release 
+	version=project.releaseVersion 
+	date=reference.releaseDate?string("yyyy-MM-dd") 
+	description="this is the current stable release" />
+
+<!-- NEXT RELEASE -->
+<#if snapshot??>
+<@LogMacro 
+	title="Next Release"
+	log=snapshot
+	version=project.version
+	date="PENDING"
+	description="these changes are queued for an upcoming release" />
+</#if>
+
+<div>
+	<ul class="pager">
+		<li class="next"><a href="releases.html">All Releases &rarr;</a></li>
+	</ul>
+</div>
diff --git a/src/site/templates/releasehistory.ftl b/src/site/templates/releasehistory.ftl
new file mode 100644
index 0000000..eaaaad2
--- /dev/null
+++ b/src/site/templates/releasehistory.ftl
@@ -0,0 +1,21 @@
+<#include "macros.ftl" >
+
+<!-- HISTORY -->
+<#if (releases!?size > 0)>
+	<p></p>
+	<h2>All Releases</h2>
+	<table class="table">
+		<tbody>
+		<!-- RELEASE HISTORY -->
+		<#list releases?sort_by("date")?reverse as log>
+		<tr id="${log.id}">
+			<td style="width:100px" id="${log.id}">
+				<b><a href="#${log.id}">${log.id}</a></b><br/>
+				${log.date?string("yyyy-MM-dd")}
+			</td>
+			<td><@LogDescriptionMacro log=log /></td>
+		</tr>
+		</#list>
+		</tbody>
+	</table>
+</#if>
\ No newline at end of file
diff --git a/src/site/templates/rss.ftl b/src/site/templates/rss.ftl
new file mode 100644
index 0000000..90e86eb
--- /dev/null
+++ b/src/site/templates/rss.ftl
@@ -0,0 +1,2 @@
+<#include "macros.ftl">
+<@RssMacro posts=releases posturl="${project.url}/releases.html#" />
\ No newline at end of file
diff --git a/src/site/upgrade_express.mkd b/src/site/upgrade_express.mkd
new file mode 100644
index 0000000..103cfc9
--- /dev/null
+++ b/src/site/upgrade_express.mkd
@@ -0,0 +1,14 @@
+## Upgrading Gitblit Express
+
+1. Make a  backup copy of */deployments/ROOT.war/WEB-INF/web.xml*
+2. Delete your */deployments/ROOT.war* and then copy the new */deployments/ROOT.war* from the archive.
+3. Diff your backup copy of web.xml with the pristine one you copied as part of */deployments/ROOT.war* and apply any necessary changes.
+
+These steps are necessary to ensure that you end up using the specified libraries and resources for the new version.  Otherwise you could end up with a hybrid filesystem that would make it difficult to troubleshoot.  It is important to note that the web.xml file contains both your default settings AND application configuration.  Not updating the web.xml is just a likely to create problems as making a hybrid filesystem.
+
+### 1.3.0 web.xml
+
+- Added LogoServlet
+- Added SparkleShareInviteServlet
+- Added EnforceAuthenticationFilter
+- Moved GitServlet
\ No newline at end of file
diff --git a/src/site/upgrade_go.mkd b/src/site/upgrade_go.mkd
new file mode 100644
index 0000000..cd51601
--- /dev/null
+++ b/src/site/upgrade_go.mkd
@@ -0,0 +1,42 @@
+## Upgrading Gitblit GO (1.2.1+)
+ 
+1. Unzip Gitblit GO to a new folder
+2. Set the *--baseFolder* argument to point to your old `data` folder
+3. Review and optionally apply any new settings as indicated in the [release log](releases.html) to `data/gitblit.properties`.
+
+In *nix systems or Windows Vista/7/8, there are other tricks you can play like symlinking the `data` folder or symlinking the GO folder.
+All platforms support the *--baseFolder* command-line argument.
+
+## Upgrading Gitblit GO (pre-1.2.1)
+1. Create a `data` folder and copy the following files and folders to it:
+    - **users.conf*
+	- **projects.conf** *(if you have one)*
+	- **gitblit.properties**
+	- **serverKeystore.jks**
+	- **serverTrustStore.jks**
+	- *certs** folder
+	- **git** folder
+	- **groovy** folder
+	- **proposals** folder
+    - and any other custom files (robots.txt, welcome/login markdown files, etc)
+	- then edit your `gitblit.properties` file and adjust the following settings:
+        - *git.repositoriesFolder* = ${baseFolder}/git
+        - *groovy.scriptsFolder* = ${baseFolder}/groovy
+        - *groovy.grapeFolder* = ${baseFolder}/groovy/grape
+        - *web.projectsFile* = ${baseFolder}/projects.conf
+        - *realm.userService* = ${baseFolder}/users.conf
+        - *web.robots.txt* = ${baseFolder}/robots.txt
+        - *federation.proposalsFolder* = ${baseFolder}/proposals
+        - *realm.ldap.backingUserService* = ${baseFolder}/users.conf
+        - *realm.redmine.backingUserService* = ${baseFolder}/users.conf
+        - *server.tempFolder* = ${baseFolder}/temp
+
+2. Unzip Gitblit GO to a new folder
+3. Copy your `data` folder and overwrite the folder of the same name in the just-unzipped version
+4. Review and optionally apply any new settings as indicated in the [release log](releases.html) to `data/gitblit.properties`.
+
+**NOTE:** You may need to adjust your service definitions to include the `--baseFolder data` argument.
+
+#### Upgrading Windows Service
+You may need to delete your old service definition and install a new one depending on what has changed in the release.
+
diff --git a/src/site/upgrade_war.mkd b/src/site/upgrade_war.mkd
new file mode 100644
index 0000000..dd9ba64
--- /dev/null
+++ b/src/site/upgrade_war.mkd
@@ -0,0 +1,17 @@
+## Upgrading Gitblit WAR (1.2.1+)
+1. Make sure your `WEB-INF/web.xml` *baseFolder* context parameter is not `${contextFolder}/WEB-INF/data`!<br/>
+If it is, move your `WEB-INF/data` folder to a location writeable by your servlet container.
+2. Deploy new WAR
+3. Edit the new WAR's `WEB-INF/web.xml` file and set the *baseFolder* context parameter to your external baseFolder.
+4. Review and optionally apply any new settings as indicated in the [release log](releases.html) to `${baseFolder}/gitblit.properties`. 
+ 
+
+## Upgrading Gitblit WAR (pre-1.2.1)
+
+1. Create a `data` as outlined in step 1 of *Upgrading Gitblit GO (pre-1.2.1)*
+2. Copy your existing web.xml to your data folder
+3. Deploy new WAR
+4. Copy the new WAR's `WEB-INF/data/gitblit.properties` file to your data folder
+5. Manually apply any changes you made to your original web.xml file to the gitblit.properties file you copied to your data folder
+6. Edit the new WAR's `WEB-INF/web.xml` file and set the *baseFolder* context parameter to your external baseFolder.
+
diff --git a/src/test/config/test-gitblit.properties b/src/test/config/test-gitblit.properties
new file mode 100644
index 0000000..7876407
--- /dev/null
+++ b/src/test/config/test-gitblit.properties
@@ -0,0 +1,88 @@
+#
+# Gitblit Unit Testing properties
+#
+
+git.repositoriesFolder = ${baseFolder}/git
+git.searchRepositoriesSubfolders = true
+git.enableGitServlet = true
+groovy.scriptsFolder = src/main/distrib/data/groovy
+groovy.preReceiveScripts = blockpush
+groovy.postReceiveScripts = sendmail
+web.authenticateViewPages = false
+web.authenticateAdminPages = true
+web.allowCookieAuthentication = true
+realm.userService = ${baseFolder}/src/test/config/test-users.conf
+realm.passwordStorage = md5
+realm.minPasswordLength = 5
+web.siteName = Test Gitblit
+web.allowAdministration = true
+web.enableRpcServlet = true
+web.enableRpcManagement = true
+web.enableRpcAdministration = true
+web.allowGravatar = true
+web.allowZipDownloads = true
+web.syndicationEntries = 25
+web.showRepositorySizes = true
+web.showFederationRegistrations = false
+web.loginMessage = gitblit
+web.repositoriesMessage = gitblit
+web.useClientTimezone = false
+web.timeFormat = HH:mm
+web.datestampShortFormat = yyyy-MM-dd
+web.datestampLongFormat = EEEE, MMMM d, yyyy
+web.datetimestampLongFormat = EEEE, MMMM d, yyyy h:mm a z
+web.mountParameters = true
+web.forwardSlashCharacter = /
+web.otherUrls = 
+web.repositoryListType = grouped
+web.repositoryRootGroupName = main
+web.repositoryListSwatches = true
+web.diffStyle = gitblit
+web.showEmailAddresses = true
+web.showSearchTypeSelection = false
+web.generateActivityGraph = true
+web.activityDuration = 14
+web.summaryCommitCount = 16
+web.summaryRefsCount = 5
+web.itemsPerPage = 50
+web.prettyPrintExtensions = c cpp cs css htm html java js php pl prefs properties py rb sh sql xml vb
+web.markdownExtensions = md mkd markdown MD MKD
+web.imageExtensions = bmp jpg gif png 
+web.binaryExtensions = jar pdf tar.gz zip
+web.aggressiveHeapManagement = false
+web.debugMode = false
+regex.global = true
+regex.global.bug = \\b(Bug:)(\\s*[#]?|-){0,1}(\\d+)\\b!!!<a href="http://somehost/bug/$3">Bug-Id: $3</a>
+regex.global.changeid = \\b(Change-Id:\\s*)([A-Za-z0-9]*)\\b!!!<a href="http://somehost/changeid/$2">Change-Id: $2</a>
+regex.myrepository.bug = \\b(Bug:)(\\s*[#]?|-){0,1}(\\d+)\\b!!!<a href="http://elsewhere/bug/$3">Bug-Id: $3</a>
+mail.server =
+mail.port = 25
+mail.debug = false
+mail.username =
+mail.password =
+mail.fromAddress = 
+mail.adminAddresses = 
+mail.mailingLists = x@test.com y@test.com z@test.com
+federation.name = Unit Test
+federation.passphrase = Unit Testing
+federation.allowProposals = false
+federation.proposalsFolder = proposals
+federation.defaultFrequency = 60 mins
+federation.sets = animal mineral vegetable
+#federation.example1.url = https://go.gitblit.com
+#federation.example1.token = 6f3b8a24bf970f17289b234284c94f43eb42f0e4
+#federation.example1.frequency = 120 mins
+#federation.example1.folder =
+#federation.example1.bare = true 
+#federation.example1.mirror = true 
+#federation.example1.mergeAccounts = true
+
+server.tempFolder = ${baseFolder}/temp
+server.useNio = true
+server.contextPath = /
+server.httpPort = 0
+server.httpsPort = 8443
+server.httpBindInterface = localhost
+server.httpsBindInterface = localhost
+server.storePassword = gitblit
+server.shutdownPort = 8081
diff --git a/test-ui-gitblit.properties b/src/test/config/test-ui-gitblit.properties
similarity index 100%
rename from test-ui-gitblit.properties
rename to src/test/config/test-ui-gitblit.properties
diff --git a/test-ui-users.conf b/src/test/config/test-ui-users.conf
similarity index 100%
rename from test-ui-users.conf
rename to src/test/config/test-ui-users.conf
diff --git a/test-users.conf b/src/test/config/test-users.conf
similarity index 100%
rename from test-users.conf
rename to src/test/config/test-users.conf
diff --git a/tests/com/gitblit/tests/ActivityTest.java b/src/test/java/com/gitblit/tests/ActivityTest.java
similarity index 100%
rename from tests/com/gitblit/tests/ActivityTest.java
rename to src/test/java/com/gitblit/tests/ActivityTest.java
diff --git a/tests/com/gitblit/tests/ArrayUtilsTest.java b/src/test/java/com/gitblit/tests/ArrayUtilsTest.java
similarity index 100%
rename from tests/com/gitblit/tests/ArrayUtilsTest.java
rename to src/test/java/com/gitblit/tests/ArrayUtilsTest.java
diff --git a/tests/com/gitblit/tests/Base64Test.java b/src/test/java/com/gitblit/tests/Base64Test.java
similarity index 100%
rename from tests/com/gitblit/tests/Base64Test.java
rename to src/test/java/com/gitblit/tests/Base64Test.java
diff --git a/tests/com/gitblit/tests/ByteFormatTest.java b/src/test/java/com/gitblit/tests/ByteFormatTest.java
similarity index 100%
rename from tests/com/gitblit/tests/ByteFormatTest.java
rename to src/test/java/com/gitblit/tests/ByteFormatTest.java
diff --git a/tests/com/gitblit/tests/DiffUtilsTest.java b/src/test/java/com/gitblit/tests/DiffUtilsTest.java
similarity index 100%
rename from tests/com/gitblit/tests/DiffUtilsTest.java
rename to src/test/java/com/gitblit/tests/DiffUtilsTest.java
diff --git a/src/test/java/com/gitblit/tests/FanoutServiceTest.java b/src/test/java/com/gitblit/tests/FanoutServiceTest.java
new file mode 100644
index 0000000..cd094da
--- /dev/null
+++ b/src/test/java/com/gitblit/tests/FanoutServiceTest.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright 2013 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit.tests;
+
+import static org.junit.Assert.assertEquals;
+
+import java.text.MessageFormat;
+import java.util.Date;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.junit.Test;
+
+import com.gitblit.fanout.FanoutClient;
+import com.gitblit.fanout.FanoutClient.FanoutAdapter;
+import com.gitblit.fanout.FanoutNioService;
+import com.gitblit.fanout.FanoutService;
+import com.gitblit.fanout.FanoutSocketService;
+
+public class FanoutServiceTest {
+	
+	int fanoutPort = FanoutService.DEFAULT_PORT;
+	
+	@Test
+	public void testNioPubSub() throws Exception {
+		testPubSub(new FanoutNioService(fanoutPort));
+	}
+
+	@Test
+	public void testSocketPubSub() throws Exception {
+		testPubSub(new FanoutSocketService(fanoutPort));
+	}
+	
+	@Test
+	public void testNioDisruptionAndRecovery() throws Exception {
+		testDisruption(new FanoutNioService(fanoutPort));
+	}
+
+	@Test
+	public void testSocketDisruptionAndRecovery() throws Exception {
+		testDisruption(new FanoutSocketService(fanoutPort));
+	}
+	
+	protected void testPubSub(FanoutService service) throws Exception {
+		System.out.println(MessageFormat.format("\n\n========================================\nPUBSUB TEST {0}\n========================================\n\n", service.toString()));
+		service.startSynchronously();
+		
+		final Map<String, String> announcementsA = new ConcurrentHashMap<String, String>();
+		FanoutClient clientA = new FanoutClient("localhost", fanoutPort);
+		clientA.addListener(new FanoutAdapter() {
+			
+			@Override
+			public void announcement(String channel, String message) {
+				announcementsA.put(channel, message);
+			}
+		});
+		
+		clientA.startSynchronously();
+
+		final Map<String, String> announcementsB = new ConcurrentHashMap<String, String>();
+		FanoutClient clientB = new FanoutClient("localhost", fanoutPort);
+		clientB.addListener(new FanoutAdapter() {
+			@Override
+			public void announcement(String channel, String message) {
+				announcementsB.put(channel, message);
+			}
+		});
+		clientB.startSynchronously();
+
+		
+		// subscribe clients A and B to the channels
+		clientA.subscribe("a");
+		clientA.subscribe("b");
+		clientA.subscribe("c");
+		
+		clientB.subscribe("a");
+		clientB.subscribe("b");
+		clientB.subscribe("c");
+		
+		// give async messages a chance to be delivered
+		Thread.sleep(1000);
+		
+		clientA.announce("a", "apple");
+		clientA.announce("b", "banana");
+		clientA.announce("c", "cantelope");
+		
+		clientB.announce("a", "avocado");
+		clientB.announce("b", "beet");
+		clientB.announce("c", "carrot");
+
+		// give async messages a chance to be delivered
+		Thread.sleep(2000);
+
+		// confirm that client B received client A's announcements
+		assertEquals("apple", announcementsB.get("a"));
+		assertEquals("banana", announcementsB.get("b"));
+		assertEquals("cantelope", announcementsB.get("c"));
+
+		// confirm that client A received client B's announcements
+		assertEquals("avocado", announcementsA.get("a"));
+		assertEquals("beet", announcementsA.get("b"));
+		assertEquals("carrot", announcementsA.get("c"));
+		
+		clientA.stop();
+		clientB.stop();
+		service.stop();		
+	}
+	
+	protected void testDisruption(FanoutService service) throws Exception  {
+		System.out.println(MessageFormat.format("\n\n========================================\nDISRUPTION TEST {0}\n========================================\n\n", service.toString()));
+		service.startSynchronously();
+		
+		final AtomicInteger pongCount = new AtomicInteger(0);
+		FanoutClient client = new FanoutClient("localhost", fanoutPort);
+		client.addListener(new FanoutAdapter() {
+			@Override
+			public void pong(Date timestamp) {
+				pongCount.incrementAndGet();
+			}
+		});
+		client.startSynchronously();
+		
+		// ping and wait for pong
+		client.ping();	
+		Thread.sleep(500);
+		
+		// restart client
+		client.stop();
+		Thread.sleep(1000);
+		client.startSynchronously();		
+		
+		// ping and wait for pong
+		client.ping();	
+		Thread.sleep(500);
+				
+		assertEquals(2, pongCount.get());
+		
+		// now disrupt service
+		service.stop();		
+		Thread.sleep(2000);
+		service.startSynchronously();
+		
+		// wait for reconnect
+		Thread.sleep(2000);
+
+		// ping and wait for pong
+		client.ping();
+		Thread.sleep(500);
+
+		// kill all
+		client.stop();
+		service.stop();
+		
+		// confirm expected pong count
+		assertEquals(3, pongCount.get());
+	}
+}
\ No newline at end of file
diff --git a/src/test/java/com/gitblit/tests/FederationTests.java b/src/test/java/com/gitblit/tests/FederationTests.java
new file mode 100644
index 0000000..8475ffe
--- /dev/null
+++ b/src/test/java/com/gitblit/tests/FederationTests.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright 2011 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit.tests;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import com.gitblit.Constants.AccessRestrictionType;
+import com.gitblit.Constants.FederationProposalResult;
+import com.gitblit.Constants.FederationRequest;
+import com.gitblit.Constants.FederationToken;
+import com.gitblit.models.FederationModel;
+import com.gitblit.models.FederationProposal;
+import com.gitblit.models.RepositoryModel;
+import com.gitblit.models.TeamModel;
+import com.gitblit.models.UserModel;
+import com.gitblit.utils.FederationUtils;
+import com.gitblit.utils.JsonUtils;
+import com.gitblit.utils.RpcUtils;
+
+public class FederationTests {
+
+	String url = GitBlitSuite.url;
+	String account = GitBlitSuite.account;
+	String password = GitBlitSuite.password;
+	String token = "d7cc58921a80b37e0329a4dae2f9af38bf61ef5c";
+
+	private static final AtomicBoolean started = new AtomicBoolean(false);
+
+	@BeforeClass
+	public static void startGitblit() throws Exception {
+		started.set(GitBlitSuite.startGitblit());
+	}
+
+	@AfterClass
+	public static void stopGitblit() throws Exception {
+		if (started.get()) {
+			GitBlitSuite.stopGitblit();
+		}
+	}
+
+	@Test
+	public void testProposal() throws Exception {
+		// create dummy repository data
+		Map<String, RepositoryModel> repositories = new HashMap<String, RepositoryModel>();
+		for (int i = 0; i < 5; i++) {
+			RepositoryModel model = new RepositoryModel();
+			model.accessRestriction = AccessRestrictionType.VIEW;
+			model.description = "cloneable repository " + i;
+			model.lastChange = new Date();
+			model.addOwner("adminuser");
+			model.name = "repo" + i + ".git";
+			model.size = "5 MB";
+			model.hasCommits = true;
+			repositories.put(model.name, model);
+		}
+
+		FederationProposal proposal = new FederationProposal("http://testurl", FederationToken.ALL,
+				"testtoken", repositories);
+
+		// propose federation
+		assertEquals("proposal refused", FederationUtils.propose(url, proposal),
+				FederationProposalResult.NO_PROPOSALS);
+	}
+
+	@Test
+	public void testJsonRepositories() throws Exception {
+		String requrl = FederationUtils.asLink(url, token, FederationRequest.PULL_REPOSITORIES);
+		String json = JsonUtils.retrieveJsonString(requrl, null, null);
+		assertNotNull(json);
+	}
+
+	@Test
+	public void testJsonUsers() throws Exception {
+		String requrl = FederationUtils.asLink(url, token, FederationRequest.PULL_USERS);
+		String json = JsonUtils.retrieveJsonString(requrl, null, null);
+		assertNotNull(json);
+	}
+
+	@Test
+	public void testJsonTeams() throws Exception {
+		String requrl = FederationUtils.asLink(url, token, FederationRequest.PULL_TEAMS);
+		String json = JsonUtils.retrieveJsonString(requrl, null, null);
+		assertNotNull(json);
+	}
+
+	private FederationModel getRegistration() {
+		FederationModel model = new FederationModel("localhost");
+		model.url = this.url;
+		model.token = this.token;
+		return model;
+	}
+
+	@Test
+	public void testPullRepositories() throws Exception {
+		Map<String, RepositoryModel> repos = FederationUtils.getRepositories(getRegistration(),
+				false);
+		assertNotNull(repos);
+		assertTrue(repos.size() > 0);
+	}
+
+	@Test
+	public void testPullUsers() throws Exception {
+		List<UserModel> users = FederationUtils.getUsers(getRegistration());
+		assertNotNull(users);
+		// admin is excluded
+		assertEquals(0, users.size());
+		
+		UserModel newUser = new UserModel("test");
+		newUser.password = "whocares";
+		assertTrue(RpcUtils.createUser(newUser, url, account, password.toCharArray()));
+		
+		TeamModel team = new TeamModel("testteam");
+		team.addUser("test");
+		team.addRepositoryPermission("helloworld.git");
+		assertTrue(RpcUtils.createTeam(team, url, account, password.toCharArray()));
+		
+		users = FederationUtils.getUsers(getRegistration());
+		assertNotNull(users);
+		assertEquals(1, users.size());
+		
+		newUser = users.get(0);
+		assertTrue(newUser.isTeamMember("testteam"));		
+		
+		assertTrue(RpcUtils.deleteUser(newUser, url, account, password.toCharArray()));
+		assertTrue(RpcUtils.deleteTeam(team, url, account, password.toCharArray()));
+	}
+
+	@Test
+	public void testPullTeams() throws Exception {
+		TeamModel team = new TeamModel("testteam");
+		team.addUser("test");
+		team.addRepositoryPermission("helloworld.git");
+		assertTrue(RpcUtils.createTeam(team, url, account, password.toCharArray()));
+		
+		List<TeamModel> teams = FederationUtils.getTeams(getRegistration());
+		assertNotNull(teams);
+		assertTrue(teams.size() > 0);
+		
+		assertTrue(RpcUtils.deleteTeam(team, url, account, password.toCharArray()));
+	}
+	
+	@Test
+	public void testPullScripts() throws Exception {
+		Map<String, String> scripts = FederationUtils.getScripts(getRegistration());
+		assertNotNull(scripts);
+		assertTrue(scripts.keySet().contains("sendmail"));
+	}
+}
diff --git a/src/test/java/com/gitblit/tests/FileUtilsTest.java b/src/test/java/com/gitblit/tests/FileUtilsTest.java
new file mode 100644
index 0000000..26f81d5
--- /dev/null
+++ b/src/test/java/com/gitblit/tests/FileUtilsTest.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2011 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit.tests;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.io.File;
+
+import org.junit.Test;
+
+import com.gitblit.utils.FileUtils;
+
+public class FileUtilsTest {
+
+	@Test
+	public void testReadContent() throws Exception {
+		File dir = new File(System.getProperty("user.dir"));
+		String rawContent = FileUtils.readContent(new File(dir, "LICENSE"), "\n");
+		assertTrue(rawContent.trim().startsWith("Apache License"));
+	}
+
+	@Test
+	public void testWriteContent() throws Exception {
+		String contentA = "this is a test";
+		File tmp = File.createTempFile("gitblit-", ".test");
+		FileUtils.writeContent(tmp, contentA);
+		String contentB = FileUtils.readContent(tmp, "\n").trim();
+		assertEquals(contentA, contentB);
+	}
+
+	@Test
+	public void testFolderSize() throws Exception {
+		assertEquals(-1, FileUtils.folderSize(null));
+		assertEquals(-1, FileUtils.folderSize(new File(System.getProperty("user.dir"), "pretend")));
+
+		File dir = new File(System.getProperty("user.dir"), "src/main/distrib");
+		long size = FileUtils.folderSize(dir);
+		assertTrue("size is actually " + size, size >= 470000L);
+
+		File file = new File(System.getProperty("user.dir"), "LICENSE");
+		size = FileUtils.folderSize(file);
+		assertEquals("size is actually " + size, 11556L, size);
+	}
+	
+	@Test
+	public void testStringSizes() throws Exception {
+		assertEquals(50 * FileUtils.KB, FileUtils.convertSizeToInt("50k", 0));
+		assertEquals(50 * FileUtils.MB, FileUtils.convertSizeToInt("50m", 0));
+		assertEquals(2 * FileUtils.GB, FileUtils.convertSizeToInt("2g", 0));
+
+		assertEquals(50 * FileUtils.KB, FileUtils.convertSizeToInt("50kb", 0));
+		assertEquals(50 * FileUtils.MB, FileUtils.convertSizeToInt("50mb", 0));
+		assertEquals(2 * FileUtils.GB, FileUtils.convertSizeToInt("2gb", 0));
+
+		assertEquals(50L * FileUtils.KB, FileUtils.convertSizeToLong("50k", 0));
+		assertEquals(50L * FileUtils.MB, FileUtils.convertSizeToLong("50m", 0));
+		assertEquals(50L * FileUtils.GB, FileUtils.convertSizeToLong("50g", 0));
+
+		assertEquals(50L * FileUtils.KB, FileUtils.convertSizeToLong("50kb", 0));
+		assertEquals(50L * FileUtils.MB, FileUtils.convertSizeToLong("50mb", 0));
+		assertEquals(50L * FileUtils.GB, FileUtils.convertSizeToLong("50gb", 0));
+		
+		assertEquals(50 * FileUtils.KB, FileUtils.convertSizeToInt("50 k", 0));
+		assertEquals(50 * FileUtils.MB, FileUtils.convertSizeToInt("50 m", 0));
+		assertEquals(2 * FileUtils.GB, FileUtils.convertSizeToInt("2 g", 0));
+
+		assertEquals(50 * FileUtils.KB, FileUtils.convertSizeToInt("50 kb", 0));
+		assertEquals(50 * FileUtils.MB, FileUtils.convertSizeToInt("50 mb", 0));
+		assertEquals(2 * FileUtils.GB, FileUtils.convertSizeToInt("2 gb", 0));
+
+	}
+}
\ No newline at end of file
diff --git a/src/test/java/com/gitblit/tests/GitBlitSuite.java b/src/test/java/com/gitblit/tests/GitBlitSuite.java
new file mode 100644
index 0000000..6fff241
--- /dev/null
+++ b/src/test/java/com/gitblit/tests/GitBlitSuite.java
@@ -0,0 +1,256 @@
+/*
+ * Copyright 2011 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit.tests;
+
+import java.io.File;
+import java.lang.reflect.Field;
+import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.RepositoryCache;
+import org.eclipse.jgit.lib.RepositoryCache.FileKey;
+import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
+import org.eclipse.jgit.util.FS;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+import org.junit.runners.Suite.SuiteClasses;
+
+import com.gitblit.GitBlit;
+import com.gitblit.GitBlitException;
+import com.gitblit.GitBlitServer;
+import com.gitblit.models.RepositoryModel;
+import com.gitblit.utils.JGitUtils;
+
+/**
+ * The GitBlitSuite uses test-gitblit.properties and test-users.conf. The suite
+ * is fairly comprehensive for all lower-level functionality. Wicket pages are
+ * currently not unit-tested.
+ * 
+ * This suite starts a Gitblit server instance within the same JVM instance as
+ * the unit tests. This allows the unit tests to access the GitBlit static
+ * singleton while also being able to communicate with the instance via tcp/ip
+ * for testing rpc requests, federation requests, and git servlet operations.
+ * 
+ * @author James Moger
+ * 
+ */
+@RunWith(Suite.class)
+@SuiteClasses({ ArrayUtilsTest.class, FileUtilsTest.class, TimeUtilsTest.class,
+		StringUtilsTest.class, Base64Test.class, JsonUtilsTest.class, ByteFormatTest.class,
+		ObjectCacheTest.class, PermissionsTest.class, UserServiceTest.class, LdapUserServiceTest.class,
+		MarkdownUtilsTest.class, JGitUtilsTest.class, SyndicationUtilsTest.class,
+		DiffUtilsTest.class, MetricUtilsTest.class, TicgitUtilsTest.class, X509UtilsTest.class,
+		GitBlitTest.class, FederationTests.class, RpcTests.class, GitServletTest.class, GitDaemonTest.class,
+		GroovyScriptTest.class, LuceneExecutorTest.class, IssuesTest.class, RepositoryModelTest.class,
+		FanoutServiceTest.class, Issue0259Test.class, Issue0271Test.class })
+public class GitBlitSuite {
+
+	public static final File REPOSITORIES = new File("data/git");
+	
+	public static final File SETTINGS = new File("src/test/config/test-gitblit.properties");
+	
+	public static final File USERSCONF = new File("src/test/config/test-users.conf");
+
+	static int port = 8280;
+	static int gitPort = 8300;
+	static int shutdownPort = 8281;
+
+	public static String url = "http://localhost:" + port;
+	public static String gitServletUrl = "http://localhost:" + port + "/git";
+	public static String gitDaemonUrl = "git://localhost:" + gitPort;
+	public static String account = "admin";
+	public static String password = "admin";
+
+	private static AtomicBoolean started = new AtomicBoolean(false);
+
+	public static Repository getHelloworldRepository() throws Exception {
+		return getRepository("helloworld.git");
+	}
+
+	public static Repository getTicgitRepository() throws Exception {
+		return getRepository("ticgit.git");
+	}
+
+	public static Repository getJGitRepository() throws Exception {
+		return getRepository("test/jgit.git");
+	}
+
+	public static Repository getAmbitionRepository() throws Exception {
+		return getRepository("test/ambition.git");
+	}
+
+	public static Repository getIssuesTestRepository() throws Exception {
+		JGitUtils.createRepository(REPOSITORIES, "gb-issues.git").close();
+		return getRepository("gb-issues.git");
+	}
+	
+	public static Repository getGitectiveRepository() throws Exception {
+		return getRepository("test/gitective.git");
+	}
+	
+	private static Repository getRepository(String name) throws Exception {
+		File gitDir = FileKey.resolve(new File(REPOSITORIES, name), FS.DETECTED);
+		Repository repository = new FileRepositoryBuilder().setGitDir(gitDir).build();
+		return repository;
+	}
+
+	public static boolean startGitblit() throws Exception {
+		if (started.get()) {
+			// already started
+			return false;
+		}
+		
+		GitServletTest.deleteWorkingFolders();
+		
+		// Start a Gitblit instance
+		Executors.newSingleThreadExecutor().execute(new Runnable() {
+			public void run() {
+				GitBlitServer.main("--httpPort", "" + port, "--httpsPort", "0", "--shutdownPort",
+						"" + shutdownPort, "--gitPort", "" + gitPort, "--repositoriesFolder",
+						"\"" + GitBlitSuite.REPOSITORIES.getAbsolutePath() + "\"", "--userService",
+						GitBlitSuite.USERSCONF.getAbsolutePath(), "--settings", GitBlitSuite.SETTINGS.getAbsolutePath(),
+						"--baseFolder", "data");
+			}
+		});
+
+		// Wait a few seconds for it to be running
+		Thread.sleep(5000);
+
+		started.set(true);
+		return true;
+	}
+
+	public static void stopGitblit() throws Exception {
+		// Stop Gitblit
+		GitBlitServer.main("--stop", "--shutdownPort", "" + shutdownPort);
+
+		// Wait a few seconds for it to be running
+		Thread.sleep(5000);
+	}
+
+	@BeforeClass
+	public static void setUp() throws Exception {
+		startGitblit();
+
+		if (REPOSITORIES.exists() || REPOSITORIES.mkdirs()) {
+			cloneOrFetch("helloworld.git", "https://github.com/git/hello-world.git");
+			cloneOrFetch("ticgit.git", "https://github.com/schacon/ticgit.git");
+			cloneOrFetch("test/jgit.git", "https://github.com/eclipse/jgit.git");
+			cloneOrFetch("test/helloworld.git", "https://github.com/git/hello-world.git");
+			cloneOrFetch("test/ambition.git", "https://github.com/defunkt/ambition.git");
+			cloneOrFetch("test/gitective.git", "https://github.com/kevinsawicki/gitective.git");
+			
+			enableTickets("ticgit.git");
+			enableDocs("ticgit.git");
+			showRemoteBranches("ticgit.git");
+			automaticallyTagBranchTips("ticgit.git");
+			showRemoteBranches("test/jgit.git");
+			automaticallyTagBranchTips("test/jgit.git");	
+		}
+	}
+
+	@AfterClass
+	public static void tearDown() throws Exception {
+		stopGitblit();
+	}
+
+	private static void cloneOrFetch(String name, String fromUrl) throws Exception {
+		System.out.print("Fetching " + name + "... ");
+		try {
+			JGitUtils.cloneRepository(REPOSITORIES, name, fromUrl);
+		} catch (Throwable t) {
+			System.out.println("Error: " + t.getMessage());
+		}
+		System.out.println("done.");
+	}
+
+	private static void enableTickets(String repositoryName) {
+		try {
+			RepositoryModel model = GitBlit.self().getRepositoryModel(repositoryName);
+			model.useTickets = true;
+			GitBlit.self().updateRepositoryModel(model.name, model, false);
+		} catch (GitBlitException g) {
+			g.printStackTrace();
+		}
+	}
+
+	private static void enableDocs(String repositoryName) {
+		try {
+			RepositoryModel model = GitBlit.self().getRepositoryModel(repositoryName);
+			model.useDocs = true;
+			GitBlit.self().updateRepositoryModel(model.name, model, false);
+		} catch (GitBlitException g) {
+			g.printStackTrace();
+		}
+	}
+
+	private static void showRemoteBranches(String repositoryName) {
+		try {
+			RepositoryModel model = GitBlit.self().getRepositoryModel(repositoryName);
+			model.showRemoteBranches = true;
+			GitBlit.self().updateRepositoryModel(model.name, model, false);
+		} catch (GitBlitException g) {
+			g.printStackTrace();
+		}
+	}
+	
+	private static void automaticallyTagBranchTips(String repositoryName) {
+		try {
+			RepositoryModel model = GitBlit.self().getRepositoryModel(repositoryName);
+			model.useIncrementalPushTags = true;
+			GitBlit.self().updateRepositoryModel(model.name, model, false);
+		} catch (GitBlitException g) {
+			g.printStackTrace();
+		}
+	}
+	
+	public static void close(File repository) {
+		try {
+			File gitDir = FileKey.resolve(repository, FS.detect());
+			if (gitDir != null && gitDir.exists()) {
+				close(RepositoryCache.open(FileKey.exact(gitDir, FS.detect())));
+			}
+		} catch (Exception e) {
+			e.printStackTrace();
+		}
+	}
+	
+	public static void close(Git git) {
+		close(git.getRepository());
+	}
+	
+	public static void close(Repository r) {
+		RepositoryCache.close(r);
+
+		// assume 2 uses in case reflection fails
+		int uses = 2;
+		try {
+			Field useCnt = Repository.class.getDeclaredField("useCnt");
+			useCnt.setAccessible(true);
+			uses = ((AtomicInteger) useCnt.get(r)).get();
+		} catch (Exception e) {
+			e.printStackTrace();
+		}
+		for (int i = 0; i < uses; i++) {
+			r.close();
+		}
+	}
+}
diff --git a/src/test/java/com/gitblit/tests/GitBlitTest.java b/src/test/java/com/gitblit/tests/GitBlitTest.java
new file mode 100644
index 0000000..ab23f4e
--- /dev/null
+++ b/src/test/java/com/gitblit/tests/GitBlitTest.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright 2011 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit.tests;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.util.List;
+
+import org.junit.Test;
+
+import com.gitblit.Constants.AccessRestrictionType;
+import com.gitblit.FileSettings;
+import com.gitblit.GitBlit;
+import com.gitblit.models.RepositoryModel;
+import com.gitblit.models.UserModel;
+
+public class GitBlitTest {
+
+	@Test
+	public void testRepositoryModel() throws Exception {
+		List<String> repositories = GitBlit.self().getRepositoryList();
+		assertTrue("Repository list is empty!", repositories.size() > 0);
+		assertTrue(
+				"Missing Helloworld repository!",
+				repositories.contains(GitBlitSuite.getHelloworldRepository().getDirectory()
+						.getName()));
+		RepositoryModel model = GitBlit.self().getRepositoryModel(
+				GitBlitSuite.getHelloworldRepository().getDirectory().getName());
+		assertTrue("Helloworld model is null!", model != null);
+		assertEquals(GitBlitSuite.getHelloworldRepository().getDirectory().getName(), model.name);
+		assertTrue(GitBlit.self().calculateSize(model) > 22000L);
+	}
+
+	@Test
+	public void testUserModel() throws Exception {
+		List<String> users = GitBlit.self().getAllUsernames();
+		assertTrue("No users found!", users.size() > 0);
+		assertTrue("Admin not found", users.contains("admin"));
+		UserModel user = GitBlit.self().getUserModel("admin");
+		assertEquals("admin", user.toString());
+		assertTrue("Admin missing #admin role!", user.canAdmin);
+		user.canAdmin = false;
+		assertFalse("Admin should not have #admin!", user.canAdmin);
+		String repository = GitBlitSuite.getHelloworldRepository().getDirectory().getName();
+		RepositoryModel repositoryModel = GitBlit.self().getRepositoryModel(repository);
+		repositoryModel.accessRestriction = AccessRestrictionType.VIEW;
+		assertFalse("Admin can still access repository!",
+				user.canView(repositoryModel));
+		user.addRepositoryPermission(repository);
+		assertTrue("Admin can't access repository!", user.canView(repositoryModel));
+		assertEquals(GitBlit.self().getRepositoryModel(user, "pretend"), null);
+		assertNotNull(GitBlit.self().getRepositoryModel(user, repository));
+		assertTrue(GitBlit.self().getRepositoryModels(user).size() > 0);
+	}
+	
+	@Test
+	public void testUserModelVerification() throws Exception {
+		UserModel user = new UserModel("james");
+		user.displayName = "James Moger";
+		
+		assertTrue(user.is("James", null));
+		assertTrue(user.is("James", ""));
+		assertTrue(user.is("JaMeS", "anything"));
+		
+		assertTrue(user.is("james moger", null));
+		assertTrue(user.is("james moger", ""));
+		assertTrue(user.is("james moger", "anything"));
+		
+		assertFalse(user.is("joe", null));
+		assertFalse(user.is("joe", ""));
+		assertFalse(user.is("joe", "anything"));
+
+		// specify email address which results in address verification
+		user.emailAddress = "something";
+
+		assertFalse(user.is("James", null));
+		assertFalse(user.is("James", ""));
+		assertFalse(user.is("JaMeS", "anything"));
+		
+		assertFalse(user.is("james moger", null));
+		assertFalse(user.is("james moger", ""));
+		assertFalse(user.is("james moger", "anything"));
+
+		assertTrue(user.is("JaMeS", user.emailAddress));
+		assertTrue(user.is("JaMeS mOgEr", user.emailAddress));
+	}
+
+	@Test
+	public void testAccessRestrictionTypes() throws Exception {
+		assertTrue(AccessRestrictionType.PUSH.exceeds(AccessRestrictionType.NONE));
+		assertTrue(AccessRestrictionType.CLONE.exceeds(AccessRestrictionType.PUSH));
+		assertTrue(AccessRestrictionType.VIEW.exceeds(AccessRestrictionType.CLONE));
+
+		assertFalse(AccessRestrictionType.NONE.exceeds(AccessRestrictionType.PUSH));
+		assertFalse(AccessRestrictionType.PUSH.exceeds(AccessRestrictionType.CLONE));
+		assertFalse(AccessRestrictionType.CLONE.exceeds(AccessRestrictionType.VIEW));
+
+		assertTrue(AccessRestrictionType.PUSH.atLeast(AccessRestrictionType.NONE));
+		assertTrue(AccessRestrictionType.CLONE.atLeast(AccessRestrictionType.PUSH));
+		assertTrue(AccessRestrictionType.VIEW.atLeast(AccessRestrictionType.CLONE));
+
+		assertFalse(AccessRestrictionType.NONE.atLeast(AccessRestrictionType.PUSH));
+		assertFalse(AccessRestrictionType.PUSH.atLeast(AccessRestrictionType.CLONE));
+		assertFalse(AccessRestrictionType.CLONE.atLeast(AccessRestrictionType.VIEW));
+
+		assertTrue(AccessRestrictionType.PUSH.toString().equals("PUSH"));
+		assertTrue(AccessRestrictionType.CLONE.toString().equals("CLONE"));
+		assertTrue(AccessRestrictionType.VIEW.toString().equals("VIEW"));
+
+		assertEquals(AccessRestrictionType.NONE, AccessRestrictionType.fromName("none"));
+		assertEquals(AccessRestrictionType.PUSH, AccessRestrictionType.fromName("push"));
+		assertEquals(AccessRestrictionType.CLONE, AccessRestrictionType.fromName("clone"));
+		assertEquals(AccessRestrictionType.VIEW, AccessRestrictionType.fromName("view"));
+	}
+
+	@Test
+	public void testFileSettings() throws Exception {
+		FileSettings settings = new FileSettings("src/main/distrib/data/gitblit.properties");
+		assertEquals(true, settings.getBoolean("missing", true));
+		assertEquals("default", settings.getString("missing", "default"));
+		assertEquals(10, settings.getInteger("missing", 10));
+		assertEquals(5, settings.getInteger("realm.realmFile", 5));
+
+		assertTrue(settings.getBoolean("git.enableGitServlet", false));
+		assertEquals("${baseFolder}/users.conf", settings.getString("realm.userService", null));
+		assertEquals(5, settings.getInteger("realm.minPasswordLength", 0));
+		List<String> mdExtensions = settings.getStrings("web.markdownExtensions");
+		assertTrue(mdExtensions.size() > 0);
+		assertTrue(mdExtensions.contains("md"));
+
+		List<String> keys = settings.getAllKeys("server");
+		assertTrue(keys.size() > 0);
+		assertTrue(keys.contains("server.httpsPort"));
+
+		assertTrue(settings.getChar("web.forwardSlashCharacter", ' ') == '/');
+	}
+
+	@Test
+	public void testGitblitSettings() throws Exception {
+		// These are already tested by above test method.
+		assertTrue(GitBlit.getBoolean("missing", true));
+		assertEquals("default", GitBlit.getString("missing", "default"));
+		assertEquals(10, GitBlit.getInteger("missing", 10));
+		assertEquals(5, GitBlit.getInteger("realm.userService", 5));
+
+		assertTrue(GitBlit.getBoolean("git.enableGitServlet", false));
+		assertEquals(GitBlitSuite.USERSCONF.getAbsolutePath(), GitBlit.getString("realm.userService", null));
+		assertEquals(5, GitBlit.getInteger("realm.minPasswordLength", 0));
+		List<String> mdExtensions = GitBlit.getStrings("web.markdownExtensions");
+		assertTrue(mdExtensions.size() > 0);
+		assertTrue(mdExtensions.contains("md"));
+
+		List<String> keys = GitBlit.getAllKeys("server");
+		assertTrue(keys.size() > 0);
+		assertTrue(keys.contains("server.httpsPort"));
+
+		assertTrue(GitBlit.getChar("web.forwardSlashCharacter", ' ') == '/');
+		assertFalse(GitBlit.isDebugMode());
+	}
+
+	@Test
+	public void testAuthentication() throws Exception {
+		assertTrue(GitBlit.self().authenticate("admin", "admin".toCharArray()) != null);
+	}
+
+	@Test
+	public void testRepositories() throws Exception {
+		assertTrue(GitBlit.self().getRepository("missing") == null);
+		assertTrue(GitBlit.self().getRepositoryModel("missing") == null);
+	}
+}
diff --git a/src/test/java/com/gitblit/tests/GitDaemonStopTest.java b/src/test/java/com/gitblit/tests/GitDaemonStopTest.java
new file mode 100644
index 0000000..7febc7a
--- /dev/null
+++ b/src/test/java/com/gitblit/tests/GitDaemonStopTest.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2013 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit.tests;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import com.gitblit.git.GitDaemon;
+
+public class GitDaemonStopTest extends Assert {
+
+	@Test
+	public void testGitDaemonStop() throws Exception {
+		GitDaemon daemon = new GitDaemon("localhost", GitDaemon.DEFAULT_PORT + 1, GitBlitSuite.REPOSITORIES);
+		daemon.setTimeout(5);
+		daemon.start();
+		Thread.sleep(5000);
+		daemon.stop();
+	}
+}
diff --git a/src/test/java/com/gitblit/tests/GitDaemonTest.java b/src/test/java/com/gitblit/tests/GitDaemonTest.java
new file mode 100644
index 0000000..6b181d0
--- /dev/null
+++ b/src/test/java/com/gitblit/tests/GitDaemonTest.java
@@ -0,0 +1,312 @@
+/*
+ * Copyright 2013 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit.tests;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.OutputStreamWriter;
+import java.text.MessageFormat;
+import java.util.Date;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.eclipse.jgit.api.CloneCommand;
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.transport.PushResult;
+import org.eclipse.jgit.transport.RemoteRefUpdate;
+import org.eclipse.jgit.transport.RemoteRefUpdate.Status;
+import org.eclipse.jgit.util.FileUtils;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import com.gitblit.Constants.AccessRestrictionType;
+import com.gitblit.Constants.AuthorizationControl;
+import com.gitblit.GitBlit;
+import com.gitblit.models.RepositoryModel;
+
+public class GitDaemonTest extends Assert {
+
+	static File ticgitFolder = new File(GitBlitSuite.REPOSITORIES, "working/ticgit");
+	
+	static File ticgit2Folder = new File(GitBlitSuite.REPOSITORIES, "working/ticgit2");
+
+	static File jgitFolder = new File(GitBlitSuite.REPOSITORIES, "working/jgit");
+	
+	static File jgit2Folder = new File(GitBlitSuite.REPOSITORIES, "working/jgit2");
+
+	String url = GitBlitSuite.gitDaemonUrl;
+
+	private static final AtomicBoolean started = new AtomicBoolean(false);
+
+	@BeforeClass
+	public static void startGitblit() throws Exception {
+		started.set(GitBlitSuite.startGitblit());
+	}
+
+	@AfterClass
+	public static void stopGitblit() throws Exception {
+		if (started.get()) {
+			GitBlitSuite.stopGitblit();
+			deleteWorkingFolders();
+		}
+	}
+
+	public static void deleteWorkingFolders() throws Exception {
+		if (ticgitFolder.exists()) {
+			GitBlitSuite.close(ticgitFolder);
+			FileUtils.delete(ticgitFolder, FileUtils.RECURSIVE);
+		}
+		if (ticgit2Folder.exists()) {
+			GitBlitSuite.close(ticgit2Folder);
+			FileUtils.delete(ticgit2Folder, FileUtils.RECURSIVE);
+		}
+		if (jgitFolder.exists()) {
+			GitBlitSuite.close(jgitFolder);
+			FileUtils.delete(jgitFolder, FileUtils.RECURSIVE);
+		}
+		if (jgit2Folder.exists()) {
+			GitBlitSuite.close(jgit2Folder);
+			FileUtils.delete(jgit2Folder, FileUtils.RECURSIVE);
+		}
+	}
+
+	@Test
+	public void testAnonymousClone() throws Exception {
+		GitBlitSuite.close(ticgitFolder);
+		if (ticgitFolder.exists()) {
+			FileUtils.delete(ticgitFolder, FileUtils.RECURSIVE | FileUtils.RETRY);
+		}
+
+		// set push restriction
+		RepositoryModel model = GitBlit.self().getRepositoryModel("ticgit.git");
+		model.accessRestriction = AccessRestrictionType.PUSH;
+		model.authorizationControl = AuthorizationControl.NAMED;
+		GitBlit.self().updateRepositoryModel(model.name, model, false);
+		
+		CloneCommand clone = Git.cloneRepository();
+		clone.setURI(MessageFormat.format("{0}/ticgit.git", url));
+		clone.setDirectory(ticgitFolder);
+		clone.setBare(false);
+		clone.setCloneAllBranches(true);
+		GitBlitSuite.close(clone.call());		
+		assertTrue(true);
+		
+		// restore anonymous repository access
+		model.accessRestriction = AccessRestrictionType.NONE;
+		model.authorizationControl = AuthorizationControl.NAMED;
+		GitBlit.self().updateRepositoryModel(model.name, model, false);
+	}
+	
+	@Test
+	public void testCloneRestrictedRepo() throws Exception {
+		GitBlitSuite.close(ticgit2Folder);
+		if (ticgit2Folder.exists()) {
+			FileUtils.delete(ticgit2Folder, FileUtils.RECURSIVE);
+		}
+
+		// restrict repository access
+		RepositoryModel model = GitBlit.self().getRepositoryModel("ticgit.git");
+		model.accessRestriction = AccessRestrictionType.CLONE;
+		model.authorizationControl = AuthorizationControl.NAMED;
+		GitBlit.self().updateRepositoryModel(model.name, model, false);
+		
+		// delete any existing working folder		
+		boolean cloned = false;
+		try {
+			CloneCommand clone = Git.cloneRepository();
+			clone.setURI(MessageFormat.format("{0}/ticgit.git", url));
+			clone.setDirectory(ticgit2Folder);
+			clone.setBare(false);
+			clone.setCloneAllBranches(true);
+			GitBlitSuite.close(clone.call());
+			cloned = true;
+		} catch (Exception e) {
+			// swallow the exception which we expect
+		}
+
+		assertFalse("Anonymous was able to clone the repository?!", cloned);
+
+		FileUtils.delete(ticgit2Folder, FileUtils.RECURSIVE);
+		
+		// restore anonymous repository access
+		model.accessRestriction = AccessRestrictionType.NONE;
+		model.authorizationControl = AuthorizationControl.NAMED;
+		GitBlit.self().updateRepositoryModel(model.name, model, false);
+	}
+
+	@Test
+	public void testAnonymousPush() throws Exception {
+		GitBlitSuite.close(ticgitFolder);
+		if (ticgitFolder.exists()) {
+			FileUtils.delete(ticgitFolder, FileUtils.RECURSIVE | FileUtils.RETRY);
+		}
+
+		// restore anonymous repository access
+		RepositoryModel model = GitBlit.self().getRepositoryModel("ticgit.git");
+		model.accessRestriction = AccessRestrictionType.NONE;
+		model.authorizationControl = AuthorizationControl.NAMED;
+		GitBlit.self().updateRepositoryModel(model.name, model, false);
+
+		CloneCommand clone = Git.cloneRepository();
+		clone.setURI(MessageFormat.format("{0}/ticgit.git", url));
+		clone.setDirectory(ticgitFolder);
+		clone.setBare(false);
+		clone.setCloneAllBranches(true);
+		GitBlitSuite.close(clone.call());		
+		assertTrue(true);
+		
+		Git git = Git.open(ticgitFolder);
+		File file = new File(ticgitFolder, "TODO");
+		OutputStreamWriter os = new OutputStreamWriter(new FileOutputStream(file, true), Constants.CHARSET);
+		BufferedWriter w = new BufferedWriter(os);
+		w.write("// hellol中文 " + new Date().toString() + "\n");
+		w.close();
+		git.add().addFilepattern(file.getName()).call();
+		git.commit().setMessage("test commit").call();
+		Iterable<PushResult> results = git.push().setPushAll().call();
+		GitBlitSuite.close(git);
+		for (PushResult result : results) {
+			for (RemoteRefUpdate update : result.getRemoteUpdates()) {
+				assertEquals(Status.OK, update.getStatus());
+			}
+		}
+	}
+
+	@Test
+	public void testPushRestrictedRepo() throws Exception {
+		GitBlitSuite.close(ticgitFolder);
+		if (ticgitFolder.exists()) {
+			FileUtils.delete(ticgitFolder, FileUtils.RECURSIVE | FileUtils.RETRY);
+		}
+
+		// restore anonymous repository access
+		RepositoryModel model = GitBlit.self().getRepositoryModel("ticgit.git");
+		model.accessRestriction = AccessRestrictionType.PUSH;
+		model.authorizationControl = AuthorizationControl.NAMED;
+		GitBlit.self().updateRepositoryModel(model.name, model, false);
+
+		CloneCommand clone = Git.cloneRepository();
+		clone.setURI(MessageFormat.format("{0}/ticgit.git", url));
+		clone.setDirectory(ticgitFolder);
+		clone.setBare(false);
+		clone.setCloneAllBranches(true);
+		GitBlitSuite.close(clone.call());		
+		assertTrue(true);
+		
+		Git git = Git.open(ticgitFolder);
+		File file = new File(ticgitFolder, "TODO");
+		OutputStreamWriter os = new OutputStreamWriter(new FileOutputStream(file, true), Constants.CHARSET);
+		BufferedWriter w = new BufferedWriter(os);
+		w.write("// hellol中文 " + new Date().toString() + "\n");
+		w.close();
+		git.add().addFilepattern(file.getName()).call();
+		git.commit().setMessage("test commit").call();
+		Iterable<PushResult> results = git.push().setPushAll().call();
+		GitBlitSuite.close(git);
+		for (PushResult result : results) {
+			for (RemoteRefUpdate update : result.getRemoteUpdates()) {
+				assertEquals(Status.REJECTED_OTHER_REASON, update.getStatus());
+			}
+		}
+	}
+
+	@Test
+	public void testPushToFrozenRepo() throws Exception {
+		GitBlitSuite.close(jgitFolder);
+		if (jgitFolder.exists()) {
+			FileUtils.delete(jgitFolder, FileUtils.RECURSIVE | FileUtils.RETRY);
+		}
+		
+		CloneCommand clone = Git.cloneRepository();
+		clone.setURI(MessageFormat.format("{0}/test/jgit.git", url));
+		clone.setDirectory(jgitFolder);
+		clone.setBare(false);
+		clone.setCloneAllBranches(true);
+		GitBlitSuite.close(clone.call());
+		assertTrue(true);
+		
+		// freeze repo
+		RepositoryModel model = GitBlit.self().getRepositoryModel("test/jgit.git");
+		model.isFrozen = true;
+		GitBlit.self().updateRepositoryModel(model.name, model, false);
+
+		Git git = Git.open(jgitFolder);
+		File file = new File(jgitFolder, "TODO");
+		OutputStreamWriter os = new OutputStreamWriter(new FileOutputStream(file, true), Constants.CHARSET);
+		BufferedWriter w = new BufferedWriter(os);
+		w.write("// " + new Date().toString() + "\n");
+		w.close();
+		git.add().addFilepattern(file.getName()).call();
+		git.commit().setMessage("test commit").call();
+		
+		Iterable<PushResult> results = git.push().call();
+		for (PushResult result : results) {
+			for (RemoteRefUpdate update : result.getRemoteUpdates()) {
+				assertEquals(Status.REJECTED_OTHER_REASON, update.getStatus());
+			}
+		}
+	
+		// unfreeze repo
+		model.isFrozen = false;
+		GitBlit.self().updateRepositoryModel(model.name, model, false);
+
+		results = git.push().setPushAll().call();
+		GitBlitSuite.close(git);
+		for (PushResult result : results) {
+			for (RemoteRefUpdate update : result.getRemoteUpdates()) {
+				assertEquals(Status.OK, update.getStatus());
+			}
+		}
+	}
+	
+	@Test
+	public void testPushToNonBareRepository() throws Exception {
+		GitBlitSuite.close(jgit2Folder);
+		if (jgit2Folder.exists()) {
+			FileUtils.delete(jgit2Folder, FileUtils.RECURSIVE | FileUtils.RETRY);
+		}
+		
+		CloneCommand clone = Git.cloneRepository();
+		clone.setURI(MessageFormat.format("{0}/working/jgit", url));
+		clone.setDirectory(jgit2Folder);
+		clone.setBare(false);
+		clone.setCloneAllBranches(true);
+		GitBlitSuite.close(clone.call());
+		assertTrue(true);
+
+		Git git = Git.open(jgit2Folder);
+		File file = new File(jgit2Folder, "NONBARE");
+		OutputStreamWriter os = new OutputStreamWriter(new FileOutputStream(file, true), Constants.CHARSET);
+		BufferedWriter w = new BufferedWriter(os);
+		w.write("// " + new Date().toString() + "\n");
+		w.close();
+		git.add().addFilepattern(file.getName()).call();
+		git.commit().setMessage("test commit followed by push to non-bare repository").call();
+
+		Iterable<PushResult> results = git.push().setPushAll().call();
+		GitBlitSuite.close(git);
+		
+		for (PushResult result : results) {
+			for (RemoteRefUpdate update : result.getRemoteUpdates()) {
+				assertEquals(Status.REJECTED_OTHER_REASON, update.getStatus());
+			}
+		}
+	}
+
+}
diff --git a/src/test/java/com/gitblit/tests/GitServletTest.java b/src/test/java/com/gitblit/tests/GitServletTest.java
new file mode 100644
index 0000000..b6a58ac
--- /dev/null
+++ b/src/test/java/com/gitblit/tests/GitServletTest.java
@@ -0,0 +1,920 @@
+package com.gitblit.tests;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.text.MessageFormat;
+import java.util.Date;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.eclipse.jgit.api.CloneCommand;
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.api.MergeCommand.FastForwardMode;
+import org.eclipse.jgit.api.MergeResult;
+import org.eclipse.jgit.api.ResetCommand.ResetType;
+import org.eclipse.jgit.api.errors.GitAPIException;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
+import org.eclipse.jgit.transport.CredentialsProvider;
+import org.eclipse.jgit.transport.PushResult;
+import org.eclipse.jgit.transport.RefSpec;
+import org.eclipse.jgit.transport.RemoteRefUpdate;
+import org.eclipse.jgit.transport.RemoteRefUpdate.Status;
+import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;
+import org.eclipse.jgit.util.FileUtils;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import com.gitblit.Constants.AccessPermission;
+import com.gitblit.Constants.AccessRestrictionType;
+import com.gitblit.Constants.AuthorizationControl;
+import com.gitblit.GitBlit;
+import com.gitblit.Keys;
+import com.gitblit.models.RefLogEntry;
+import com.gitblit.models.RepositoryModel;
+import com.gitblit.models.UserModel;
+import com.gitblit.utils.ArrayUtils;
+import com.gitblit.utils.JGitUtils;
+import com.gitblit.utils.RefLogUtils;
+
+public class GitServletTest {
+
+	static File ticgitFolder = new File(GitBlitSuite.REPOSITORIES, "working/ticgit");
+	
+	static File ticgit2Folder = new File(GitBlitSuite.REPOSITORIES, "working/ticgit2");
+
+	static File jgitFolder = new File(GitBlitSuite.REPOSITORIES, "working/jgit");
+	
+	static File jgit2Folder = new File(GitBlitSuite.REPOSITORIES, "working/jgit2");
+
+	String url = GitBlitSuite.gitServletUrl;
+	String account = GitBlitSuite.account;
+	String password = GitBlitSuite.password;
+
+	private static final AtomicBoolean started = new AtomicBoolean(false);
+	
+	private static UserModel getUser() {
+		UserModel user = new UserModel("james");
+		user.displayName = "James Moger";
+		user.emailAddress = "james.moger@gmail.com";
+		user.password = "james";
+		return user;
+	}
+	
+	private static void delete(UserModel user) {
+		if (GitBlit.self().getUserModel(user.username) != null) {
+			GitBlit.self().deleteUser(user.username);
+		}
+	}
+
+	@BeforeClass
+	public static void startGitblit() throws Exception {
+		started.set(GitBlitSuite.startGitblit());
+
+		delete(getUser());
+	}
+
+	@AfterClass
+	public static void stopGitblit() throws Exception {
+		if (started.get()) {
+			GitBlitSuite.stopGitblit();
+			deleteWorkingFolders();
+		}
+		
+		delete(getUser());
+	}
+	
+	public static void deleteWorkingFolders() throws Exception {
+		if (ticgitFolder.exists()) {
+			GitBlitSuite.close(ticgitFolder);
+			FileUtils.delete(ticgitFolder, FileUtils.RECURSIVE);
+		}
+		if (ticgit2Folder.exists()) {
+			GitBlitSuite.close(ticgit2Folder);
+			FileUtils.delete(ticgit2Folder, FileUtils.RECURSIVE);
+		}
+		if (jgitFolder.exists()) {
+			GitBlitSuite.close(jgitFolder);
+			FileUtils.delete(jgitFolder, FileUtils.RECURSIVE);
+		}
+		if (jgit2Folder.exists()) {
+			GitBlitSuite.close(jgit2Folder);
+			FileUtils.delete(jgit2Folder, FileUtils.RECURSIVE);
+		}
+	}
+
+	@Test
+	public void testClone() throws Exception {
+		GitBlitSuite.close(ticgitFolder);
+		if (ticgitFolder.exists()) {
+			FileUtils.delete(ticgitFolder, FileUtils.RECURSIVE | FileUtils.RETRY);
+		}
+		
+		CloneCommand clone = Git.cloneRepository();
+		clone.setURI(MessageFormat.format("{0}/ticgit.git", url));
+		clone.setDirectory(ticgitFolder);
+		clone.setBare(false);
+		clone.setCloneAllBranches(true);
+		clone.setCredentialsProvider(new UsernamePasswordCredentialsProvider(account, password));
+		GitBlitSuite.close(clone.call());		
+		assertTrue(true);
+	}
+
+	@Test
+	public void testBogusLoginClone() throws Exception {
+		// restrict repository access
+		RepositoryModel model = GitBlit.self().getRepositoryModel("ticgit.git");
+		model.accessRestriction = AccessRestrictionType.CLONE;
+		GitBlit.self().updateRepositoryModel(model.name, model, false);
+
+		// delete any existing working folder		
+		boolean cloned = false;
+		try {
+			CloneCommand clone = Git.cloneRepository();
+			clone.setURI(MessageFormat.format("{0}/ticgit.git", url));
+			clone.setDirectory(ticgit2Folder);
+			clone.setBare(false);
+			clone.setCloneAllBranches(true);
+			clone.setCredentialsProvider(new UsernamePasswordCredentialsProvider("bogus", "bogus"));
+			GitBlitSuite.close(clone.call());
+			cloned = true;
+		} catch (Exception e) {
+			// swallow the exception which we expect
+		}
+
+		// restore anonymous repository access
+		model.accessRestriction = AccessRestrictionType.NONE;
+		GitBlit.self().updateRepositoryModel(model.name, model, false);
+
+		assertFalse("Bogus login cloned a repository?!", cloned);
+	}
+	
+	@Test
+	public void testUnauthorizedLoginClone() throws Exception {
+		// restrict repository access
+		RepositoryModel model = GitBlit.self().getRepositoryModel("ticgit.git");
+		model.accessRestriction = AccessRestrictionType.CLONE;
+		model.authorizationControl = AuthorizationControl.NAMED;
+		UserModel user = new UserModel("james");
+		user.password = "james";
+		GitBlit.self().updateUserModel(user.username, user, true);
+		GitBlit.self().updateRepositoryModel(model.name, model, false);
+
+		FileUtils.delete(ticgit2Folder, FileUtils.RECURSIVE);
+		
+		// delete any existing working folder		
+		boolean cloned = false;
+		try {
+			CloneCommand clone = Git.cloneRepository();
+			clone.setURI(MessageFormat.format("{0}/ticgit.git", url));
+			clone.setDirectory(ticgit2Folder);
+			clone.setBare(false);
+			clone.setCloneAllBranches(true);
+			clone.setCredentialsProvider(new UsernamePasswordCredentialsProvider(user.username, user.password));
+			GitBlitSuite.close(clone.call());
+			cloned = true;
+		} catch (Exception e) {
+			// swallow the exception which we expect
+		}
+
+		assertFalse("Unauthorized login cloned a repository?!", cloned);
+
+		FileUtils.delete(ticgit2Folder, FileUtils.RECURSIVE);
+		
+		// switch to authenticated
+		model.authorizationControl = AuthorizationControl.AUTHENTICATED;
+		GitBlit.self().updateRepositoryModel(model.name, model, false);
+		
+		// try clone again
+		cloned = false;
+		CloneCommand clone = Git.cloneRepository();
+		clone.setURI(MessageFormat.format("{0}/ticgit.git", url));
+		clone.setDirectory(ticgit2Folder);
+		clone.setBare(false);
+		clone.setCloneAllBranches(true);
+		clone.setCredentialsProvider(new UsernamePasswordCredentialsProvider(user.username, user.password));
+		GitBlitSuite.close(clone.call());
+		cloned = true;
+
+		assertTrue("Authenticated login could not clone!", cloned);
+		
+		FileUtils.delete(ticgit2Folder, FileUtils.RECURSIVE);
+		
+		// restore anonymous repository access
+		model.accessRestriction = AccessRestrictionType.NONE;
+		model.authorizationControl = AuthorizationControl.NAMED;
+		GitBlit.self().updateRepositoryModel(model.name, model, false);
+		
+		delete(user);		
+	}
+
+	@Test
+	public void testAnonymousPush() throws Exception {
+		GitBlitSuite.close(ticgitFolder);
+		if (ticgitFolder.exists()) {
+			FileUtils.delete(ticgitFolder, FileUtils.RECURSIVE | FileUtils.RETRY);
+		}
+
+		RepositoryModel model = GitBlit.self().getRepositoryModel("ticgit.git");
+		model.accessRestriction = AccessRestrictionType.NONE;
+		GitBlit.self().updateRepositoryModel(model.name, model, false);
+
+		CloneCommand clone = Git.cloneRepository();
+		clone.setURI(MessageFormat.format("{0}/ticgit.git", url));
+		clone.setDirectory(ticgitFolder);
+		clone.setBare(false);
+		clone.setCloneAllBranches(true);
+		clone.setCredentialsProvider(new UsernamePasswordCredentialsProvider(account, password));
+		GitBlitSuite.close(clone.call());		
+		assertTrue(true);
+		
+		Git git = Git.open(ticgitFolder);
+		File file = new File(ticgitFolder, "TODO");
+		OutputStreamWriter os = new OutputStreamWriter(new FileOutputStream(file, true), Constants.CHARSET);
+		BufferedWriter w = new BufferedWriter(os);
+		w.write("// hellol中文 " + new Date().toString() + "\n");
+		w.close();
+		git.add().addFilepattern(file.getName()).call();
+		git.commit().setMessage("test commit").call();
+		Iterable<PushResult> results = git.push().setPushAll().call();
+		GitBlitSuite.close(git);
+		for (PushResult result : results) {
+			for (RemoteRefUpdate update : result.getRemoteUpdates()) {
+				assertEquals(Status.OK, update.getStatus());
+			}
+		}
+	}
+
+	@Test
+	public void testSubfolderPush() throws Exception {
+		GitBlitSuite.close(jgitFolder);
+		if (jgitFolder.exists()) {
+			FileUtils.delete(jgitFolder, FileUtils.RECURSIVE | FileUtils.RETRY);
+		}
+		
+		CloneCommand clone = Git.cloneRepository();
+		clone.setURI(MessageFormat.format("{0}/test/jgit.git", url));
+		clone.setDirectory(jgitFolder);
+		clone.setBare(false);
+		clone.setCloneAllBranches(true);
+		clone.setCredentialsProvider(new UsernamePasswordCredentialsProvider(account, password));
+		GitBlitSuite.close(clone.call());
+		assertTrue(true);
+
+		Git git = Git.open(jgitFolder);
+		File file = new File(jgitFolder, "TODO");
+		OutputStreamWriter os = new OutputStreamWriter(new FileOutputStream(file, true), Constants.CHARSET);
+		BufferedWriter w = new BufferedWriter(os);
+		w.write("// " + new Date().toString() + "\n");
+		w.close();
+		git.add().addFilepattern(file.getName()).call();
+		git.commit().setMessage("test commit").call();
+		Iterable<PushResult> results = git.push().setPushAll().setCredentialsProvider(new UsernamePasswordCredentialsProvider(account, password)).call();
+		GitBlitSuite.close(git);
+		for (PushResult result : results) {
+			for (RemoteRefUpdate update : result.getRemoteUpdates()) {
+				assertEquals(Status.OK, update.getStatus());
+			}
+		}
+	}
+	
+	@Test
+	public void testPushToFrozenRepo() throws Exception {
+		GitBlitSuite.close(jgitFolder);
+		if (jgitFolder.exists()) {
+			FileUtils.delete(jgitFolder, FileUtils.RECURSIVE | FileUtils.RETRY);
+		}
+		
+		CloneCommand clone = Git.cloneRepository();
+		clone.setURI(MessageFormat.format("{0}/test/jgit.git", url));
+		clone.setDirectory(jgitFolder);
+		clone.setBare(false);
+		clone.setCloneAllBranches(true);
+		clone.setCredentialsProvider(new UsernamePasswordCredentialsProvider(account, password));
+		GitBlitSuite.close(clone.call());
+		assertTrue(true);
+		
+		// freeze repo
+		RepositoryModel model = GitBlit.self().getRepositoryModel("test/jgit.git");
+		model.isFrozen = true;
+		GitBlit.self().updateRepositoryModel(model.name, model, false);
+
+		Git git = Git.open(jgitFolder);
+		File file = new File(jgitFolder, "TODO");
+		OutputStreamWriter os = new OutputStreamWriter(new FileOutputStream(file, true), Constants.CHARSET);
+		BufferedWriter w = new BufferedWriter(os);
+		w.write("// " + new Date().toString() + "\n");
+		w.close();
+		git.add().addFilepattern(file.getName()).call();
+		git.commit().setMessage("test commit").call();
+		
+		Iterable<PushResult> results = git.push().setPushAll().setCredentialsProvider(new UsernamePasswordCredentialsProvider(account, password)).call();
+		for (PushResult result : results) {
+			for (RemoteRefUpdate update : result.getRemoteUpdates()) {
+				assertEquals(Status.REJECTED_OTHER_REASON, update.getStatus());
+			}
+		}
+		
+		// unfreeze repo
+		model.isFrozen = false;
+		GitBlit.self().updateRepositoryModel(model.name, model, false);
+
+		results = git.push().setPushAll().setCredentialsProvider(new UsernamePasswordCredentialsProvider(account, password)).call();
+		GitBlitSuite.close(git);
+		for (PushResult result : results) {
+			for (RemoteRefUpdate update : result.getRemoteUpdates()) {
+				assertEquals(Status.OK, update.getStatus());
+			}
+		}
+	}
+	
+	@Test
+	public void testPushToNonBareRepository() throws Exception {
+		CloneCommand clone = Git.cloneRepository();
+		clone.setURI(MessageFormat.format("{0}/working/jgit", url));
+		clone.setDirectory(jgit2Folder);
+		clone.setBare(false);
+		clone.setCloneAllBranches(true);
+		clone.setCredentialsProvider(new UsernamePasswordCredentialsProvider(account, password));
+		GitBlitSuite.close(clone.call());
+		assertTrue(true);
+
+		Git git = Git.open(jgit2Folder);
+		File file = new File(jgit2Folder, "NONBARE");
+		OutputStreamWriter os = new OutputStreamWriter(new FileOutputStream(file, true), Constants.CHARSET);
+		BufferedWriter w = new BufferedWriter(os);
+		w.write("// " + new Date().toString() + "\n");
+		w.close();
+		git.add().addFilepattern(file.getName()).call();
+		git.commit().setMessage("test commit followed by push to non-bare repository").call();
+		Iterable<PushResult> results = git.push().setPushAll().setCredentialsProvider(new UsernamePasswordCredentialsProvider(account, password)).call();
+		GitBlitSuite.close(git);
+		for (PushResult result : results) {
+			for (RemoteRefUpdate update : result.getRemoteUpdates()) {
+				assertEquals(Status.REJECTED_OTHER_REASON, update.getStatus());
+			}
+		}
+	}
+
+	@Test
+	public void testCommitterVerification() throws Exception {
+		UserModel user = getUser();
+
+		// account only uses account name to verify
+		testCommitterVerification(user, user.username, null, true);
+		// committer email address is ignored because account does not specify email
+		testCommitterVerification(user, user.username, "something", true);
+		// completely different committer
+		testCommitterVerification(user, "joe", null, false);
+
+		// test display name verification
+		user.displayName = "James Moger";
+		testCommitterVerification(user, user.displayName, null, true);
+		testCommitterVerification(user, user.displayName, "something", true);
+		testCommitterVerification(user, "joe", null, false);
+		
+		// test email address verification
+		user.emailAddress = "something";
+		testCommitterVerification(user, user.displayName, null, false);
+		testCommitterVerification(user, user.displayName, "somethingelse", false);
+		testCommitterVerification(user, user.displayName, user.emailAddress, true);
+		
+		// use same email address but with different committer
+		testCommitterVerification(user, "joe", "somethingelse", false);
+	}
+	
+	private void testCommitterVerification(UserModel user, String displayName, String emailAddress, boolean expectedSuccess) throws Exception {
+		
+		delete(user);
+		
+		CredentialsProvider cp = new UsernamePasswordCredentialsProvider(user.username, user.password);
+		
+		// fork from original to a temporary bare repo
+		File verification = new File(GitBlitSuite.REPOSITORIES, "refchecks/verify-committer.git");
+		if (verification.exists()) {
+			FileUtils.delete(verification, FileUtils.RECURSIVE);
+		}
+		CloneCommand clone = Git.cloneRepository();
+		clone.setURI(MessageFormat.format("{0}/ticgit.git", url));
+		clone.setDirectory(verification);
+		clone.setBare(true);
+		clone.setCloneAllBranches(true);
+		clone.setCredentialsProvider(cp);
+		GitBlitSuite.close(clone.call());
+		
+		// require push permissions and committer verification
+		RepositoryModel model = GitBlit.self().getRepositoryModel("refchecks/verify-committer.git");
+		model.authorizationControl = AuthorizationControl.NAMED;
+		model.accessRestriction = AccessRestrictionType.PUSH;
+		model.verifyCommitter = true;
+		
+		// grant user push permission
+		user.setRepositoryPermission(model.name, AccessPermission.PUSH);
+		
+		GitBlit.self().updateUserModel(user.username, user, true);
+		GitBlit.self().updateRepositoryModel(model.name, model, false);
+
+		// clone temp bare repo to working copy
+		File local = new File(GitBlitSuite.REPOSITORIES, "refchecks/verify-wc");
+		if (local.exists()) {
+			FileUtils.delete(local, FileUtils.RECURSIVE);
+		}
+		clone = Git.cloneRepository();
+		clone.setURI(MessageFormat.format("{0}/{1}", url, model.name));
+		clone.setDirectory(local);
+		clone.setBare(false);
+		clone.setCloneAllBranches(true);
+		clone.setCredentialsProvider(cp);
+		GitBlitSuite.close(clone.call());
+		
+		Git git = Git.open(local);
+		
+		// force an identity which may or may not match the account's identity
+		git.getRepository().getConfig().setString("user", null, "name", displayName);
+		git.getRepository().getConfig().setString("user", null, "email", emailAddress);
+		git.getRepository().getConfig().save();
+		
+		// commit a file and push it
+		File file = new File(local, "PUSHCHK");
+		OutputStreamWriter os = new OutputStreamWriter(new FileOutputStream(file, true), Constants.CHARSET);
+		BufferedWriter w = new BufferedWriter(os);
+		w.write("// " + new Date().toString() + "\n");
+		w.close();
+		git.add().addFilepattern(file.getName()).call();
+		git.commit().setMessage("push test").call();
+		Iterable<PushResult> results = git.push().setCredentialsProvider(cp).setRemote("origin").call();
+		
+		for (PushResult result : results) {
+			RemoteRefUpdate ref = result.getRemoteUpdate("refs/heads/master");
+			Status status = ref.getStatus();
+			if (expectedSuccess) {
+				assertTrue("Verification failed! User was NOT able to push commit! " + status.name(), Status.OK.equals(status));
+			} else {
+				assertTrue("Verification failed! User was able to push commit! " + status.name(), Status.REJECTED_OTHER_REASON.equals(status));
+			}
+		}
+		
+		GitBlitSuite.close(git);
+		// close serving repository
+		GitBlitSuite.close(verification);
+	}
+	
+	@Test
+	public void testMergeCommitterVerification() throws Exception {
+		
+		testMergeCommitterVerification(false);
+		
+		testMergeCommitterVerification(true);
+	}
+	
+	private void testMergeCommitterVerification(boolean expectedSuccess) throws Exception {
+		UserModel user = getUser();
+		
+		delete(user);
+		
+		CredentialsProvider cp = new UsernamePasswordCredentialsProvider(user.username, user.password);
+		
+		// fork from original to a temporary bare repo
+		File verification = new File(GitBlitSuite.REPOSITORIES, "refchecks/verify-committer.git");
+		if (verification.exists()) {
+			FileUtils.delete(verification, FileUtils.RECURSIVE);
+		}
+		CloneCommand clone = Git.cloneRepository();
+		clone.setURI(MessageFormat.format("{0}/ticgit.git", url));
+		clone.setDirectory(verification);
+		clone.setBare(true);
+		clone.setCloneAllBranches(true);
+		clone.setCredentialsProvider(cp);
+		GitBlitSuite.close(clone.call());
+		
+		// require push permissions and committer verification
+		RepositoryModel model = GitBlit.self().getRepositoryModel("refchecks/verify-committer.git");
+		model.authorizationControl = AuthorizationControl.NAMED;
+		model.accessRestriction = AccessRestrictionType.PUSH;
+		model.verifyCommitter = true;
+		
+		// grant user push permission
+		user.setRepositoryPermission(model.name, AccessPermission.PUSH);
+		
+		GitBlit.self().updateUserModel(user.username, user, true);
+		GitBlit.self().updateRepositoryModel(model.name, model, false);
+
+		// clone temp bare repo to working copy
+		File local = new File(GitBlitSuite.REPOSITORIES, "refchecks/verify-wc");
+		if (local.exists()) {
+			FileUtils.delete(local, FileUtils.RECURSIVE);
+		}
+		clone = Git.cloneRepository();
+		clone.setURI(MessageFormat.format("{0}/{1}", url, model.name));
+		clone.setDirectory(local);
+		clone.setBare(false);
+		clone.setCloneAllBranches(true);
+		clone.setCredentialsProvider(cp);
+		GitBlitSuite.close(clone.call());
+		
+		Git git = Git.open(local);
+		
+		// checkout a mergetest branch
+		git.checkout().setCreateBranch(true).setName("mergetest").call();
+		
+		// change identity
+		git.getRepository().getConfig().setString("user", null, "name", "mergetest");
+		git.getRepository().getConfig().setString("user", null, "email", "mergetest@merge.com");
+		git.getRepository().getConfig().save();
+		
+		// commit a file
+		File file = new File(local, "MERGECHK2");
+		OutputStreamWriter os = new OutputStreamWriter(new FileOutputStream(file, true), Constants.CHARSET);
+		BufferedWriter w = new BufferedWriter(os);
+		w.write("// " + new Date().toString() + "\n");
+		w.close();
+		git.add().addFilepattern(file.getName()).call();
+		RevCommit mergeTip = git.commit().setMessage(file.getName() + " test").call();
+				
+		// return to master
+		git.checkout().setName("master").call();
+
+		// restore identity
+		if (expectedSuccess) {
+			git.getRepository().getConfig().setString("user", null, "name", user.username);
+			git.getRepository().getConfig().setString("user", null, "email", user.emailAddress);
+			git.getRepository().getConfig().save();
+		}
+
+		// commit a file
+		file = new File(local, "MERGECHK1");
+		os = new OutputStreamWriter(new FileOutputStream(file, true), Constants.CHARSET);
+		w = new BufferedWriter(os);
+		w.write("// " + new Date().toString() + "\n");
+		w.close();
+		git.add().addFilepattern(file.getName()).call();
+		git.commit().setMessage(file.getName() + " test").call();
+		
+		// merge the tip of the mergetest branch into master with --no-ff
+		MergeResult mergeResult = git.merge().setFastForward(FastForwardMode.NO_FF).include(mergeTip.getId()).call();
+		assertEquals(MergeResult.MergeStatus.MERGED, mergeResult.getMergeStatus());
+		
+		// push the merged master to the origin
+		Iterable<PushResult> results = git.push().setCredentialsProvider(cp).setRemote("origin").call();
+		
+		for (PushResult result : results) {
+			RemoteRefUpdate ref = result.getRemoteUpdate("refs/heads/master");
+			Status status = ref.getStatus();
+			if (expectedSuccess) {
+				assertTrue("Verification failed! User was NOT able to push commit! " + status.name(), Status.OK.equals(status));
+			} else {
+				assertTrue("Verification failed! User was able to push commit! " + status.name(), Status.REJECTED_OTHER_REASON.equals(status));
+			}
+		}
+		
+		GitBlitSuite.close(git);
+		// close serving repository
+		GitBlitSuite.close(verification);
+	}
+
+	@Test
+	public void testBlockClone() throws Exception {
+		testRefChange(AccessPermission.VIEW, null, null, null);
+	}
+
+	@Test
+	public void testBlockPush() throws Exception {
+		testRefChange(AccessPermission.CLONE, null, null, null);
+	}
+
+	@Test
+	public void testBlockBranchCreation() throws Exception {
+		testRefChange(AccessPermission.PUSH, Status.REJECTED_OTHER_REASON, null, null);
+	}
+
+	@Test
+	public void testBlockBranchDeletion() throws Exception {
+		testRefChange(AccessPermission.CREATE, Status.OK, Status.REJECTED_OTHER_REASON, null);
+	}
+	
+	@Test
+	public void testBlockBranchRewind() throws Exception {
+		testRefChange(AccessPermission.DELETE, Status.OK, Status.OK, Status.REJECTED_OTHER_REASON);
+	}
+
+	@Test
+	public void testBranchRewind() throws Exception {		
+		testRefChange(AccessPermission.REWIND, Status.OK, Status.OK, Status.OK);
+	}
+
+	private void testRefChange(AccessPermission permission, Status expectedCreate, Status expectedDelete, Status expectedRewind) throws Exception {
+
+		UserModel user = getUser();
+		delete(user);
+		
+		CredentialsProvider cp = new UsernamePasswordCredentialsProvider(user.username, user.password);
+		
+		// fork from original to a temporary bare repo
+		File refChecks = new File(GitBlitSuite.REPOSITORIES, "refchecks/ticgit.git");
+		if (refChecks.exists()) {
+			FileUtils.delete(refChecks, FileUtils.RECURSIVE);
+		}
+		CloneCommand clone = Git.cloneRepository();
+		clone.setURI(MessageFormat.format("{0}/ticgit.git", url));
+		clone.setDirectory(refChecks);
+		clone.setBare(true);
+		clone.setCloneAllBranches(true);
+		clone.setCredentialsProvider(cp);
+		GitBlitSuite.close(clone.call());
+
+		// elevate repository to clone permission
+		RepositoryModel model = GitBlit.self().getRepositoryModel("refchecks/ticgit.git");
+		switch (permission) {
+			case VIEW:
+				model.accessRestriction = AccessRestrictionType.CLONE;
+				break;
+			case CLONE:
+				model.accessRestriction = AccessRestrictionType.CLONE;
+				break;
+			default:
+				model.accessRestriction = AccessRestrictionType.PUSH;
+		}
+		model.authorizationControl = AuthorizationControl.NAMED;
+		
+		// grant user specified
+		user.setRepositoryPermission(model.name, permission);
+
+		GitBlit.self().updateUserModel(user.username, user, true);
+		GitBlit.self().updateRepositoryModel(model.name, model, false);
+
+		// clone temp bare repo to working copy
+		File local = new File(GitBlitSuite.REPOSITORIES, "refchecks/ticgit-wc");
+		if (local.exists()) {
+			FileUtils.delete(local, FileUtils.RECURSIVE);
+		}
+		clone = Git.cloneRepository();
+		clone.setURI(MessageFormat.format("{0}/{1}", url, model.name));
+		clone.setDirectory(local);
+		clone.setBare(false);
+		clone.setCloneAllBranches(true);
+		clone.setCredentialsProvider(cp);
+		
+		try {
+			GitBlitSuite.close(clone.call());
+		} catch (GitAPIException e) {
+			if (permission.atLeast(AccessPermission.CLONE)) {
+				throw e;
+			} else {
+				// close serving repository
+				GitBlitSuite.close(refChecks);
+				
+				// user does not have clone permission
+				assertTrue(e.getMessage(), e.getMessage().contains("not permitted"));	
+				return;
+			}
+		}
+		
+		Git git = Git.open(local);
+		
+		// commit a file and push it
+		File file = new File(local, "PUSHCHK");
+		OutputStreamWriter os = new OutputStreamWriter(new FileOutputStream(file, true), Constants.CHARSET);
+		BufferedWriter w = new BufferedWriter(os);
+		w.write("// " + new Date().toString() + "\n");
+		w.close();
+		git.add().addFilepattern(file.getName()).call();
+		git.commit().setMessage("push test").call();
+		Iterable<PushResult> results = null;
+		try {
+			results = git.push().setCredentialsProvider(cp).setRemote("origin").call();
+		} catch (GitAPIException e) {
+			if (permission.atLeast(AccessPermission.PUSH)) {
+				throw e;
+			} else {
+				// close serving repository
+				GitBlitSuite.close(refChecks);
+				
+				// user does not have push permission
+				assertTrue(e.getMessage(), e.getMessage().contains("not permitted"));
+				GitBlitSuite.close(git);
+				return;
+			}
+		}
+		
+		for (PushResult result : results) {
+			RemoteRefUpdate ref = result.getRemoteUpdate("refs/heads/master");
+			Status status = ref.getStatus();
+			if (permission.atLeast(AccessPermission.PUSH)) {
+				assertTrue("User failed to push commit?! " + status.name(), Status.OK.equals(status));
+			} else {
+				// close serving repository
+				GitBlitSuite.close(refChecks);
+
+				assertTrue("User was able to push commit! " + status.name(), Status.REJECTED_OTHER_REASON.equals(status));
+				GitBlitSuite.close(git);
+				// skip delete test
+				return;
+			}
+		}
+		
+		// create a local branch and push the new branch back to the origin				
+		git.branchCreate().setName("protectme").call();
+		RefSpec refSpec = new RefSpec("refs/heads/protectme:refs/heads/protectme");
+		results = git.push().setCredentialsProvider(cp).setRefSpecs(refSpec).setRemote("origin").call();
+		for (PushResult result : results) {
+			RemoteRefUpdate ref = result.getRemoteUpdate("refs/heads/protectme");
+			Status status = ref.getStatus();
+			if (Status.OK.equals(expectedCreate)) {
+				assertTrue("User failed to push creation?! " + status.name(), status.equals(expectedCreate));
+			} else {
+				// close serving repository
+				GitBlitSuite.close(refChecks);
+
+				assertTrue("User was able to push ref creation! " + status.name(), status.equals(expectedCreate));
+				GitBlitSuite.close(git);
+				// skip delete test
+				return;
+			}
+		}
+		
+		// delete the branch locally
+		git.branchDelete().setBranchNames("protectme").call();
+		
+		// push a delete ref command
+		refSpec = new RefSpec(":refs/heads/protectme");
+		results = git.push().setCredentialsProvider(cp).setRefSpecs(refSpec).setRemote("origin").call();
+		for (PushResult result : results) {
+			RemoteRefUpdate ref = result.getRemoteUpdate("refs/heads/protectme");
+			Status status = ref.getStatus();
+			if (Status.OK.equals(expectedDelete)) {
+				assertTrue("User failed to push ref deletion?! " + status.name(), status.equals(Status.OK));
+			} else {
+				// close serving repository
+				GitBlitSuite.close(refChecks);
+
+				assertTrue("User was able to push ref deletion?! " + status.name(), status.equals(expectedDelete));
+				GitBlitSuite.close(git);
+				// skip rewind test
+				return;
+			}
+		}
+		
+		// rewind master by two commits
+		git.reset().setRef("HEAD~2").setMode(ResetType.HARD).call();
+		
+		// commit a change on this detached HEAD
+		file = new File(local, "REWINDCHK");
+		os = new OutputStreamWriter(new FileOutputStream(file, true), Constants.CHARSET);
+		w = new BufferedWriter(os);
+		w.write("// " + new Date().toString() + "\n");
+		w.close();
+		git.add().addFilepattern(file.getName()).call();
+		RevCommit commit = git.commit().setMessage("rewind master and new commit").call();
+		
+		// Reset master to our new commit now we our local branch tip is no longer
+		// upstream of the remote branch tip.  It is an alternate tip of the branch.
+		JGitUtils.setBranchRef(git.getRepository(), "refs/heads/master", commit.getName());
+		
+		// Try pushing our new tip to the origin.
+		// This requires the server to "rewind" it's master branch and update it
+		// to point to our alternate tip.  This leaves the original master tip
+		// unreferenced.
+		results = git.push().setCredentialsProvider(cp).setRemote("origin").setForce(true).call();
+		for (PushResult result : results) {
+			RemoteRefUpdate ref = result.getRemoteUpdate("refs/heads/master");
+			Status status = ref.getStatus();
+			if (Status.OK.equals(expectedRewind)) {
+				assertTrue("User failed to rewind master?! " + status.name(), status.equals(expectedRewind));
+			} else {
+				assertTrue("User was able to rewind master?! " + status.name(), status.equals(expectedRewind));
+			}
+		}
+		GitBlitSuite.close(git);
+		
+		// close serving repository
+		GitBlitSuite.close(refChecks);
+
+		delete(user);
+	}
+	
+	@Test
+	public void testCreateOnPush() throws Exception {
+		testCreateOnPush(false, false);
+		testCreateOnPush(true, false);
+		testCreateOnPush(false, true);
+	}
+	
+	private void testCreateOnPush(boolean canCreate, boolean canAdmin) throws Exception {
+
+		UserModel user = new UserModel("sampleuser");
+		user.password = user.username;
+		
+		delete(user);
+		
+		user.canCreate = canCreate;
+		user.canAdmin = canAdmin;
+		
+		GitBlit.self().updateUserModel(user.username, user, true);
+
+		CredentialsProvider cp = new UsernamePasswordCredentialsProvider(user.username, user.password);
+		
+		// fork from original to a temporary bare repo
+		File tmpFolder = File.createTempFile("gitblit", "").getParentFile();
+		File createCheck = new File(tmpFolder, "ticgit.git");
+		if (createCheck.exists()) {
+			FileUtils.delete(createCheck, FileUtils.RECURSIVE);
+		}
+		
+		File personalRepo = new File(GitBlitSuite.REPOSITORIES, MessageFormat.format("~{0}/ticgit.git", user.username));
+		GitBlitSuite.close(personalRepo);
+		if (personalRepo.exists()) {
+			FileUtils.delete(personalRepo, FileUtils.RECURSIVE);
+		}
+
+		File projectRepo = new File(GitBlitSuite.REPOSITORIES, "project/ticgit.git");
+		GitBlitSuite.close(projectRepo);
+		if (projectRepo.exists()) {
+			FileUtils.delete(projectRepo, FileUtils.RECURSIVE);
+		}
+
+		CloneCommand clone = Git.cloneRepository();
+		clone.setURI(MessageFormat.format("{0}/ticgit.git", url));
+		clone.setDirectory(createCheck);
+		clone.setBare(true);
+		clone.setCloneAllBranches(true);
+		clone.setCredentialsProvider(cp);
+		Git git = clone.call();
+		
+		GitBlitSuite.close(personalRepo);
+		
+		// add a personal repository remote and a project remote
+		git.getRepository().getConfig().setString("remote", "user", "url", MessageFormat.format("{0}/~{1}/ticgit.git", url, user.username));
+		git.getRepository().getConfig().setString("remote", "project", "url", MessageFormat.format("{0}/project/ticgit.git", url));
+		git.getRepository().getConfig().save();
+
+		// push to non-existent user repository
+		try {
+			Iterable<PushResult> results = git.push().setRemote("user").setPushAll().setCredentialsProvider(cp).call();
+
+			for (PushResult result : results) {
+				RemoteRefUpdate ref = result.getRemoteUpdate("refs/heads/master");
+				Status status = ref.getStatus();
+				assertTrue("User failed to create repository?! " + status.name(), Status.OK.equals(status));
+			}
+
+			assertTrue("User canAdmin:" + user.canAdmin + " canCreate:" + user.canCreate, user.canAdmin || user.canCreate);
+			
+			// confirm default personal repository permissions
+			RepositoryModel model = GitBlit.self().getRepositoryModel(MessageFormat.format("~{0}/ticgit.git", user.username));
+			assertEquals("Unexpected owner", user.username, ArrayUtils.toString(model.owners));
+			assertEquals("Unexpected authorization control", AuthorizationControl.NAMED, model.authorizationControl);
+			assertEquals("Unexpected access restriction", AccessRestrictionType.VIEW, model.accessRestriction);
+			
+		} catch (GitAPIException e) {
+			assertTrue(e.getMessage(), e.getMessage().contains("git-receive-pack not found"));
+			assertFalse("User canAdmin:" + user.canAdmin + " canCreate:" + user.canCreate, user.canAdmin || user.canCreate);
+		}
+		
+		// push to non-existent project repository
+		try {
+			Iterable<PushResult> results = git.push().setRemote("project").setPushAll().setCredentialsProvider(cp).call();
+			GitBlitSuite.close(git);
+
+			for (PushResult result : results) {
+				RemoteRefUpdate ref = result.getRemoteUpdate("refs/heads/master");
+				Status status = ref.getStatus();
+				assertTrue("User failed to create repository?! " + status.name(), Status.OK.equals(status));
+			}
+			
+			assertTrue("User canAdmin:" + user.canAdmin, user.canAdmin);
+			
+			// confirm default project repository permissions
+			RepositoryModel model = GitBlit.self().getRepositoryModel("project/ticgit.git");
+			assertEquals("Unexpected owner", user.username, ArrayUtils.toString(model.owners));
+			assertEquals("Unexpected authorization control", AuthorizationControl.fromName(GitBlit.getString(Keys.git.defaultAuthorizationControl, "NAMED")), model.authorizationControl);
+			assertEquals("Unexpected access restriction", AccessRestrictionType.fromName(GitBlit.getString(Keys.git.defaultAccessRestriction, "NONE")), model.accessRestriction);
+
+		} catch (GitAPIException e) {
+			assertTrue(e.getMessage(), e.getMessage().contains("git-receive-pack not found"));
+			assertFalse("User canAdmin:" + user.canAdmin, user.canAdmin);
+		}
+
+		GitBlitSuite.close(git);
+		delete(user);
+	}
+	
+	@Test
+	public void testPushLog() throws IOException {
+		String name = "refchecks/ticgit.git";
+		File refChecks = new File(GitBlitSuite.REPOSITORIES, name);
+		Repository repository = new FileRepositoryBuilder().setGitDir(refChecks).build();
+		List<RefLogEntry> pushes = RefLogUtils.getRefLog(name, repository);
+		GitBlitSuite.close(repository);
+		assertTrue("Repository has an empty push log!", pushes.size() > 0);
+	}
+}
diff --git a/tests/com/gitblit/tests/GroovyScriptTest.java b/src/test/java/com/gitblit/tests/GroovyScriptTest.java
similarity index 100%
rename from tests/com/gitblit/tests/GroovyScriptTest.java
rename to src/test/java/com/gitblit/tests/GroovyScriptTest.java
diff --git a/src/test/java/com/gitblit/tests/Issue0259Test.java b/src/test/java/com/gitblit/tests/Issue0259Test.java
new file mode 100644
index 0000000..7267971
--- /dev/null
+++ b/src/test/java/com/gitblit/tests/Issue0259Test.java
@@ -0,0 +1,213 @@
+/*
+ * Copyright 2013 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit.tests;
+
+import java.io.File;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import com.gitblit.ConfigUserService;
+import com.gitblit.Constants.AccessPermission;
+import com.gitblit.Constants.AccessRestrictionType;
+import com.gitblit.models.RepositoryModel;
+import com.gitblit.models.TeamModel;
+import com.gitblit.models.UserModel;
+
+/**
+ * https://code.google.com/p/gitblit/issues/detail?id=259
+ * 
+ * Reported Problem:
+ * We have an user with RWD access rights, but he can’t push.
+ * 
+ * @see src/test/resources/issue0259.conf
+ * 
+ * At the next day he try again and he can push to the project.
+ * 
+ * @author James Moger
+ *
+ */
+public class Issue0259Test extends Assert {
+
+	RepositoryModel repo(String name, AccessRestrictionType restriction) {
+		RepositoryModel repo = new RepositoryModel();
+		repo.name = name;
+		repo.accessRestriction = restriction;
+		return repo;
+	}
+	
+	/**
+	 * Test the provided users.conf file for expected access permissions.
+	 *  
+	 * @throws Exception
+	 */
+	@Test
+	public void testFile() throws Exception {
+		File realmFile = new File("src/test/resources/issue0259.conf");
+		ConfigUserService service = new ConfigUserService(realmFile);
+		
+		RepositoryModel test = repo("test.git", AccessRestrictionType.VIEW);
+		RepositoryModel projects_test = repo("projects/test.git", AccessRestrictionType.VIEW);
+		
+		UserModel a = service.getUserModel("a");
+		UserModel b = service.getUserModel("b");
+		UserModel c = service.getUserModel("c");
+		
+		// assert RWD or RW+ for projects/test.git
+		assertEquals(AccessPermission.DELETE, a.getRepositoryPermission(projects_test).permission);
+		assertEquals(AccessPermission.DELETE, b.getRepositoryPermission(projects_test).permission);
+		assertEquals(AccessPermission.REWIND, c.getRepositoryPermission(projects_test).permission);
+		
+		assertTrue(a.canPush(projects_test));
+		assertTrue(b.canPush(projects_test));
+		assertTrue(c.canPush(projects_test));
+
+		assertTrue(a.canDeleteRef(projects_test));
+		assertTrue(b.canDeleteRef(projects_test));
+		assertTrue(c.canDeleteRef(projects_test));
+
+		assertFalse(a.canRewindRef(projects_test));
+		assertFalse(b.canRewindRef(projects_test));
+		assertTrue(c.canRewindRef(projects_test));
+		
+		// assert R for test.git
+		assertEquals(AccessPermission.CLONE, a.getRepositoryPermission(test).permission);
+		assertEquals(AccessPermission.CLONE, b.getRepositoryPermission(test).permission);
+		assertEquals(AccessPermission.REWIND, c.getRepositoryPermission(test).permission);
+
+		assertTrue(a.canClone(test));
+		assertTrue(b.canClone(test));
+
+		assertFalse(a.canPush(test));
+		assertFalse(b.canPush(test));
+		assertTrue(c.canPush(test));
+	}
+	
+	@Test
+	public void testTeamsOrder() throws Exception {
+		testTeams(false);
+	}
+
+	@Test
+	public void testTeamsReverseOrder() throws Exception {
+		testTeams(true);
+	}
+
+	/**
+	 * Tests multiple teams each with a regex permisson that will match.  The
+	 * highest matching permission should be used.  Order should be irrelevant.
+	 *  
+	 * @param reverseOrder
+	 * @throws Exception
+	 */
+	private void testTeams(boolean reverseOrder) throws Exception {
+		RepositoryModel test = repo("test.git", AccessRestrictionType.VIEW);
+		RepositoryModel projects_test = repo("projects/test.git", AccessRestrictionType.VIEW);
+		
+		TeamModel t1 = new TeamModel("t1");
+		t1.setRepositoryPermission(".*", AccessPermission.CLONE);
+		
+		TeamModel t2 = new TeamModel("t2");
+		t2.setRepositoryPermission("projects/.*", AccessPermission.DELETE);		
+		
+		UserModel a = new UserModel("a");
+		if (reverseOrder) {
+			a.teams.add(t2);
+			a.teams.add(t1);
+		} else {
+			a.teams.add(t1);
+			a.teams.add(t2);
+		}
+		
+		// simulate a repository rename
+		a.setRepositoryPermission("projects/renamed.git", null);
+		t1.setRepositoryPermission("projects/renamed.git", null);
+		t2.setRepositoryPermission("projects/renamed.git", null);
+
+		assertEquals(AccessPermission.CLONE, a.getRepositoryPermission(test).permission);
+		assertEquals(AccessPermission.DELETE, a.getRepositoryPermission(projects_test).permission);
+		
+		assertTrue(a.canClone(test));
+		assertTrue(a.canClone(projects_test));
+		
+		assertFalse(a.canDeleteRef(test));
+		assertTrue(a.canDeleteRef(projects_test));
+	}
+	
+	@Test
+	public void testTeam() throws Exception {
+		testTeam(false);		
+	}
+	
+	@Test
+	public void testTeamReverseOrder() throws Exception {
+		testTeam(true);
+	}
+	
+	/**
+	 * Test a single team that has multiple repository permissions that all match.
+	 * Here defined order IS important.  The first permission match wins so it is
+	 * important to define permissions from most-specific match to least-specific
+	 * match.
+	 * 
+	 * If the defined permissions are:
+	 *   R:.*
+	 *   RWD:projects/.*
+	 * then the expected result is R for all repositories because it is first.
+	 * 
+	 * But if the defined permissions are:
+	 *   RWD:projects/.*
+	 *   R:.*
+	 * then the expected result is RWD for projects/test.git and R for test.git
+	 *  
+	 * @param reverseOrder
+	 * @throws Exception
+	 */
+	private void testTeam(boolean reverseOrder) throws Exception {
+		RepositoryModel test = repo("test.git", AccessRestrictionType.VIEW);
+		RepositoryModel projects_test = repo("projects/test.git", AccessRestrictionType.VIEW);
+		
+		TeamModel t1 = new TeamModel("t1");
+		if (reverseOrder) {
+			t1.setRepositoryPermission("projects/.*", AccessPermission.DELETE);
+			t1.setRepositoryPermission(".*", AccessPermission.CLONE);
+		} else {
+			t1.setRepositoryPermission(".*", AccessPermission.CLONE);
+			t1.setRepositoryPermission("projects/.*", AccessPermission.DELETE);
+		}
+		UserModel a = new UserModel("a");
+		a.teams.add(t1);
+
+		// simulate a repository rename
+		a.setRepositoryPermission("projects/renamed.git", null);
+		t1.setRepositoryPermission("projects/renamed.git", null);
+		
+		assertEquals(AccessPermission.CLONE, a.getRepositoryPermission(test).permission);
+		assertTrue(a.canClone(test));
+		assertFalse(a.canDeleteRef(test));
+		assertTrue(a.canClone(projects_test));
+
+		if (reverseOrder) {
+			// RWD permission is found first
+			assertEquals(AccessPermission.DELETE, a.getRepositoryPermission(projects_test).permission);
+			assertTrue(a.canDeleteRef(projects_test));
+		} else {
+			// R permission is found first
+			assertEquals(AccessPermission.CLONE, a.getRepositoryPermission(projects_test).permission);
+			assertFalse(a.canDeleteRef(projects_test));
+		}		
+	}
+}
diff --git a/src/test/java/com/gitblit/tests/Issue0271Test.java b/src/test/java/com/gitblit/tests/Issue0271Test.java
new file mode 100644
index 0000000..437c5b9
--- /dev/null
+++ b/src/test/java/com/gitblit/tests/Issue0271Test.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2013 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit.tests;
+
+import java.io.File;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import com.gitblit.ConfigUserService;
+import com.gitblit.Constants.AccessPermission;
+import com.gitblit.Constants.AccessRestrictionType;
+import com.gitblit.models.RepositoryModel;
+import com.gitblit.models.UserModel;
+
+/**
+ * https://code.google.com/p/gitblit/issues/detail?id=271
+ * 
+ * Reported Problem:
+ * Inherited team permissions are incorrect.
+ * 
+ * @see src/test/resources/issue0270.conf
+ * 
+ * @author James Moger
+ *
+ */
+public class Issue0271Test extends Assert {
+
+	RepositoryModel repo(String name, AccessRestrictionType restriction) {
+		RepositoryModel repo = new RepositoryModel();
+		repo.name = name;
+		repo.accessRestriction = restriction;
+		return repo;
+	}
+	
+	/**
+	 * Test the provided users.conf file for expected access permissions.
+	 *  
+	 * @throws Exception
+	 */
+	@Test
+	public void testFile() throws Exception {
+		File realmFile = new File("src/test/resources/issue0271.conf");
+		ConfigUserService service = new ConfigUserService(realmFile);
+		
+		RepositoryModel test = repo("test.git", AccessRestrictionType.VIEW);
+		RepositoryModel teama_test = repo("teama/test.git", AccessRestrictionType.VIEW);
+		
+		UserModel a = service.getUserModel("a");
+		UserModel b = service.getUserModel("b");
+		UserModel c = service.getUserModel("c");
+		
+		// assert V for test.git
+		assertEquals(AccessPermission.VIEW, a.getRepositoryPermission(test).permission);
+		assertEquals(AccessPermission.VIEW, b.getRepositoryPermission(test).permission);
+		assertEquals(AccessPermission.VIEW, c.getRepositoryPermission(test).permission);
+		
+		// assert expected permissions for teama/test.git
+		assertEquals(AccessPermission.VIEW, a.getRepositoryPermission(teama_test).permission);
+		assertEquals(AccessPermission.PUSH, b.getRepositoryPermission(teama_test).permission);
+		assertEquals(AccessPermission.CREATE, c.getRepositoryPermission(teama_test).permission);
+	}
+}
\ No newline at end of file
diff --git a/tests/com/gitblit/tests/IssuesTest.java b/src/test/java/com/gitblit/tests/IssuesTest.java
similarity index 100%
rename from tests/com/gitblit/tests/IssuesTest.java
rename to src/test/java/com/gitblit/tests/IssuesTest.java
diff --git a/src/test/java/com/gitblit/tests/JGitUtilsTest.java b/src/test/java/com/gitblit/tests/JGitUtilsTest.java
new file mode 100644
index 0000000..375dbd5
--- /dev/null
+++ b/src/test/java/com/gitblit/tests/JGitUtilsTest.java
@@ -0,0 +1,472 @@
+/*
+ * Copyright 2011 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit.tests;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.jgit.diff.DiffEntry.ChangeType;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.FileMode;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.RepositoryCache;
+import org.eclipse.jgit.lib.RepositoryCache.FileKey;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevTree;
+import org.eclipse.jgit.util.FS;
+import org.eclipse.jgit.util.FileUtils;
+import org.junit.Test;
+
+import com.gitblit.Constants.SearchType;
+import com.gitblit.GitBlit;
+import com.gitblit.Keys;
+import com.gitblit.models.GitNote;
+import com.gitblit.models.PathModel;
+import com.gitblit.models.PathModel.PathChangeModel;
+import com.gitblit.models.RefModel;
+import com.gitblit.utils.CompressionUtils;
+import com.gitblit.utils.JGitUtils;
+import com.gitblit.utils.StringUtils;
+
+public class JGitUtilsTest {
+
+	@Test
+	public void testDisplayName() throws Exception {
+		assertEquals("Napoleon Bonaparte",
+				JGitUtils.getDisplayName(new PersonIdent("Napoleon Bonaparte", "")));
+		assertEquals("<someone@somewhere.com>",
+				JGitUtils.getDisplayName(new PersonIdent("", "someone@somewhere.com")));
+		assertEquals("Napoleon Bonaparte <someone@somewhere.com>",
+				JGitUtils.getDisplayName(new PersonIdent("Napoleon Bonaparte",
+						"someone@somewhere.com")));
+	}
+
+	@Test
+	public void testFindRepositories() {
+		List<String> list = JGitUtils.getRepositoryList(null, false, true, -1, null);
+		assertEquals(0, list.size());
+		list.addAll(JGitUtils.getRepositoryList(new File("DoesNotExist"), true, true, -1, null));
+		assertEquals(0, list.size());
+		list.addAll(JGitUtils.getRepositoryList(GitBlitSuite.REPOSITORIES, false, true, -1, null));
+		assertTrue("No repositories found in " + GitBlitSuite.REPOSITORIES, list.size() > 0);
+	}
+
+	@Test
+	public void testFindExclusions() {
+		List<String> list = JGitUtils.getRepositoryList(GitBlitSuite.REPOSITORIES, false, true, -1, null);
+		assertTrue("Missing jgit repository?!", list.contains("test/jgit.git"));
+
+		list = JGitUtils.getRepositoryList(GitBlitSuite.REPOSITORIES, false, true, -1, Arrays.asList("test/jgit\\.git"));
+		assertFalse("Repository exclusion failed!", list.contains("test/jgit.git"));
+
+		list = JGitUtils.getRepositoryList(GitBlitSuite.REPOSITORIES, false, true, -1, Arrays.asList("test/*"));
+		assertFalse("Repository exclusion failed!", list.contains("test/jgit.git"));
+
+		list = JGitUtils.getRepositoryList(GitBlitSuite.REPOSITORIES, false, true, -1, Arrays.asList(".*jgit.*"));
+		assertFalse("Repository exclusion failed!", list.contains("test/jgit.git"));
+		assertFalse("Repository exclusion failed!", list.contains("working/jgit"));
+		assertFalse("Repository exclusion failed!", list.contains("working/jgit2"));
+
+	}
+
+	@Test
+	public void testOpenRepository() throws Exception {
+		Repository repository = GitBlitSuite.getHelloworldRepository();
+		repository.close();
+		assertNotNull("Could not find repository!", repository);
+	}
+
+	@Test
+	public void testFirstCommit() throws Exception {
+		assertEquals(new Date(0), JGitUtils.getFirstChange(null, null));
+
+		Repository repository = GitBlitSuite.getHelloworldRepository();
+		RevCommit commit = JGitUtils.getFirstCommit(repository, null);
+		Date firstChange = JGitUtils.getFirstChange(repository, null);
+		repository.close();
+		assertNotNull("Could not get first commit!", commit);
+		assertEquals("Incorrect first commit!", "f554664a346629dc2b839f7292d06bad2db4aece",
+				commit.getName());
+		assertTrue(firstChange.equals(new Date(commit.getCommitTime() * 1000L)));
+	}
+
+	@Test
+	public void testLastCommit() throws Exception {
+		assertEquals(new Date(0), JGitUtils.getLastChange(null).when);
+
+		Repository repository = GitBlitSuite.getHelloworldRepository();
+		assertTrue(JGitUtils.getCommit(repository, null) != null);
+		Date date = JGitUtils.getLastChange(repository).when;
+		repository.close();
+		assertNotNull("Could not get last repository change date!", date);
+	}
+
+	@Test
+	public void testCreateRepository() throws Exception {
+		String[] repositories = { "NewTestRepository.git", "NewTestRepository" };
+		for (String repositoryName : repositories) {
+			Repository repository = JGitUtils.createRepository(GitBlitSuite.REPOSITORIES,
+					repositoryName);
+			File folder = FileKey.resolve(new File(GitBlitSuite.REPOSITORIES, repositoryName),
+					FS.DETECTED);
+			assertNotNull(repository);
+			assertFalse(JGitUtils.hasCommits(repository));
+			assertNull(JGitUtils.getFirstCommit(repository, null));
+			assertEquals(folder.lastModified(), JGitUtils.getFirstChange(repository, null)
+					.getTime());
+			assertEquals(folder.lastModified(), JGitUtils.getLastChange(repository).when.getTime());
+			assertNull(JGitUtils.getCommit(repository, null));
+			repository.close();
+			RepositoryCache.close(repository);
+			FileUtils.delete(repository.getDirectory(), FileUtils.RECURSIVE);
+		}
+	}
+
+	@Test
+	public void testRefs() throws Exception {
+		Repository repository = GitBlitSuite.getJGitRepository();
+		Map<ObjectId, List<RefModel>> map = JGitUtils.getAllRefs(repository);
+		repository.close();
+		assertTrue(map.size() > 0);
+		for (Map.Entry<ObjectId, List<RefModel>> entry : map.entrySet()) {
+			List<RefModel> list = entry.getValue();
+			for (RefModel ref : list) {
+				if (ref.displayName.equals("refs/tags/spearce-gpg-pub")) {
+					assertEquals("refs/tags/spearce-gpg-pub", ref.toString());
+					assertEquals("8bbde7aacf771a9afb6992434f1ae413e010c6d8", ref.getObjectId()
+							.getName());
+					assertEquals("spearce@spearce.org", ref.getAuthorIdent().getEmailAddress());
+					assertTrue(ref.getShortMessage().startsWith("GPG key"));
+					assertTrue(ref.getFullMessage().startsWith("GPG key"));
+					assertEquals(Constants.OBJ_BLOB, ref.getReferencedObjectType());
+				} else if (ref.displayName.equals("refs/tags/v0.12.1")) {
+					assertTrue(ref.isAnnotatedTag());
+				}
+			}
+		}
+	}
+
+	@Test
+	public void testBranches() throws Exception {
+		Repository repository = GitBlitSuite.getJGitRepository();
+		assertTrue(JGitUtils.getLocalBranches(repository, true, 0).size() == 0);
+		for (RefModel model : JGitUtils.getLocalBranches(repository, true, -1)) {
+			assertTrue(model.getName().startsWith(Constants.R_HEADS));
+			assertTrue(model.equals(model));
+			assertFalse(model.equals(""));
+			assertTrue(model.hashCode() == model.getReferencedObjectId().hashCode()
+					+ model.getName().hashCode());
+			assertTrue(model.getShortMessage().equals(model.getShortMessage()));
+		}
+		for (RefModel model : JGitUtils.getRemoteBranches(repository, true, -1)) {
+			assertTrue(model.getName().startsWith(Constants.R_REMOTES));
+			assertTrue(model.equals(model));
+			assertFalse(model.equals(""));
+			assertTrue(model.hashCode() == model.getReferencedObjectId().hashCode()
+					+ model.getName().hashCode());
+			assertTrue(model.getShortMessage().equals(model.getShortMessage()));
+		}
+		assertTrue(JGitUtils.getRemoteBranches(repository, true, 8).size() == 8);
+		repository.close();
+	}
+
+	@Test
+	public void testTags() throws Exception {
+		Repository repository = GitBlitSuite.getJGitRepository();
+		assertTrue(JGitUtils.getTags(repository, true, 5).size() == 5);
+		for (RefModel model : JGitUtils.getTags(repository, true, -1)) {
+			if (model.getObjectId().getName().equals("d28091fb2977077471138fe97da1440e0e8ae0da")) {
+				assertTrue("Not an annotated tag!", model.isAnnotatedTag());
+			}
+			assertTrue(model.getName().startsWith(Constants.R_TAGS));
+			assertTrue(model.equals(model));
+			assertFalse(model.equals(""));
+			assertTrue(model.hashCode() == model.getReferencedObjectId().hashCode()
+					+ model.getName().hashCode());
+		}
+		repository.close();
+
+		repository = GitBlitSuite.getGitectiveRepository();
+		for (RefModel model : JGitUtils.getTags(repository, true, -1)) {
+			if (model.getObjectId().getName().equals("035254295a9bba11f72b1f9d6791a6b957abee7b")) {
+				assertFalse(model.isAnnotatedTag());
+				assertTrue(model.getAuthorIdent().getEmailAddress().equals("kevinsawicki@gmail.com"));
+				assertEquals("Add scm and issue tracker elements to pom.xml\n", model.getFullMessage());
+			}
+		}
+		repository.close();
+	}
+
+	@Test
+	public void testCommitNotes() throws Exception {
+		Repository repository = GitBlitSuite.getJGitRepository();
+		RevCommit commit = JGitUtils.getCommit(repository,
+				"690c268c793bfc218982130fbfc25870f292295e");
+		List<GitNote> list = JGitUtils.getNotesOnCommit(repository, commit);
+		repository.close();
+		assertTrue(list.size() > 0);
+		assertEquals("183474d554e6f68478a02d9d7888b67a9338cdff", list.get(0).notesRef
+				.getReferencedObjectId().getName());
+	}
+	
+	@Test
+	public void testRelinkHEAD() throws Exception {
+		Repository repository = GitBlitSuite.getJGitRepository();
+		// confirm HEAD is master
+		String currentRef = JGitUtils.getHEADRef(repository);
+		assertEquals("refs/heads/master", currentRef);
+		List<String> availableHeads = JGitUtils.getAvailableHeadTargets(repository);
+		assertTrue(availableHeads.size() > 0);
+		
+		// set HEAD to stable-1.2
+		JGitUtils.setHEADtoRef(repository, "refs/heads/stable-1.2");
+		currentRef = JGitUtils.getHEADRef(repository);
+		assertEquals("refs/heads/stable-1.2", currentRef);
+
+		// restore HEAD to master
+		JGitUtils.setHEADtoRef(repository, "refs/heads/master");
+		currentRef = JGitUtils.getHEADRef(repository);
+		assertEquals("refs/heads/master", currentRef);
+		
+		repository.close();
+	}
+
+	@Test
+	public void testRelinkBranch() throws Exception {
+		Repository repository = GitBlitSuite.getJGitRepository();
+		
+		// create/set the branch
+		JGitUtils.setBranchRef(repository, "refs/heads/reftest", "3b358ce514ec655d3ff67de1430994d8428cdb04");
+		assertEquals(1, JGitUtils.getAllRefs(repository).get(ObjectId.fromString("3b358ce514ec655d3ff67de1430994d8428cdb04")).size());
+		assertEquals(null, JGitUtils.getAllRefs(repository).get(ObjectId.fromString("755dfdb40948f5c1ec79e06bde3b0a78c352f27f")));
+		
+		// reset the branch
+		JGitUtils.setBranchRef(repository, "refs/heads/reftest", "755dfdb40948f5c1ec79e06bde3b0a78c352f27f");
+		assertEquals(null, JGitUtils.getAllRefs(repository).get(ObjectId.fromString("3b358ce514ec655d3ff67de1430994d8428cdb04")));
+		assertEquals(1, JGitUtils.getAllRefs(repository).get(ObjectId.fromString("755dfdb40948f5c1ec79e06bde3b0a78c352f27f")).size());
+
+		// delete the branch
+		assertTrue(JGitUtils.deleteBranchRef(repository, "refs/heads/reftest"));
+		repository.close();
+	}
+
+	@Test
+	public void testCreateOrphanedBranch() throws Exception {
+		Repository repository = JGitUtils.createRepository(GitBlitSuite.REPOSITORIES, "orphantest");
+		assertTrue(JGitUtils.createOrphanBranch(repository,
+				"x" + Long.toHexString(System.currentTimeMillis()).toUpperCase(), null));
+		 FileUtils.delete(repository.getDirectory(), FileUtils.RECURSIVE);
+	}
+
+	@Test
+	public void testStringContent() throws Exception {
+		Repository repository = GitBlitSuite.getHelloworldRepository();
+		String contentA = JGitUtils.getStringContent(repository, (RevTree) null, "java.java");
+		RevCommit commit = JGitUtils.getCommit(repository, Constants.HEAD);
+		String contentB = JGitUtils.getStringContent(repository, commit.getTree(), "java.java");
+		String contentC = JGitUtils.getStringContent(repository, commit.getTree(), "missing.txt");
+
+		// manually construct a blob, calculate the hash, lookup the hash in git
+		StringBuilder sb = new StringBuilder();
+		sb.append("blob ").append(contentA.length()).append('\0');
+		sb.append(contentA);
+		String sha1 = StringUtils.getSHA1(sb.toString());
+		String contentD = JGitUtils.getStringContent(repository, sha1);
+		repository.close();
+		assertTrue("ContentA is null!", contentA != null && contentA.length() > 0);
+		assertTrue("ContentB is null!", contentB != null && contentB.length() > 0);
+		assertTrue(contentA.equals(contentB));
+		assertNull(contentC);
+		assertTrue(contentA.equals(contentD));
+	}
+
+	@Test
+	public void testFilesInCommit() throws Exception {
+		Repository repository = GitBlitSuite.getHelloworldRepository();
+		RevCommit commit = JGitUtils.getCommit(repository,
+				"1d0c2933a4ae69c362f76797d42d6bd182d05176");
+		List<PathChangeModel> paths = JGitUtils.getFilesInCommit(repository, commit);
+
+		commit = JGitUtils.getCommit(repository, "af0e9b2891fda85afc119f04a69acf7348922830");
+		List<PathChangeModel> deletions = JGitUtils.getFilesInCommit(repository, commit);
+
+		commit = JGitUtils.getFirstCommit(repository, null);
+		List<PathChangeModel> additions = JGitUtils.getFilesInCommit(repository, commit);
+
+		List<PathChangeModel> latestChanges = JGitUtils.getFilesInCommit(repository, null);
+
+		repository.close();
+		assertTrue("No changed paths found!", paths.size() == 1);
+		for (PathChangeModel path : paths) {
+			assertTrue("PathChangeModel hashcode incorrect!",
+					path.hashCode() == (path.commitId.hashCode() + path.path.hashCode()));
+			assertTrue("PathChangeModel equals itself failed!", path.equals(path));
+			assertFalse("PathChangeModel equals string failed!", path.equals(""));
+		}
+		assertEquals(ChangeType.DELETE, deletions.get(0).changeType);
+		assertEquals(ChangeType.ADD, additions.get(0).changeType);
+		assertTrue(latestChanges.size() > 0);
+	}
+
+	@Test
+	public void testFilesInPath() throws Exception {
+		assertEquals(0, JGitUtils.getFilesInPath(null, null, null).size());
+		Repository repository = GitBlitSuite.getHelloworldRepository();
+		List<PathModel> files = JGitUtils.getFilesInPath(repository, null, null);
+		repository.close();
+		assertTrue(files.size() > 10);
+	}
+
+	@Test
+	public void testDocuments() throws Exception {
+		Repository repository = GitBlitSuite.getTicgitRepository();
+		List<String> extensions = GitBlit.getStrings(Keys.web.markdownExtensions);
+		List<PathModel> markdownDocs = JGitUtils.getDocuments(repository, extensions);
+		List<PathModel> markdownDocs2 = JGitUtils.getDocuments(repository,
+				Arrays.asList(new String[] { ".mkd", ".md" }));
+		List<PathModel> allFiles = JGitUtils.getDocuments(repository, null);
+		repository.close();
+		assertTrue(markdownDocs.size() > 0);
+		assertTrue(markdownDocs2.size() > 0);
+		assertTrue(allFiles.size() > markdownDocs.size());
+	}
+
+	@Test
+	public void testFileModes() throws Exception {
+		assertEquals("drwxr-xr-x", JGitUtils.getPermissionsFromMode(FileMode.TREE.getBits()));
+		assertEquals("-rw-r--r--",
+				JGitUtils.getPermissionsFromMode(FileMode.REGULAR_FILE.getBits()));
+		assertEquals("-rwxr-xr-x",
+				JGitUtils.getPermissionsFromMode(FileMode.EXECUTABLE_FILE.getBits()));
+		assertEquals("symlink", JGitUtils.getPermissionsFromMode(FileMode.SYMLINK.getBits()));
+		assertEquals("submodule", JGitUtils.getPermissionsFromMode(FileMode.GITLINK.getBits()));
+		assertEquals("missing", JGitUtils.getPermissionsFromMode(FileMode.MISSING.getBits()));
+	}
+
+	@Test
+	public void testRevlog() throws Exception {
+		assertTrue(JGitUtils.getRevLog(null, 0).size() == 0);
+		List<RevCommit> commits = JGitUtils.getRevLog(null, 10);
+		assertEquals(0, commits.size());
+
+		Repository repository = GitBlitSuite.getHelloworldRepository();
+		// get most recent 10 commits
+		commits = JGitUtils.getRevLog(repository, 10);
+		assertEquals(10, commits.size());
+
+		// test paging and offset by getting the 10th most recent commit
+		RevCommit lastCommit = JGitUtils.getRevLog(repository, null, 9, 1).get(0);
+		assertEquals(lastCommit, commits.get(9));
+
+		// grab the two most recent commits to java.java
+		commits = JGitUtils.getRevLog(repository, null, "java.java", 0, 2);
+		assertEquals(2, commits.size());
+
+		// grab the commits since 2008-07-15
+		commits = JGitUtils.getRevLog(repository, null,
+				new SimpleDateFormat("yyyy-MM-dd").parse("2008-07-15"));
+		assertEquals(12, commits.size());
+		repository.close();
+	}
+
+	@Test
+	public void testRevLogRange() throws Exception {
+		Repository repository = GitBlitSuite.getHelloworldRepository();
+		List<RevCommit> commits = JGitUtils.getRevLog(repository,
+				"fbd14fa6d1a01d4aefa1fca725792683800fc67e",
+				"85a0e4087b8439c0aa6b1f4f9e08c26052ab7e87");
+		repository.close();
+		assertEquals(14, commits.size());
+	}
+
+	@Test
+	public void testSearchTypes() throws Exception {
+		assertEquals(SearchType.COMMIT, SearchType.forName("commit"));
+		assertEquals(SearchType.COMMITTER, SearchType.forName("committer"));
+		assertEquals(SearchType.AUTHOR, SearchType.forName("author"));
+		assertEquals(SearchType.COMMIT, SearchType.forName("unknown"));
+
+		assertEquals("commit", SearchType.COMMIT.toString());
+		assertEquals("committer", SearchType.COMMITTER.toString());
+		assertEquals("author", SearchType.AUTHOR.toString());
+	}
+
+	@Test
+	public void testSearchRevlogs() throws Exception {
+		assertEquals(0, JGitUtils.searchRevlogs(null, null, "java", SearchType.COMMIT, 0, 0).size());
+		List<RevCommit> results = JGitUtils.searchRevlogs(null, null, "java", SearchType.COMMIT, 0,
+				3);
+		assertEquals(0, results.size());
+
+		// test commit message search
+		Repository repository = GitBlitSuite.getHelloworldRepository();
+		results = JGitUtils.searchRevlogs(repository, null, "java", SearchType.COMMIT, 0, 3);
+		assertEquals(3, results.size());
+
+		// test author search
+		results = JGitUtils.searchRevlogs(repository, null, "timothy", SearchType.AUTHOR, 0, -1);
+		assertEquals(1, results.size());
+
+		// test committer search
+		results = JGitUtils.searchRevlogs(repository, null, "mike", SearchType.COMMITTER, 0, 10);
+		assertEquals(10, results.size());
+
+		// test paging and offset
+		RevCommit commit = JGitUtils.searchRevlogs(repository, null, "mike", SearchType.COMMITTER,
+				9, 1).get(0);
+		assertEquals(results.get(9), commit);
+
+		repository.close();
+	}
+
+	@Test
+	public void testZip() throws Exception {
+		assertFalse(CompressionUtils.zip(null, null, null, null));
+		Repository repository = GitBlitSuite.getHelloworldRepository();
+		File zipFileA = new File(GitBlitSuite.REPOSITORIES, "helloworld.zip");
+		FileOutputStream fosA = new FileOutputStream(zipFileA);
+		boolean successA = CompressionUtils.zip(repository, null, Constants.HEAD, fosA);
+		fosA.close();
+
+		File zipFileB = new File(GitBlitSuite.REPOSITORIES, "helloworld-java.zip");
+		FileOutputStream fosB = new FileOutputStream(zipFileB);
+		boolean successB = CompressionUtils.zip(repository, "java.java", Constants.HEAD, fosB);
+		fosB.close();
+
+		repository.close();
+		assertTrue("Failed to generate zip file!", successA);
+		assertTrue(zipFileA.length() > 0);
+		zipFileA.delete();
+
+		assertTrue("Failed to generate zip file!", successB);
+		assertTrue(zipFileB.length() > 0);
+		zipFileB.delete();
+	}
+
+}
\ No newline at end of file
diff --git a/src/test/java/com/gitblit/tests/JsonUtilsTest.java b/src/test/java/com/gitblit/tests/JsonUtilsTest.java
new file mode 100644
index 0000000..40e8f97
--- /dev/null
+++ b/src/test/java/com/gitblit/tests/JsonUtilsTest.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2011 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit.tests;
+
+import static org.junit.Assert.assertEquals;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.junit.Test;
+
+import com.gitblit.utils.JsonUtils;
+import com.google.gson.reflect.TypeToken;
+
+public class JsonUtilsTest {
+
+	@Test
+	public void testSerialization() {
+		Map<String, String> map = new HashMap<String, String>();
+		map.put("a", "alligator");
+		map.put("b", "bear");
+		map.put("c", "caterpillar");
+		map.put("d", "dingo");
+		map.put("e", "eagle");
+		String json = JsonUtils.toJsonString(map);
+		assertEquals(
+				"{\"d\":\"dingo\",\"e\":\"eagle\",\"b\":\"bear\",\"c\":\"caterpillar\",\"a\":\"alligator\"}",
+				json);
+		Map<String, String> map2 = JsonUtils.fromJsonString(json,
+				new TypeToken<Map<String, String>>() {
+				}.getType());
+		assertEquals(map, map2);
+
+		SomeJsonObject someJson = new SomeJsonObject();
+		json = JsonUtils.toJsonString(someJson);
+		SomeJsonObject someJson2 = JsonUtils.fromJsonString(json, SomeJsonObject.class);
+		assertEquals(someJson.name, someJson2.name);
+		SimpleDateFormat df = new SimpleDateFormat("yyyyMMdd HHmmss");
+		assertEquals(df.format(someJson.date), df.format(someJson2.date));
+	}
+
+	private class SomeJsonObject {
+		Date date = new Date();
+		String name = "myJson";
+	}
+}
\ No newline at end of file
diff --git a/tests/com/gitblit/tests/LdapUserServiceTest.java b/src/test/java/com/gitblit/tests/LdapUserServiceTest.java
similarity index 100%
rename from tests/com/gitblit/tests/LdapUserServiceTest.java
rename to src/test/java/com/gitblit/tests/LdapUserServiceTest.java
diff --git a/src/test/java/com/gitblit/tests/LuceneExecutorTest.java b/src/test/java/com/gitblit/tests/LuceneExecutorTest.java
new file mode 100644
index 0000000..0b67f5d
--- /dev/null
+++ b/src/test/java/com/gitblit/tests/LuceneExecutorTest.java
@@ -0,0 +1,172 @@
+/*
+ * 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.tests;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.jgit.lib.Repository;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.gitblit.LuceneExecutor;
+import com.gitblit.models.RefModel;
+import com.gitblit.models.RepositoryModel;
+import com.gitblit.models.SearchResult;
+import com.gitblit.tests.mock.MemorySettings;
+import com.gitblit.utils.FileUtils;
+import com.gitblit.utils.JGitUtils;
+
+/**
+ * Tests Lucene indexing and querying.
+ * 
+ * @author James Moger
+ * 
+ */
+public class LuceneExecutorTest {
+
+	LuceneExecutor lucene;
+	
+	private LuceneExecutor newLuceneExecutor() {
+		Map<String, Object> map = new HashMap<String, Object>();
+		MemorySettings settings = new MemorySettings(map);		
+		return new LuceneExecutor(settings, GitBlitSuite.REPOSITORIES);
+	}
+	
+	private RepositoryModel newRepositoryModel(Repository repository) {		
+		RepositoryModel model = new RepositoryModel();
+		model.name = FileUtils.getRelativePath(GitBlitSuite.REPOSITORIES, repository.getDirectory());
+		model.hasCommits = JGitUtils.hasCommits(repository);
+		
+		// index all local branches
+		model.indexedBranches = new ArrayList<String>();
+		for (RefModel ref : JGitUtils.getLocalBranches(repository, true, -1)) {
+			model.indexedBranches.add(ref.getName());
+		}
+		return model;
+	}
+	
+	@Before
+	public void setup() {
+		lucene = newLuceneExecutor();
+	}
+	
+	@After
+	public void tearDown() {
+		lucene.close();
+	}
+	
+	@Test
+	public void testIndex() throws Exception {
+		// reindex helloworld
+		Repository repository = GitBlitSuite.getHelloworldRepository();
+		RepositoryModel model = newRepositoryModel(repository);
+		lucene.reindex(model, repository);
+		repository.close();
+		
+		SearchResult result = lucene.search("type:blob AND path:bit.bit", 1, 1, model.name).get(0);		
+		assertEquals("Mike Donaghy", result.author);
+		result = lucene.search("type:blob AND path:clipper.prg", 1, 1, model.name).get(0);		
+		assertEquals("tinogomes", result.author);		
+
+		// reindex JGit
+		repository = GitBlitSuite.getJGitRepository();
+		model = newRepositoryModel(repository);
+		lucene.reindex(model, repository);
+		repository.close();
+	}
+
+	@Test
+	public void testQuery() throws Exception {
+		// 2 occurrences on the master branch
+		Repository repository = GitBlitSuite.getHelloworldRepository();				
+		RepositoryModel model = newRepositoryModel(repository);
+		repository.close();
+		
+		List<SearchResult> results = lucene.search("ada", 1, 10, model.name);
+		assertEquals(2, results.size());
+		for (SearchResult res : results) {
+			assertEquals("refs/heads/master", res.branch);
+		}
+
+		// author test
+		results = lucene.search("author: tinogomes AND type:commit", 1, 10, model.name);
+		assertEquals(2, results.size());
+		
+		// blob test
+		results = lucene.search("type: blob AND \"import std.stdio\"", 1, 10, model.name);
+		assertEquals(1, results.size());
+		assertEquals("d.D", results.get(0).path);
+		
+		// commit test
+		repository = GitBlitSuite.getJGitRepository();
+		model = newRepositoryModel(repository);
+		repository.close();
+		
+		results = lucene.search("\"initial jgit contribution to eclipse.org\"", 1, 10, model.name);
+		assertEquals(1, results.size());
+		assertEquals("Git Development Community", results.get(0).author);
+		assertEquals("1a6964c8274c50f0253db75f010d78ef0e739343", results.get(0).commitId);
+		assertEquals("refs/heads/master", results.get(0).branch);
+		
+		// hash id tests
+		results = lucene.search("type:commit AND commit:1a6964c8274c50f0253db75f010d78ef0e739343", 1, 10, model.name);
+		assertEquals(1, results.size());
+
+		results = lucene.search("type:commit AND commit:1a6964c8274*", 1, 10, model.name);
+		assertEquals("Shawn O. Pearce", results.get(0).committer);
+		assertEquals(1, results.size());		
+		
+		// annotated tag test
+		results = lucene.search("I663208919f297836a9c16bf458e4a43ffaca4c12", 1, 10, model.name);
+		assertEquals(1, results.size());
+		assertEquals("[v1.3.0.201202151440-r]", results.get(0).tags.toString());		
+	}
+	
+	@Test
+	public void testMultiSearch() throws Exception {
+		List<String> list = new ArrayList<String>();
+		Repository repository = GitBlitSuite.getHelloworldRepository();
+		list.add(newRepositoryModel(repository).name);
+		repository.close();
+
+		repository = GitBlitSuite.getJGitRepository();
+		list.add(newRepositoryModel(repository).name);
+		repository.close();
+
+		List<SearchResult> results = lucene.search("test", 1, 10, list);
+		assertEquals(10, results.size());
+	}
+	
+	@Test
+	public void testDeleteBlobFromIndex() throws Exception {
+		// start with a fresh reindex of entire repository
+		Repository repository = GitBlitSuite.getHelloworldRepository();
+		RepositoryModel model = newRepositoryModel(repository);
+		lucene.reindex(model, repository);
+		
+		// now delete a blob
+		assertTrue(lucene.deleteBlob(model.name, "refs/heads/master", "java.java"));
+		assertFalse(lucene.deleteBlob(model.name, "refs/heads/master", "java.java"));
+	}
+}
\ No newline at end of file
diff --git a/src/test/java/com/gitblit/tests/MailTest.java b/src/test/java/com/gitblit/tests/MailTest.java
new file mode 100644
index 0000000..4feedb0
--- /dev/null
+++ b/src/test/java/com/gitblit/tests/MailTest.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2011 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit.tests;
+
+import static org.junit.Assert.assertTrue;
+
+import javax.mail.Message;
+
+import org.junit.Test;
+
+import com.gitblit.FileSettings;
+import com.gitblit.Keys;
+import com.gitblit.MailExecutor;
+
+public class MailTest {
+
+	@Test
+	public void testSendMail() throws Exception {
+		FileSettings settings = new FileSettings("mailtest.properties");
+		MailExecutor mail = new MailExecutor(settings);
+		Message message = mail.createMessage(settings.getStrings(Keys.mail.adminAddresses));
+		message.setSubject("Test");
+		message.setText("Lägger till andra stycket i ny fil. UTF-8 encoded");
+		mail.queue(message);
+		mail.run();
+
+		assertTrue("mail queue is not empty!", mail.hasEmptyQueue());
+	}
+}
diff --git a/tests/com/gitblit/tests/MarkdownUtilsTest.java b/src/test/java/com/gitblit/tests/MarkdownUtilsTest.java
similarity index 100%
rename from tests/com/gitblit/tests/MarkdownUtilsTest.java
rename to src/test/java/com/gitblit/tests/MarkdownUtilsTest.java
diff --git a/tests/com/gitblit/tests/MetricUtilsTest.java b/src/test/java/com/gitblit/tests/MetricUtilsTest.java
similarity index 100%
rename from tests/com/gitblit/tests/MetricUtilsTest.java
rename to src/test/java/com/gitblit/tests/MetricUtilsTest.java
diff --git a/tests/com/gitblit/tests/ObjectCacheTest.java b/src/test/java/com/gitblit/tests/ObjectCacheTest.java
similarity index 100%
rename from tests/com/gitblit/tests/ObjectCacheTest.java
rename to src/test/java/com/gitblit/tests/ObjectCacheTest.java
diff --git a/src/test/java/com/gitblit/tests/PermissionsTest.java b/src/test/java/com/gitblit/tests/PermissionsTest.java
new file mode 100644
index 0000000..12225e4
--- /dev/null
+++ b/src/test/java/com/gitblit/tests/PermissionsTest.java
@@ -0,0 +1,2881 @@
+/*
+ * 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.tests;
+
+import java.util.Date;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import com.gitblit.Constants.AccessPermission;
+import com.gitblit.Constants.AccessRestrictionType;
+import com.gitblit.Constants.AuthorizationControl;
+import com.gitblit.models.RepositoryModel;
+import com.gitblit.models.TeamModel;
+import com.gitblit.models.UserModel;
+
+/**
+ * Comprehensive, brute-force test of all permutations of discrete permissions.
+ * 
+ * @author James Moger
+ *
+ */
+public class PermissionsTest extends Assert {
+
+	/**
+	 * Admin access rights/permissions
+	 */
+	@Test
+	public void testAdmin() throws Exception {
+		UserModel user = new UserModel("admin");
+		user.canAdmin = true;
+		
+		for (AccessRestrictionType ar : AccessRestrictionType.values()) {
+			RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
+			repository.authorizationControl = AuthorizationControl.NAMED;
+			repository.accessRestriction = ar;
+				
+			assertTrue("admin CAN NOT view!", user.canView(repository));
+			assertTrue("admin CAN NOT clone!", user.canClone(repository));
+			assertTrue("admin CAN NOT push!", user.canPush(repository));
+			
+			assertTrue("admin CAN NOT create ref!", user.canCreateRef(repository));
+			assertTrue("admin CAN NOT delete ref!", user.canDeleteRef(repository));
+			assertTrue("admin CAN NOT rewind ref!", user.canRewindRef(repository));
+			
+			assertEquals("admin has wrong permission!", AccessPermission.REWIND, user.getRepositoryPermission(repository).permission);
+
+			assertTrue("admin CAN NOT fork!", user.canFork(repository));
+			
+			assertTrue("admin CAN NOT delete!", user.canDelete(repository));
+			assertTrue("admin CAN NOT edit!", user.canEdit(repository));
+		}
+	}
+	
+	/**
+	 * Anonymous access rights/permissions 
+	 */
+	@Test
+	public void testAnonymous_NONE() throws Exception {
+		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
+		repository.authorizationControl = AuthorizationControl.NAMED;
+		repository.accessRestriction = AccessRestrictionType.NONE;
+		
+		UserModel user = UserModel.ANONYMOUS;
+				
+		// all permissions, except fork
+		assertTrue("anonymous CAN NOT view!", user.canView(repository));
+		assertTrue("anonymous CAN NOT clone!", user.canClone(repository));
+		assertTrue("anonymous CAN NOT push!", user.canPush(repository));
+		
+		assertTrue("anonymous CAN NOT create ref!", user.canCreateRef(repository));
+		assertTrue("anonymous CAN NOT delete ref!", user.canDeleteRef(repository));
+		assertTrue("anonymous CAN NOT rewind ref!", user.canRewindRef(repository));
+
+		assertEquals("anonymous has wrong permission!", AccessPermission.REWIND, user.getRepositoryPermission(repository).permission);
+
+		repository.allowForks = false;
+		assertFalse("anonymous CAN fork!", user.canFork(repository));
+		repository.allowForks = true;
+		assertFalse("anonymous CAN fork!", user.canFork(repository));
+		
+		assertFalse("anonymous CAN delete!", user.canDelete(repository));
+		assertFalse("anonymous CAN edit!", user.canEdit(repository));
+	}
+	
+	@Test
+	public void testAnonymous_PUSH() throws Exception {
+		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
+		repository.authorizationControl = AuthorizationControl.NAMED;
+		repository.accessRestriction = AccessRestrictionType.PUSH;
+
+		UserModel user = UserModel.ANONYMOUS;
+
+		assertTrue("anonymous CAN NOT view!", user.canView(repository));
+		assertTrue("anonymous CAN NOT clone!", user.canClone(repository));
+		assertFalse("anonymous CAN push!", user.canPush(repository));
+		
+		assertFalse("anonymous CAN create ref!", user.canCreateRef(repository));
+		assertFalse("anonymous CAN delete ref!", user.canDeleteRef(repository));
+		assertFalse("anonymous CAN rewind ref!", user.canRewindRef(repository));
+		
+		assertEquals("anonymous has wrong permission!", AccessPermission.CLONE, user.getRepositoryPermission(repository).permission);
+
+		repository.allowForks = false;
+		assertFalse("anonymous CAN fork!", user.canFork(repository));
+		repository.allowForks = true;
+		assertFalse("anonymous CAN fork!", user.canFork(repository));
+	}
+	
+	@Test
+	public void testAnonymous_CLONE() throws Exception {
+		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
+		repository.authorizationControl = AuthorizationControl.NAMED;
+		repository.accessRestriction = AccessRestrictionType.CLONE;
+
+		UserModel user = UserModel.ANONYMOUS;
+
+		assertTrue("anonymous CAN NOT view!", user.canView(repository));
+		assertFalse("anonymous CAN clone!", user.canClone(repository));
+		assertFalse("anonymous CAN push!", user.canPush(repository));
+		
+		assertFalse("anonymous CAN create ref!", user.canCreateRef(repository));
+		assertFalse("anonymous CAN delete ref!", user.canDeleteRef(repository));
+		assertFalse("anonymous CAN rewind ref!", user.canRewindRef(repository));
+
+		assertEquals("anonymous has wrong permission!", AccessPermission.VIEW, user.getRepositoryPermission(repository).permission);
+
+		repository.allowForks = false;
+		assertFalse("anonymous CAN fork!", user.canFork(repository));
+		repository.allowForks = true;
+		assertFalse("anonymous CAN fork!", user.canFork(repository));
+	}
+	
+	@Test
+	public void testAnonymous_VIEW() throws Exception {
+		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
+		repository.authorizationControl = AuthorizationControl.NAMED;
+		repository.accessRestriction = AccessRestrictionType.VIEW;
+		
+		UserModel user = UserModel.ANONYMOUS;
+
+		assertFalse("anonymous CAN view!", user.canView(repository));
+		assertFalse("anonymous CAN clone!", user.canClone(repository));
+		assertFalse("anonymous CAN push!", user.canPush(repository));
+		
+		assertFalse("anonymous CAN create ref!", user.canCreateRef(repository));
+		assertFalse("anonymous CAN delete ref!", user.canDeleteRef(repository));
+		assertFalse("anonymous CAN rewind ref!", user.canRewindRef(repository));
+
+		assertEquals("anonymous has wrong permission!", AccessPermission.NONE, user.getRepositoryPermission(repository).permission);
+
+		repository.allowForks = false;
+		assertFalse("anonymous CAN fork!", user.canFork(repository));
+		repository.allowForks = true;
+		assertFalse("anonymous CAN fork!", user.canFork(repository));
+	}
+	
+	/**
+	 * Authenticated access rights/permissions 
+	 */
+	@Test
+	public void testAuthenticated_NONE() throws Exception {
+		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
+		repository.authorizationControl = AuthorizationControl.AUTHENTICATED;
+		repository.accessRestriction = AccessRestrictionType.NONE;
+		
+		UserModel user = new UserModel("test");
+		
+		// all permissions, except fork
+		assertTrue("authenticated CAN NOT view!", user.canView(repository));
+		assertTrue("authenticated CAN NOT clone!", user.canClone(repository));
+		assertTrue("authenticated CAN NOT push!", user.canPush(repository));
+		
+		assertTrue("authenticated CAN NOT create ref!", user.canCreateRef(repository));
+		assertTrue("authenticated CAN NOT delete ref!", user.canDeleteRef(repository));
+		assertTrue("authenticated CAN NOT rewind ref!", user.canRewindRef(repository));
+
+		assertEquals("authenticated has wrong permission!", AccessPermission.REWIND, user.getRepositoryPermission(repository).permission);
+
+		user.canFork = false;
+		repository.allowForks = false;
+		assertFalse("authenticated CAN fork!", user.canFork(repository));
+		repository.allowForks = true;
+		assertFalse("authenticated CAN fork!", user.canFork(repository));
+		user.canFork = true;
+		assertTrue("authenticated CAN NOT fork!", user.canFork(repository));
+		
+		assertFalse("authenticated CAN delete!", user.canDelete(repository));
+		assertFalse("authenticated CAN edit!", user.canEdit(repository));
+	}
+
+	@Test
+	public void testAuthenticated_PUSH() throws Exception {
+		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
+		repository.authorizationControl = AuthorizationControl.AUTHENTICATED;
+		repository.accessRestriction = AccessRestrictionType.PUSH;
+		
+		UserModel user = new UserModel("test");
+
+		assertTrue("authenticated CAN NOT view!", user.canView(repository));
+		assertTrue("authenticated CAN NOT clone!", user.canClone(repository));
+		assertTrue("authenticated CAN NOT push!", user.canPush(repository));
+		
+		assertTrue("authenticated CAN NOT create ref!", user.canCreateRef(repository));
+		assertTrue("authenticated CAN NOT delete ref!", user.canDeleteRef(repository));
+		assertTrue("authenticated CAN NOT rewind ref!", user.canRewindRef(repository));
+
+		assertEquals("authenticated has wrong permission!", AccessPermission.REWIND, user.getRepositoryPermission(repository).permission);
+
+		user.canFork = false;
+		repository.allowForks = false;
+		assertFalse("authenticated CAN fork!", user.canFork(repository));
+		repository.allowForks = true;
+		assertFalse("authenticated CAN fork!", user.canFork(repository));
+		user.canFork = true;
+		assertTrue("authenticated CAN NOT fork!", user.canFork(repository));
+	}
+	
+	@Test
+	public void testAuthenticated_CLONE() throws Exception {
+		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
+		repository.authorizationControl = AuthorizationControl.AUTHENTICATED;
+		repository.accessRestriction = AccessRestrictionType.CLONE;
+			
+		UserModel user = new UserModel("test");
+
+		assertTrue("authenticated CAN NOT view!", user.canView(repository));
+		assertTrue("authenticated CAN NOT clone!", user.canClone(repository));
+		assertTrue("authenticated CAN NOT push!", user.canPush(repository));
+		
+		assertTrue("authenticated CAN NOT create ref!", user.canCreateRef(repository));
+		assertTrue("authenticated CAN NOT delete ref!", user.canDeleteRef(repository));
+		assertTrue("authenticated CAN NOT rewind ref!", user.canRewindRef(repository));
+
+		assertEquals("authenticated has wrong permission!", AccessPermission.REWIND, user.getRepositoryPermission(repository).permission);
+
+		user.canFork = false;
+		repository.allowForks = false;
+		assertFalse("authenticated CAN fork!", user.canFork(repository));
+		repository.allowForks = true;
+		assertFalse("authenticated CAN fork!", user.canFork(repository));
+		user.canFork = true;
+		assertTrue("authenticated CAN NOT fork!", user.canFork(repository));
+	}
+	
+	@Test
+	public void testAuthenticated_VIEW() throws Exception {
+		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
+		repository.authorizationControl = AuthorizationControl.AUTHENTICATED;
+		repository.accessRestriction = AccessRestrictionType.VIEW;
+			
+		UserModel user = new UserModel("test");
+
+		assertTrue("authenticated CAN NOT view!", user.canView(repository));
+		assertTrue("authenticated CAN NOT clone!", user.canClone(repository));
+		assertTrue("authenticated CAN NOT push!", user.canPush(repository));
+		
+		assertTrue("authenticated CAN NOT create ref!", user.canCreateRef(repository));
+		assertTrue("authenticated CAN NOT delete ref!", user.canDeleteRef(repository));
+		assertTrue("authenticated CAN NOT rewind ref!", user.canRewindRef(repository));
+
+		assertEquals("authenticated has wrong permission!", AccessPermission.REWIND, user.getRepositoryPermission(repository).permission);
+
+		user.canFork = false;
+		repository.allowForks = false;
+		assertFalse("authenticated CAN fork!", user.canFork(repository));
+		repository.allowForks = true;
+		assertFalse("authenticated CAN fork!", user.canFork(repository));
+		user.canFork = true;
+		assertTrue("authenticated CAN NOT fork!", user.canFork(repository));
+	}
+	
+	/**
+	 * NONE_NONE = NO access restriction, NO access permission
+	 */
+	@Test
+	public void testNamed_NONE_NONE() throws Exception {
+		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
+		repository.authorizationControl = AuthorizationControl.NAMED;
+		repository.accessRestriction = AccessRestrictionType.NONE;
+
+		UserModel user = new UserModel("test");
+		
+		assertTrue("named CAN NOT view!", user.canView(repository));
+		assertTrue("named CAN NOT clone!", user.canClone(repository));
+		assertTrue("named CAN NOT push!", user.canPush(repository));
+		
+		assertTrue("named CAN NOT create ref!", user.canCreateRef(repository));
+		assertTrue("named CAN NOT delete ref!", user.canDeleteRef(repository));
+		assertTrue("named CAN NOT rewind ref!", user.canRewindRef(repository));
+		
+		assertEquals("named has wrong permission!", AccessPermission.REWIND, user.getRepositoryPermission(repository).permission);
+
+		repository.allowForks = false;
+		user.canFork = false;
+		assertFalse("named CAN fork!", user.canFork(repository));
+		user.canFork = true;
+		assertFalse("named CAN fork!", user.canFork(repository));
+		repository.allowForks = true;
+		assertTrue("named CAN NOT fork!", user.canFork(repository));
+		
+		assertFalse("named CAN delete!", user.canDelete(repository));
+		assertFalse("named CAN edit!", user.canEdit(repository));
+	}
+	
+	/**
+	 * PUSH_NONE = PUSH access restriction, NO access permission
+	 */
+	@Test
+	public void testNamed_PUSH_NONE() throws Exception {
+		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
+		repository.authorizationControl = AuthorizationControl.NAMED;
+		repository.accessRestriction = AccessRestrictionType.PUSH;
+		
+		UserModel user = new UserModel("test");
+		
+		assertTrue("named CAN NOT view!", user.canView(repository));
+		assertTrue("named CAN NOT clone!", user.canClone(repository));
+		assertFalse("named CAN push!", user.canPush(repository));
+		
+		assertFalse("named CAN create ref!", user.canCreateRef(repository));
+		assertFalse("named CAN delete ref!", user.canDeleteRef(repository));
+		assertFalse("named CAN rewind ref!", user.canRewindRef(repository));
+
+		assertEquals("named has wrong permission!", AccessPermission.CLONE, user.getRepositoryPermission(repository).permission);
+
+		repository.allowForks = false;
+		user.canFork = false;
+		assertFalse("named CAN fork!", user.canFork(repository));
+		user.canFork = true;
+		assertFalse("named CAN fork!", user.canFork(repository));
+		repository.allowForks = true;
+		assertTrue("named CAN NOT fork!", user.canFork(repository));
+	}
+	
+	/**
+	 * CLONE_NONE = CLONE access restriction, NO access permission
+	 */
+	@Test
+	public void testNamed_CLONE_NONE() throws Exception {
+		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
+		repository.authorizationControl = AuthorizationControl.NAMED;
+		repository.accessRestriction = AccessRestrictionType.CLONE;
+		
+		UserModel user = new UserModel("test");
+		
+		assertTrue("named CAN NOT view!", user.canView(repository));
+		assertFalse("named CAN clone!", user.canClone(repository));
+		assertFalse("named CAN push!", user.canPush(repository));
+		
+		assertFalse("named CAN create ref!", user.canCreateRef(repository));
+		assertFalse("named CAN delete ref!", user.canDeleteRef(repository));
+		assertFalse("named CAN rewind ref!", user.canRewindRef(repository));
+
+		assertEquals("named has wrong permission!", AccessPermission.VIEW, user.getRepositoryPermission(repository).permission);
+
+		repository.allowForks = false;
+		user.canFork = false;
+		assertFalse("named CAN fork!", user.canFork(repository));
+		user.canFork = true;
+		assertFalse("named CAN fork!", user.canFork(repository));
+		repository.allowForks = true;
+		assertFalse("named CAN NOT fork!", user.canFork(repository));
+	}
+	
+	/**
+	 * VIEW_NONE = VIEW access restriction, NO access permission
+	 */
+	@Test
+	public void testNamed_VIEW_NONE() throws Exception {
+		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
+		repository.authorizationControl = AuthorizationControl.NAMED;
+		repository.accessRestriction = AccessRestrictionType.VIEW;
+		
+		UserModel user = new UserModel("test");
+		
+		assertFalse("named CAN view!", user.canView(repository));
+		assertFalse("named CAN clone!", user.canClone(repository));
+		assertFalse("named CAN push!", user.canPush(repository));
+		
+		assertFalse("named CAN create ref!", user.canCreateRef(repository));
+		assertFalse("named CAN delete ref!", user.canDeleteRef(repository));
+		assertFalse("named CAN rewind ref!", user.canRewindRef(repository));
+		
+		assertEquals("named has wrong permission!", AccessPermission.NONE, user.getRepositoryPermission(repository).permission);
+
+		repository.allowForks = false;
+		user.canFork = false;
+		assertFalse("named CAN fork!", user.canFork(repository));
+		user.canFork = true;
+		assertFalse("named CAN fork!", user.canFork(repository));
+		repository.allowForks = true;
+		assertFalse("named CAN NOT fork!", user.canFork(repository));
+	}
+
+	
+	/**
+	 * NONE_VIEW = NO access restriction, VIEW access permission.
+	 * (not useful scenario)
+	 */
+	@Test
+	public void testNamed_NONE_VIEW() throws Exception {
+		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
+		repository.authorizationControl = AuthorizationControl.NAMED;
+		repository.accessRestriction = AccessRestrictionType.NONE;
+
+		UserModel user = new UserModel("test");
+		user.setRepositoryPermission(repository.name, AccessPermission.VIEW);
+		
+		assertTrue("named CAN NOT view!", user.canView(repository));
+		assertTrue("named CAN NOT clone!", user.canClone(repository));
+		assertTrue("named CAN NOT push!", user.canPush(repository));
+		
+		assertTrue("named CAN NOT create ref!", user.canCreateRef(repository));
+		assertTrue("named CAN NOT delete ref!", user.canDeleteRef(repository));
+		assertTrue("named CAN NOT rewind ref!", user.canRewindRef(repository));
+
+		assertEquals("named has wrong permission!", AccessPermission.REWIND, user.getRepositoryPermission(repository).permission);
+
+		repository.allowForks = false;
+		user.canFork = false;
+		assertFalse("named CAN fork!", user.canFork(repository));
+		user.canFork = true;
+		assertFalse("named CAN fork!", user.canFork(repository));
+		repository.allowForks = true;
+		assertTrue("named CAN NOT fork!", user.canFork(repository));
+	}
+	
+	/**
+	 * PUSH_VIEW = PUSH access restriction, VIEW access permission
+	 */
+	@Test
+	public void testNamed_PUSH_VIEW() throws Exception {
+		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
+		repository.authorizationControl = AuthorizationControl.NAMED;
+		repository.accessRestriction = AccessRestrictionType.PUSH;
+		
+		UserModel user = new UserModel("test");
+		user.setRepositoryPermission(repository.name, AccessPermission.VIEW);
+		
+		assertTrue("named CAN NOT view!", user.canView(repository));
+		assertTrue("named CAN NOT clone!", user.canClone(repository));
+		assertFalse("named CAN push!", user.canPush(repository));
+		
+		assertFalse("named CAN create ref!", user.canCreateRef(repository));
+		assertFalse("named CAN delete ref!", user.canDeleteRef(repository));
+		assertFalse("named CAN rewind ref!", user.canRewindRef(repository));
+
+		assertEquals("named has wrong permission!", AccessPermission.CLONE, user.getRepositoryPermission(repository).permission);
+
+		repository.allowForks = false;
+		user.canFork = false;
+		assertFalse("named CAN fork!", user.canFork(repository));
+		user.canFork = true;
+		assertFalse("named CAN fork!", user.canFork(repository));
+		repository.allowForks = true;
+		assertTrue("named CAN NOT fork!", user.canFork(repository));
+	}
+	
+	/**
+	 * CLONE_VIEW = CLONE access restriction, VIEW access permission
+	 */
+	@Test
+	public void testNamed_CLONE_VIEW() throws Exception {
+		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
+		repository.authorizationControl = AuthorizationControl.NAMED;
+		repository.accessRestriction = AccessRestrictionType.CLONE;
+		
+		UserModel user = new UserModel("test");
+		user.setRepositoryPermission(repository.name, AccessPermission.VIEW);
+
+		assertTrue("named CAN NOT view!", user.canView(repository));
+		assertFalse("named CAN clone!", user.canClone(repository));
+		assertFalse("named CAN push!", user.canPush(repository));
+		
+		assertFalse("named CAN create ref!", user.canCreateRef(repository));
+		assertFalse("named CAN delete ref!", user.canDeleteRef(repository));
+		assertFalse("named CAN rewind ref!", user.canRewindRef(repository));
+
+		assertEquals("named has wrong permission!", AccessPermission.VIEW, user.getRepositoryPermission(repository).permission);
+
+		repository.allowForks = false;
+		user.canFork = false;
+		assertFalse("named CAN fork!", user.canFork(repository));
+		user.canFork = true;
+		assertFalse("named CAN fork!", user.canFork(repository));
+		repository.allowForks = true;
+		assertFalse("named CAN NOT fork!", user.canFork(repository));
+	}
+	
+	/**
+	 * VIEW_VIEW = VIEW access restriction, VIEW access permission
+	 */
+	@Test
+	public void testNamed_VIEW_VIEW() throws Exception {
+		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
+		repository.authorizationControl = AuthorizationControl.NAMED;
+		repository.accessRestriction = AccessRestrictionType.VIEW;
+		
+		UserModel user = new UserModel("test");
+		user.setRepositoryPermission(repository.name, AccessPermission.VIEW);
+
+		assertTrue("named CAN NOT view!", user.canView(repository));
+		assertFalse("named CAN clone!", user.canClone(repository));
+		assertFalse("named CAN push!", user.canPush(repository));
+		
+		assertFalse("named CAN create ref!", user.canCreateRef(repository));
+		assertFalse("named CAN delete ref!", user.canDeleteRef(repository));
+		assertFalse("named CAN rewind ref!", user.canRewindRef(repository));
+		
+		assertEquals("named has wrong permission!", AccessPermission.VIEW, user.getRepositoryPermission(repository).permission);
+
+		repository.allowForks = false;
+		user.canFork = false;
+		assertFalse("named CAN fork!", user.canFork(repository));
+		user.canFork = true;
+		assertFalse("named CAN fork!", user.canFork(repository));
+		repository.allowForks = true;
+		assertFalse("named CAN NOT fork!", user.canFork(repository));
+	}
+	
+	/**
+	 * NONE_CLONE = NO access restriction, CLONE access permission.
+	 * (not useful scenario)
+	 */
+	@Test
+	public void testNamed_NONE_CLONE() throws Exception {
+		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
+		repository.authorizationControl = AuthorizationControl.NAMED;
+		repository.accessRestriction = AccessRestrictionType.NONE;
+
+		UserModel user = new UserModel("test");
+		user.setRepositoryPermission(repository.name, AccessPermission.CLONE);
+		
+		assertTrue("named CAN NOT view!", user.canView(repository));
+		assertTrue("named CAN NOT clone!", user.canClone(repository));
+		assertTrue("named CAN NOT push!", user.canPush(repository));
+		
+		assertTrue("named CAN NOT create ref!", user.canCreateRef(repository));
+		assertTrue("named CAN NOT delete ref!", user.canDeleteRef(repository));
+		assertTrue("named CAN NOT rewind ref!", user.canRewindRef(repository));
+		
+		assertEquals("named has wrong permission!", AccessPermission.REWIND, user.getRepositoryPermission(repository).permission);
+
+		repository.allowForks = false;
+		user.canFork = false;
+		assertFalse("named CAN fork!", user.canFork(repository));
+		user.canFork = true;
+		assertFalse("named CAN fork!", user.canFork(repository));
+		repository.allowForks = true;
+		assertTrue("named CAN NOT fork!", user.canFork(repository));
+	}
+	
+	/**
+	 * PUSH_CLONE = PUSH access restriction, CLONE access permission
+	 */
+	@Test
+	public void testNamed_PUSH_CLONE() throws Exception {
+		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
+		repository.authorizationControl = AuthorizationControl.NAMED;
+		repository.accessRestriction = AccessRestrictionType.PUSH;
+		
+		UserModel user = new UserModel("test");
+		user.setRepositoryPermission(repository.name, AccessPermission.CLONE);
+		
+		assertTrue("named CAN NOT view!", user.canView(repository));
+		assertTrue("named CAN NOT clone!", user.canClone(repository));
+		assertFalse("named CAN push!", user.canPush(repository));
+		
+		assertFalse("named CAN create ref!", user.canCreateRef(repository));
+		assertFalse("named CAN delete ref!", user.canDeleteRef(repository));
+		assertFalse("named CAN rewind ref!", user.canRewindRef(repository));
+
+		assertEquals("named has wrong permission!", AccessPermission.CLONE, user.getRepositoryPermission(repository).permission);
+
+		repository.allowForks = false;
+		user.canFork = false;
+		assertFalse("named CAN fork!", user.canFork(repository));
+		user.canFork = true;
+		assertFalse("named CAN fork!", user.canFork(repository));
+		repository.allowForks = true;
+		assertTrue("named CAN NOT fork!", user.canFork(repository));
+	}
+	
+	/**
+	 * CLONE_CLONE = CLONE access restriction, CLONE access permission
+	 */
+	@Test
+	public void testNamed_CLONE_CLONE() throws Exception {
+		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
+		repository.authorizationControl = AuthorizationControl.NAMED;
+		repository.accessRestriction = AccessRestrictionType.CLONE;
+		
+		UserModel user = new UserModel("test");
+		user.setRepositoryPermission(repository.name, AccessPermission.CLONE);
+
+		assertTrue("named CAN NOT view!", user.canView(repository));
+		assertTrue("named CAN NOT clone!", user.canClone(repository));
+		assertFalse("named CAN push!", user.canPush(repository));
+		
+		assertFalse("named CAN create ref!", user.canCreateRef(repository));
+		assertFalse("named CAN delete ref!", user.canDeleteRef(repository));
+		assertFalse("named CAN rewind ref!", user.canRewindRef(repository));
+
+		assertEquals("named has wrong permission!", AccessPermission.CLONE, user.getRepositoryPermission(repository).permission);
+
+		repository.allowForks = false;
+		user.canFork = false;
+		assertFalse("named CAN fork!", user.canFork(repository));
+		user.canFork = true;
+		assertFalse("named CAN fork!", user.canFork(repository));
+		repository.allowForks = true;
+		assertTrue("named CAN NOT fork!", user.canFork(repository));
+	}
+	
+	/**
+	 * VIEW_CLONE = VIEW access restriction, CLONE access permission
+	 */
+	@Test
+	public void testNamed_VIEW_CLONE() throws Exception {
+		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
+		repository.authorizationControl = AuthorizationControl.NAMED;
+		repository.accessRestriction = AccessRestrictionType.VIEW;
+		
+		UserModel user = new UserModel("test");
+		user.setRepositoryPermission(repository.name, AccessPermission.CLONE);
+
+		assertTrue("named CAN NOT view!", user.canView(repository));
+		assertTrue("named CAN NOT clone!", user.canClone(repository));
+		assertFalse("named CAN push!", user.canPush(repository));
+		
+		assertFalse("named CAN create ref!", user.canCreateRef(repository));
+		assertFalse("named CAN delete ref!", user.canDeleteRef(repository));
+		assertFalse("named CAN rewind ref!", user.canRewindRef(repository));
+		
+		assertEquals("named has wrong permission!", AccessPermission.CLONE, user.getRepositoryPermission(repository).permission);
+
+		repository.allowForks = false;
+		user.canFork = false;
+		assertFalse("named CAN fork!", user.canFork(repository));
+		user.canFork = true;
+		assertFalse("named CAN fork!", user.canFork(repository));
+		repository.allowForks = true;
+		assertTrue("named CAN NOT fork!", user.canFork(repository));
+	}	
+
+	/**
+	 * NONE_PUSH = NO access restriction, PUSH access permission.
+	 * (not useful scenario)
+	 */
+	@Test
+	public void testNamed_NONE_PUSH() throws Exception {
+		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
+		repository.authorizationControl = AuthorizationControl.NAMED;
+		repository.accessRestriction = AccessRestrictionType.NONE;
+
+		UserModel user = new UserModel("test");
+		user.setRepositoryPermission(repository.name, AccessPermission.PUSH);
+		
+		assertTrue("named CAN NOT view!", user.canView(repository));
+		assertTrue("named CAN NOT clone!", user.canClone(repository));
+		assertTrue("named CAN NOT push!", user.canPush(repository));
+		
+		assertTrue("named CAN NOT create ref!", user.canCreateRef(repository));
+		assertTrue("named CAN NOT delete ref!", user.canDeleteRef(repository));
+		assertTrue("named CAN NOT rewind ref!", user.canRewindRef(repository));
+		
+		assertEquals("named has wrong permission!", AccessPermission.REWIND, user.getRepositoryPermission(repository).permission);
+
+		repository.allowForks = false;
+		user.canFork = false;
+		assertFalse("named CAN fork!", user.canFork(repository));
+		user.canFork = true;
+		assertFalse("named CAN fork!", user.canFork(repository));
+		repository.allowForks = true;
+		assertTrue("named CAN NOT fork!", user.canFork(repository));
+	}
+	
+	/**
+	 * PUSH_PUSH = PUSH access restriction, PUSH access permission
+	 */
+	@Test
+	public void testNamed_PUSH_PUSH() throws Exception {
+		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
+		repository.authorizationControl = AuthorizationControl.NAMED;
+		repository.accessRestriction = AccessRestrictionType.PUSH;
+		
+		UserModel user = new UserModel("test");
+		user.setRepositoryPermission(repository.name, AccessPermission.PUSH);
+		
+		assertTrue("named CAN NOT view!", user.canView(repository));
+		assertTrue("named CAN NOT clone!", user.canClone(repository));
+		assertTrue("named CAN NOT push!", user.canPush(repository));
+		
+		assertFalse("named CAN create ref!", user.canCreateRef(repository));
+		assertFalse("named CAN delete ref!", user.canDeleteRef(repository));
+		assertFalse("named CAN rewind ref!", user.canRewindRef(repository));
+
+		assertEquals("named has wrong permission!", AccessPermission.PUSH, user.getRepositoryPermission(repository).permission);
+
+		repository.allowForks = false;
+		user.canFork = false;
+		assertFalse("named CAN fork!", user.canFork(repository));
+		user.canFork = true;
+		assertFalse("named CAN fork!", user.canFork(repository));
+		repository.allowForks = true;
+		assertTrue("named CAN NOT fork!", user.canFork(repository));
+	}
+	
+	/**
+	 * CLONE_PUSH = CLONE access restriction, PUSH access permission
+	 */
+	@Test
+	public void testNamed_CLONE_PUSH() throws Exception {
+		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
+		repository.authorizationControl = AuthorizationControl.NAMED;
+		repository.accessRestriction = AccessRestrictionType.CLONE;
+		
+		UserModel user = new UserModel("test");
+		user.setRepositoryPermission(repository.name, AccessPermission.PUSH);
+
+		assertTrue("named CAN NOT view!", user.canView(repository));
+		assertTrue("named CAN NOT clone!", user.canClone(repository));
+		assertTrue("named CAN NOT push!", user.canPush(repository));
+		
+		assertFalse("named CAN create ref!", user.canCreateRef(repository));
+		assertFalse("named CAN delete red!", user.canDeleteRef(repository));
+		assertFalse("named CAN rewind ref!", user.canRewindRef(repository));
+
+		assertEquals("named has wrong permission!", AccessPermission.PUSH, user.getRepositoryPermission(repository).permission);
+
+		repository.allowForks = false;
+		user.canFork = false;
+		assertFalse("named CAN fork!", user.canFork(repository));
+		user.canFork = true;
+		assertFalse("named CAN fork!", user.canFork(repository));
+		repository.allowForks = true;
+		assertTrue("named CAN NOT fork!", user.canFork(repository));
+	}
+	
+	/**
+	 * VIEW_PUSH = VIEW access restriction, PUSH access permission
+	 */
+	@Test
+	public void testNamed_VIEW_PUSH() throws Exception {
+		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
+		repository.authorizationControl = AuthorizationControl.NAMED;
+		repository.accessRestriction = AccessRestrictionType.VIEW;
+		
+		UserModel user = new UserModel("test");
+		user.setRepositoryPermission(repository.name, AccessPermission.PUSH);
+
+		assertTrue("named CAN NOT view!", user.canView(repository));
+		assertTrue("named CAN NOT clone!", user.canClone(repository));
+		assertTrue("named CAN not push!", user.canPush(repository));
+		
+		assertFalse("named CAN create ref!", user.canCreateRef(repository));
+		assertFalse("named CAN delete ref!", user.canDeleteRef(repository));
+		assertFalse("named CAN rewind ref!", user.canRewindRef(repository));
+		
+		assertEquals("named has wrong permission!", AccessPermission.PUSH, user.getRepositoryPermission(repository).permission);
+
+		repository.allowForks = false;
+		user.canFork = false;
+		assertFalse("named CAN fork!", user.canFork(repository));
+		user.canFork = true;
+		assertFalse("named CAN fork!", user.canFork(repository));
+		repository.allowForks = true;
+		assertTrue("named CAN NOT fork!", user.canFork(repository));
+	}
+
+	/**
+	 * NONE_CREATE = NO access restriction, CREATE access permission.
+	 * (not useful scenario)
+	 */
+	@Test
+	public void testNamed_NONE_CREATE() throws Exception {
+		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
+		repository.authorizationControl = AuthorizationControl.NAMED;
+		repository.accessRestriction = AccessRestrictionType.NONE;
+
+		UserModel user = new UserModel("test");
+		user.setRepositoryPermission(repository.name, AccessPermission.CREATE);
+		
+		assertTrue("named CAN NOT view!", user.canView(repository));
+		assertTrue("named CAN NOT clone!", user.canClone(repository));
+		assertTrue("named CAN NOT push!", user.canPush(repository));
+		
+		assertTrue("named CAN NOT create ref!", user.canCreateRef(repository));
+		assertTrue("named CAN NOT delete ref!", user.canDeleteRef(repository));
+		assertTrue("named CAN NOT rewind ref!", user.canRewindRef(repository));
+		
+		assertEquals("named has wrong permission!", AccessPermission.REWIND, user.getRepositoryPermission(repository).permission);
+
+		repository.allowForks = false;
+		user.canFork = false;
+		assertFalse("named CAN fork!", user.canFork(repository));
+		user.canFork = true;
+		assertFalse("named CAN fork!", user.canFork(repository));
+		repository.allowForks = true;
+		assertTrue("named CAN NOT fork!", user.canFork(repository));
+	}
+	
+	/**
+	 * PUSH_CREATE = PUSH access restriction, CREATE access permission
+	 */
+	@Test
+	public void testNamed_PUSH_CREATE() throws Exception {
+		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
+		repository.authorizationControl = AuthorizationControl.NAMED;
+		repository.accessRestriction = AccessRestrictionType.PUSH;
+		
+		UserModel user = new UserModel("test");
+		user.setRepositoryPermission(repository.name, AccessPermission.CREATE);
+		
+		assertTrue("named CAN NOT view!", user.canView(repository));
+		assertTrue("named CAN NOT clone!", user.canClone(repository));
+		assertTrue("named CAN NOT push!", user.canPush(repository));
+		
+		assertTrue("named CAN NOT create ref!", user.canCreateRef(repository));
+		assertFalse("named CAN delete ref!", user.canDeleteRef(repository));
+		assertFalse("named CAN rewind ref!", user.canRewindRef(repository));
+
+		assertEquals("named has wrong permission!", AccessPermission.CREATE, user.getRepositoryPermission(repository).permission);
+
+		repository.allowForks = false;
+		user.canFork = false;
+		assertFalse("named CAN fork!", user.canFork(repository));
+		user.canFork = true;
+		assertFalse("named CAN fork!", user.canFork(repository));
+		repository.allowForks = true;
+		assertTrue("named CAN NOT fork!", user.canFork(repository));
+	}
+	
+	/**
+	 * CLONE_CREATE = CLONE access restriction, CREATE access permission
+	 */
+	@Test
+	public void testNamed_CLONE_CREATE() throws Exception {
+		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
+		repository.authorizationControl = AuthorizationControl.NAMED;
+		repository.accessRestriction = AccessRestrictionType.CLONE;
+		
+		UserModel user = new UserModel("test");
+		user.setRepositoryPermission(repository.name, AccessPermission.CREATE);
+
+		assertTrue("named CAN NOT view!", user.canView(repository));
+		assertTrue("named CAN NOT clone!", user.canClone(repository));
+		assertTrue("named CAN NOT push!", user.canPush(repository));
+		
+		assertTrue("named CAN NOT create ref!", user.canCreateRef(repository));
+		assertFalse("named CAN delete red!", user.canDeleteRef(repository));
+		assertFalse("named CAN rewind ref!", user.canRewindRef(repository));
+
+		assertEquals("named has wrong permission!", AccessPermission.CREATE, user.getRepositoryPermission(repository).permission);
+
+		repository.allowForks = false;
+		user.canFork = false;
+		assertFalse("named CAN fork!", user.canFork(repository));
+		user.canFork = true;
+		assertFalse("named CAN fork!", user.canFork(repository));
+		repository.allowForks = true;
+		assertTrue("named CAN NOT fork!", user.canFork(repository));
+	}
+	
+	/**
+	 * VIEW_CREATE = VIEW access restriction, CREATE access permission
+	 */
+	@Test
+	public void testNamed_VIEW_CREATE() throws Exception {
+		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
+		repository.authorizationControl = AuthorizationControl.NAMED;
+		repository.accessRestriction = AccessRestrictionType.VIEW;
+		
+		UserModel user = new UserModel("test");
+		user.setRepositoryPermission(repository.name, AccessPermission.CREATE);
+
+		assertTrue("named CAN NOT view!", user.canView(repository));
+		assertTrue("named CAN NOT clone!", user.canClone(repository));
+		assertTrue("named CAN not push!", user.canPush(repository));
+		
+		assertTrue("named CAN NOT create ref!", user.canCreateRef(repository));
+		assertFalse("named CAN delete ref!", user.canDeleteRef(repository));
+		assertFalse("named CAN rewind ref!", user.canRewindRef(repository));
+		
+		assertEquals("named has wrong permission!", AccessPermission.CREATE, user.getRepositoryPermission(repository).permission);
+
+		repository.allowForks = false;
+		user.canFork = false;
+		assertFalse("named CAN fork!", user.canFork(repository));
+		user.canFork = true;
+		assertFalse("named CAN fork!", user.canFork(repository));
+		repository.allowForks = true;
+		assertTrue("named CAN NOT fork!", user.canFork(repository));
+	}
+
+	/**
+	 * NONE_DELETE = NO access restriction, DELETE access permission.
+	 * (not useful scenario)
+	 */
+	@Test
+	public void testNamed_NONE_DELETE() throws Exception {
+		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
+		repository.authorizationControl = AuthorizationControl.NAMED;
+		repository.accessRestriction = AccessRestrictionType.NONE;
+
+		UserModel user = new UserModel("test");
+		user.setRepositoryPermission(repository.name, AccessPermission.DELETE);
+		
+		assertTrue("named CAN NOT view!", user.canView(repository));
+		assertTrue("named CAN NOT clone!", user.canClone(repository));
+		assertTrue("named CAN NOT push!", user.canPush(repository));
+		
+		assertTrue("named CAN NOT create ref!", user.canCreateRef(repository));
+		assertTrue("named CAN NOT delete ref!", user.canDeleteRef(repository));
+		assertTrue("named CAN NOT rewind ref!", user.canRewindRef(repository));
+		
+		assertEquals("named has wrong permission!", AccessPermission.REWIND, user.getRepositoryPermission(repository).permission);
+
+		repository.allowForks = false;
+		user.canFork = false;
+		assertFalse("named CAN fork!", user.canFork(repository));
+		user.canFork = true;
+		assertFalse("named CAN fork!", user.canFork(repository));
+		repository.allowForks = true;
+		assertTrue("named CAN NOT fork!", user.canFork(repository));
+	}
+	
+	/**
+	 * PUSH_DELETE = PUSH access restriction, DELETE access permission
+	 */
+	@Test
+	public void testNamed_PUSH_DELETE() throws Exception {
+		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
+		repository.authorizationControl = AuthorizationControl.NAMED;
+		repository.accessRestriction = AccessRestrictionType.PUSH;
+		
+		UserModel user = new UserModel("test");
+		user.setRepositoryPermission(repository.name, AccessPermission.DELETE);
+		
+		assertTrue("named CAN NOT view!", user.canView(repository));
+		assertTrue("named CAN NOT clone!", user.canClone(repository));
+		assertTrue("named CAN NOT push!", user.canPush(repository));
+		
+		assertTrue("named CAN NOT create ref!", user.canCreateRef(repository));
+		assertTrue("named CAN NOT delete ref!", user.canDeleteRef(repository));
+		assertFalse("named CAN rewind ref!", user.canRewindRef(repository));
+
+		assertEquals("named has wrong permission!", AccessPermission.DELETE, user.getRepositoryPermission(repository).permission);
+
+		repository.allowForks = false;
+		user.canFork = false;
+		assertFalse("named CAN fork!", user.canFork(repository));
+		user.canFork = true;
+		assertFalse("named CAN fork!", user.canFork(repository));
+		repository.allowForks = true;
+		assertTrue("named CAN NOT fork!", user.canFork(repository));
+	}
+	
+	/**
+	 * CLONE_DELETE = CLONE access restriction, DELETE access permission
+	 */
+	@Test
+	public void testNamed_CLONE_DELETE() throws Exception {
+		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
+		repository.authorizationControl = AuthorizationControl.NAMED;
+		repository.accessRestriction = AccessRestrictionType.CLONE;
+		
+		UserModel user = new UserModel("test");
+		user.setRepositoryPermission(repository.name, AccessPermission.DELETE);
+
+		assertTrue("named CAN NOT view!", user.canView(repository));
+		assertTrue("named CAN NOT clone!", user.canClone(repository));
+		assertTrue("named CAN NOT push!", user.canPush(repository));
+		
+		assertTrue("named CAN NOT create ref!", user.canCreateRef(repository));
+		assertTrue("named CAN NOT delete red!", user.canDeleteRef(repository));
+		assertFalse("named CAN rewind ref!", user.canRewindRef(repository));
+
+		assertEquals("named has wrong permission!", AccessPermission.DELETE, user.getRepositoryPermission(repository).permission);
+
+		repository.allowForks = false;
+		user.canFork = false;
+		assertFalse("named CAN fork!", user.canFork(repository));
+		user.canFork = true;
+		assertFalse("named CAN fork!", user.canFork(repository));
+		repository.allowForks = true;
+		assertTrue("named CAN NOT fork!", user.canFork(repository));
+	}
+	
+	/**
+	 * VIEW_DELETE = VIEW access restriction, DELETE access permission
+	 */
+	@Test
+	public void testNamed_VIEW_DELETE() throws Exception {
+		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
+		repository.authorizationControl = AuthorizationControl.NAMED;
+		repository.accessRestriction = AccessRestrictionType.VIEW;
+		
+		UserModel user = new UserModel("test");
+		user.setRepositoryPermission(repository.name, AccessPermission.DELETE);
+
+		assertTrue("named CAN NOT view!", user.canView(repository));
+		assertTrue("named CAN NOT clone!", user.canClone(repository));
+		assertTrue("named CAN not push!", user.canPush(repository));
+		
+		assertTrue("named CAN NOT create ref!", user.canCreateRef(repository));
+		assertTrue("named CAN NOT delete ref!", user.canDeleteRef(repository));
+		assertFalse("named CAN rewind ref!", user.canRewindRef(repository));
+		
+		assertEquals("named has wrong permission!", AccessPermission.DELETE, user.getRepositoryPermission(repository).permission);
+
+		repository.allowForks = false;
+		user.canFork = false;
+		assertFalse("named CAN fork!", user.canFork(repository));
+		user.canFork = true;
+		assertFalse("named CAN fork!", user.canFork(repository));
+		repository.allowForks = true;
+		assertTrue("named CAN NOT fork!", user.canFork(repository));
+	}
+	
+	/**
+	 * NONE_REWIND = NO access restriction, REWIND access permission.
+	 * (not useful scenario)
+	 */
+	@Test
+	public void testNamed_NONE_REWIND() throws Exception {
+		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
+		repository.authorizationControl = AuthorizationControl.NAMED;
+		repository.accessRestriction = AccessRestrictionType.NONE;
+
+		UserModel user = new UserModel("test");
+		user.setRepositoryPermission(repository.name, AccessPermission.REWIND);
+		
+		assertTrue("named CAN NOT view!", user.canView(repository));
+		assertTrue("named CAN NOT clone!", user.canClone(repository));
+		assertTrue("named CAN NOT push!", user.canPush(repository));
+		
+		assertTrue("named CAN NOT create ref!", user.canCreateRef(repository));
+		assertTrue("named CAN NOT delete ref!", user.canDeleteRef(repository));
+		assertTrue("named CAN NOT rewind ref!", user.canRewindRef(repository));
+		
+		assertEquals("named has wrong permission!", AccessPermission.REWIND, user.getRepositoryPermission(repository).permission);
+
+		repository.allowForks = false;
+		user.canFork = false;
+		assertFalse("named CAN fork!", user.canFork(repository));
+		user.canFork = true;
+		assertFalse("named CAN fork!", user.canFork(repository));
+		repository.allowForks = true;
+		assertTrue("named CAN NOT fork!", user.canFork(repository));
+	}
+	
+	/**
+	 * PUSH_REWIND = PUSH access restriction, REWIND access permission
+	 */
+	@Test
+	public void testNamed_PUSH_REWIND() throws Exception {
+		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
+		repository.authorizationControl = AuthorizationControl.NAMED;
+		repository.accessRestriction = AccessRestrictionType.PUSH;
+		
+		UserModel user = new UserModel("test");
+		user.setRepositoryPermission(repository.name, AccessPermission.REWIND);
+		
+		assertTrue("named CAN NOT view!", user.canView(repository));
+		assertTrue("named CAN NOT clone!", user.canClone(repository));
+		assertTrue("named CAN NOT push!", user.canPush(repository));
+		
+		assertTrue("named CAN NOT create ref!", user.canCreateRef(repository));
+		assertTrue("named CAN NOT delete ref!", user.canDeleteRef(repository));
+		assertTrue("named CAN NOT rewind ref!", user.canRewindRef(repository));
+
+		assertEquals("named has wrong permission!", AccessPermission.REWIND, user.getRepositoryPermission(repository).permission);
+
+		repository.allowForks = false;
+		user.canFork = false;
+		assertFalse("named CAN fork!", user.canFork(repository));
+		user.canFork = true;
+		assertFalse("named CAN fork!", user.canFork(repository));
+		repository.allowForks = true;
+		assertTrue("named CAN NOT fork!", user.canFork(repository));
+	}
+	
+	/**
+	 * CLONE_REWIND = CLONE access restriction, REWIND access permission
+	 */
+	@Test
+	public void testNamed_CLONE_REWIND() throws Exception {
+		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
+		repository.authorizationControl = AuthorizationControl.NAMED;
+		repository.accessRestriction = AccessRestrictionType.CLONE;
+		
+		UserModel user = new UserModel("test");
+		user.setRepositoryPermission(repository.name, AccessPermission.REWIND);
+
+		assertTrue("named CAN NOT view!", user.canView(repository));
+		assertTrue("named CAN NOT clone!", user.canClone(repository));
+		assertTrue("named CAN NOT push!", user.canPush(repository));
+		
+		assertTrue("named CAN NOT create ref!", user.canCreateRef(repository));
+		assertTrue("named CAN NOT delete ref!", user.canDeleteRef(repository));
+		assertTrue("named CAN NOT rewind ref!", user.canRewindRef(repository));
+
+		assertEquals("named has wrong permission!", AccessPermission.REWIND, user.getRepositoryPermission(repository).permission);
+
+		repository.allowForks = false;
+		user.canFork = false;
+		assertFalse("named CAN fork!", user.canFork(repository));
+		user.canFork = true;
+		assertFalse("named CAN fork!", user.canFork(repository));
+		repository.allowForks = true;
+		assertTrue("named CAN NOT fork!", user.canFork(repository));
+	}
+	
+	/**
+	 * VIEW_REWIND = VIEW access restriction, REWIND access permission
+	 */
+	@Test
+	public void testNamed_VIEW_REWIND() throws Exception {
+		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
+		repository.authorizationControl = AuthorizationControl.NAMED;
+		repository.accessRestriction = AccessRestrictionType.VIEW;
+		
+		UserModel user = new UserModel("test");
+		user.setRepositoryPermission(repository.name, AccessPermission.REWIND);
+
+		assertTrue("named CAN NOT view!", user.canView(repository));
+		assertTrue("named CAN NOT clone!", user.canClone(repository));
+		assertTrue("named CAN NOT push!", user.canPush(repository));
+		
+		assertTrue("named CAN NOT create ref!", user.canCreateRef(repository));
+		assertTrue("named CAN NOT delete ref!", user.canDeleteRef(repository));
+		assertTrue("named CAN NOT rewind ref!", user.canRewindRef(repository));
+		
+		assertEquals("named has wrong permission!", AccessPermission.REWIND, user.getRepositoryPermission(repository).permission);
+
+		repository.allowForks = false;
+		user.canFork = false;
+		assertFalse("named CAN fork!", user.canFork(repository));
+		user.canFork = true;
+		assertFalse("named CAN fork!", user.canFork(repository));
+		repository.allowForks = true;
+		assertTrue("named CAN NOT fork!", user.canFork(repository));
+	}
+	
+	/**
+	 * NONE_NONE = NO access restriction, NO access permission
+	 */
+	@Test
+	public void testTeam_NONE_NONE() throws Exception {
+		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
+		repository.authorizationControl = AuthorizationControl.NAMED;
+		repository.accessRestriction = AccessRestrictionType.NONE;
+
+		TeamModel team = new TeamModel("test");
+		
+		assertTrue("team CAN NOT view!", team.canView(repository));
+		assertTrue("team CAN NOT clone!", team.canClone(repository));
+		assertTrue("team CAN NOT push!", team.canPush(repository));
+		
+		assertTrue("team CAN NOT create ref!", team.canCreateRef(repository));
+		assertTrue("team CAN NOT delete ref!", team.canDeleteRef(repository));
+		assertTrue("team CAN NOT rewind ref!", team.canRewindRef(repository));
+		
+		assertEquals("team has wrong permission!", AccessPermission.REWIND, team.getRepositoryPermission(repository).permission);
+
+	}
+	
+	/**
+	 * PUSH_NONE = PUSH access restriction, NO access permission
+	 */
+	@Test
+	public void testTeam_PUSH_NONE() throws Exception {
+		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
+		repository.authorizationControl = AuthorizationControl.NAMED;
+		repository.accessRestriction = AccessRestrictionType.PUSH;
+
+		TeamModel team = new TeamModel("test");
+		
+		assertTrue("team CAN NOT view!", team.canView(repository));
+		assertTrue("team CAN NOT clone!", team.canClone(repository));
+		assertFalse("team CAN push!", team.canPush(repository));
+		
+		assertFalse("team CAN create ref!", team.canCreateRef(repository));
+		assertFalse("team CAN delete ref!", team.canDeleteRef(repository));
+		assertFalse("team CAN rewind ref!", team.canRewindRef(repository));
+		
+		assertEquals("team has wrong permission!", AccessPermission.CLONE, team.getRepositoryPermission(repository).permission);
+
+	}
+
+	/**
+	 * CLONE_NONE = CLONE access restriction, NO access permission
+	 */
+	@Test
+	public void testTeam_CLONE_NONE() throws Exception {
+		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
+		repository.authorizationControl = AuthorizationControl.NAMED;
+		repository.accessRestriction = AccessRestrictionType.CLONE;
+
+		TeamModel team = new TeamModel("test");
+		
+		assertTrue("team CAN NOT view!", team.canView(repository));
+		assertFalse("team CAN clone!", team.canClone(repository));
+		assertFalse("team CAN push!", team.canPush(repository));
+		
+		assertFalse("team CAN create ref!", team.canCreateRef(repository));
+		assertFalse("team CAN delete ref!", team.canDeleteRef(repository));
+		assertFalse("team CAN rewind ref!", team.canRewindRef(repository));
+		
+		assertEquals("team has wrong permission!", AccessPermission.VIEW, team.getRepositoryPermission(repository).permission);
+	}
+
+	/**
+	 * VIEW_NONE = VIEW access restriction, NO access permission
+	 */
+	@Test
+	public void testTeam_VIEW_NONE() throws Exception {
+		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
+		repository.authorizationControl = AuthorizationControl.NAMED;
+		repository.accessRestriction = AccessRestrictionType.VIEW;
+
+		TeamModel team = new TeamModel("test");
+		
+		assertFalse("team CAN view!", team.canView(repository));
+		assertFalse("team CAN clone!", team.canClone(repository));
+		assertFalse("team CAN push!", team.canPush(repository));
+		
+		assertFalse("team CAN create ref!", team.canCreateRef(repository));
+		assertFalse("team CAN delete ref!", team.canDeleteRef(repository));
+		assertFalse("team CAN rewind ref!", team.canRewindRef(repository));
+		
+		assertEquals("team has wrong permission!", AccessPermission.NONE, team.getRepositoryPermission(repository).permission);
+	}
+	
+	/**
+	 * NONE_PUSH = NO access restriction, PUSH access permission
+	 * (not useful scenario)
+	 */
+	@Test
+	public void testTeam_NONE_PUSH() throws Exception {
+		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
+		repository.authorizationControl = AuthorizationControl.NAMED;
+		repository.accessRestriction = AccessRestrictionType.NONE;
+
+		TeamModel team = new TeamModel("test");
+		team.setRepositoryPermission(repository.name, AccessPermission.PUSH);
+		
+		assertTrue("team CAN NOT view!", team.canView(repository));
+		assertTrue("team CAN NOT clone!", team.canClone(repository));
+		assertTrue("team CAN NOT push!", team.canPush(repository));
+		
+		assertTrue("team CAN NOT create ref!", team.canCreateRef(repository));
+		assertTrue("team CAN NOT delete ref!", team.canDeleteRef(repository));
+		assertTrue("team CAN NOT rewind ref!", team.canRewindRef(repository));
+		
+		assertEquals("team has wrong permission!", AccessPermission.REWIND, team.getRepositoryPermission(repository).permission);
+	}
+	
+	/**
+	 * PUSH_PUSH = PUSH access restriction, PUSH access permission
+	 */
+	@Test
+	public void testTeam_PUSH_PUSH() throws Exception {
+		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
+		repository.authorizationControl = AuthorizationControl.NAMED;
+		repository.accessRestriction = AccessRestrictionType.PUSH;
+
+		TeamModel team = new TeamModel("test");
+		team.setRepositoryPermission(repository.name, AccessPermission.PUSH);
+		
+		assertTrue("team CAN NOT view!", team.canView(repository));
+		assertTrue("team CAN NOT clone!", team.canClone(repository));
+		assertTrue("team CAN NOT push!", team.canPush(repository));
+		
+		assertFalse("team CAN create ref!", team.canCreateRef(repository));
+		assertFalse("team CAN delete ref!", team.canDeleteRef(repository));
+		assertFalse("team CAN rewind ref!", team.canRewindRef(repository));
+		
+		assertEquals("team has wrong permission!", AccessPermission.PUSH, team.getRepositoryPermission(repository).permission);
+	}
+	
+	/**
+	 * CLONE_PUSH = CLONE access restriction, PUSH access permission
+	 */
+	@Test
+	public void testTeam_CLONE_PUSH() throws Exception {
+		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
+		repository.authorizationControl = AuthorizationControl.NAMED;
+		repository.accessRestriction = AccessRestrictionType.CLONE;
+
+		TeamModel team = new TeamModel("test");
+		team.setRepositoryPermission(repository.name, AccessPermission.PUSH);
+		
+		assertTrue("team CAN NOT view!", team.canView(repository));
+		assertTrue("team CAN NOT clone!", team.canClone(repository));
+		assertTrue("team CAN NOT push!", team.canPush(repository));
+		
+		assertFalse("team CAN create ref!", team.canCreateRef(repository));
+		assertFalse("team CAN delete ref!", team.canDeleteRef(repository));
+		assertFalse("team CAN rewind ref!", team.canRewindRef(repository));
+		
+		assertEquals("team has wrong permission!", AccessPermission.PUSH, team.getRepositoryPermission(repository).permission);
+	}
+	
+	/**
+	 * VIEW_PUSH = VIEW access restriction, PUSH access permission
+	 */
+	@Test
+	public void testTeam_VIEW_PUSH() throws Exception {
+		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
+		repository.authorizationControl = AuthorizationControl.NAMED;
+		repository.accessRestriction = AccessRestrictionType.VIEW;
+
+		TeamModel team = new TeamModel("test");
+		team.setRepositoryPermission(repository.name, AccessPermission.PUSH);
+		
+		assertTrue("team CAN NOT view!", team.canView(repository));
+		assertTrue("team CAN NOT clone!", team.canClone(repository));
+		assertTrue("team CAN NOT push!", team.canPush(repository));
+		
+		assertFalse("team CAN create ref!", team.canCreateRef(repository));
+		assertFalse("team CAN delete ref!", team.canDeleteRef(repository));
+		assertFalse("team CAN rewind ref!", team.canRewindRef(repository));
+
+		assertEquals("team has wrong permission!", AccessPermission.PUSH, team.getRepositoryPermission(repository).permission);
+	}
+	
+	/**
+	 * NONE_CREATE = NO access restriction, CREATE access permission
+	 * (not useful scenario)
+	 */
+	@Test
+	public void testTeam_NONE_CREATE() throws Exception {
+		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
+		repository.authorizationControl = AuthorizationControl.NAMED;
+		repository.accessRestriction = AccessRestrictionType.NONE;
+
+		TeamModel team = new TeamModel("test");
+		team.setRepositoryPermission(repository.name, AccessPermission.CREATE);
+		
+		assertTrue("team CAN NOT view!", team.canView(repository));
+		assertTrue("team CAN NOT clone!", team.canClone(repository));
+		assertTrue("team CAN NOT push!", team.canPush(repository));
+		
+		assertTrue("team CAN NOT create ref!", team.canCreateRef(repository));
+		assertTrue("team CAN NOT delete ref!", team.canDeleteRef(repository));
+		assertTrue("team CAN NOT rewind ref!", team.canRewindRef(repository));
+
+		assertEquals("team has wrong permission!", AccessPermission.REWIND, team.getRepositoryPermission(repository).permission);
+	}
+	
+	/**
+	 * PUSH_CREATE = PUSH access restriction, CREATE access permission
+	 */
+	@Test
+	public void testTeam_PUSH_CREATE() throws Exception {
+		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
+		repository.authorizationControl = AuthorizationControl.NAMED;
+		repository.accessRestriction = AccessRestrictionType.PUSH;
+
+		TeamModel team = new TeamModel("test");
+		team.setRepositoryPermission(repository.name, AccessPermission.CREATE);
+		
+		assertTrue("team CAN NOT view!", team.canView(repository));
+		assertTrue("team CAN NOT clone!", team.canClone(repository));
+		assertTrue("team CAN NOT push!", team.canPush(repository));
+		
+		assertTrue("team CAN NOT create ref!", team.canCreateRef(repository));
+		assertFalse("team CAN delete ref!", team.canDeleteRef(repository));
+		assertFalse("team CAN rewind ref!", team.canRewindRef(repository));
+
+		assertEquals("team has wrong permission!", AccessPermission.CREATE, team.getRepositoryPermission(repository).permission);
+	}
+	
+	/**
+	 * CLONE_CREATE = CLONE access restriction, CREATE access permission
+	 */
+	@Test
+	public void testTeam_CLONE_CREATE() throws Exception {
+		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
+		repository.authorizationControl = AuthorizationControl.NAMED;
+		repository.accessRestriction = AccessRestrictionType.CLONE;
+
+		TeamModel team = new TeamModel("test");
+		team.setRepositoryPermission(repository.name, AccessPermission.CREATE);
+		
+		assertTrue("team CAN NOT view!", team.canView(repository));
+		assertTrue("team CAN NOT clone!", team.canClone(repository));
+		assertTrue("team CAN NOT push!", team.canPush(repository));
+		
+		assertTrue("team CAN NOT create ref!", team.canCreateRef(repository));
+		assertFalse("team CAN delete ref!", team.canDeleteRef(repository));
+		assertFalse("team CAN rewind ref!", team.canRewindRef(repository));
+
+		assertEquals("team has wrong permission!", AccessPermission.CREATE, team.getRepositoryPermission(repository).permission);
+	}
+	
+	/**
+	 * VIEW_CREATE = VIEW access restriction, CREATE access permission
+	 */
+	@Test
+	public void testTeam_VIEW_CREATE() throws Exception {
+		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
+		repository.authorizationControl = AuthorizationControl.NAMED;
+		repository.accessRestriction = AccessRestrictionType.VIEW;
+
+		TeamModel team = new TeamModel("test");
+		team.setRepositoryPermission(repository.name, AccessPermission.CREATE);
+		
+		assertTrue("team CAN NOT view!", team.canView(repository));
+		assertTrue("team CAN NOT clone!", team.canClone(repository));
+		assertTrue("team CAN NOT push!", team.canPush(repository));
+		
+		assertTrue("team CAN NOT create ref!", team.canCreateRef(repository));
+		assertFalse("team CAN delete ref!", team.canDeleteRef(repository));
+		assertFalse("team CAN rewind ref!", team.canRewindRef(repository));
+
+		assertEquals("team has wrong permission!", AccessPermission.CREATE, team.getRepositoryPermission(repository).permission);
+	}
+
+	/**
+	 * NONE_DELETE = NO access restriction, DELETE access permission
+	 * (not useful scenario)
+	 */
+	@Test
+	public void testTeam_NONE_DELETE() throws Exception {
+		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
+		repository.authorizationControl = AuthorizationControl.NAMED;
+		repository.accessRestriction = AccessRestrictionType.NONE;
+
+		TeamModel team = new TeamModel("test");
+		team.setRepositoryPermission(repository.name, AccessPermission.DELETE);
+		
+		assertTrue("team CAN NOT view!", team.canView(repository));
+		assertTrue("team CAN NOT clone!", team.canClone(repository));
+		assertTrue("team CAN NOT push!", team.canPush(repository));
+		
+		assertTrue("team CAN NOT create ref!", team.canCreateRef(repository));
+		assertTrue("team CAN NOT delete ref!", team.canDeleteRef(repository));
+		assertTrue("team CAN NOT rewind ref!", team.canRewindRef(repository));
+
+		assertEquals("team has wrong permission!", AccessPermission.REWIND, team.getRepositoryPermission(repository).permission);
+	}
+	
+	/**
+	 * PUSH_DELETE = PUSH access restriction, DELETE access permission
+	 */
+	@Test
+	public void testTeam_PUSH_DELETE() throws Exception {
+		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
+		repository.authorizationControl = AuthorizationControl.NAMED;
+		repository.accessRestriction = AccessRestrictionType.PUSH;
+
+		TeamModel team = new TeamModel("test");
+		team.setRepositoryPermission(repository.name, AccessPermission.DELETE);
+		
+		assertTrue("team CAN NOT view!", team.canView(repository));
+		assertTrue("team CAN NOT clone!", team.canClone(repository));
+		assertTrue("team CAN NOT push!", team.canPush(repository));
+		
+		assertTrue("team CAN NOT create ref!", team.canCreateRef(repository));
+		assertTrue("team CAN NOT delete ref!", team.canDeleteRef(repository));
+		assertFalse("team CAN rewind ref!", team.canRewindRef(repository));
+
+		assertEquals("team has wrong permission!", AccessPermission.DELETE, team.getRepositoryPermission(repository).permission);
+	}
+	
+	/**
+	 * CLONE_DELETE = CLONE access restriction, DELETE access permission
+	 */
+	@Test
+	public void testTeam_CLONE_DELETE() throws Exception {
+		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
+		repository.authorizationControl = AuthorizationControl.NAMED;
+		repository.accessRestriction = AccessRestrictionType.CLONE;
+
+		TeamModel team = new TeamModel("test");
+		team.setRepositoryPermission(repository.name, AccessPermission.DELETE);
+		
+		assertTrue("team CAN NOT view!", team.canView(repository));
+		assertTrue("team CAN NOT clone!", team.canClone(repository));
+		assertTrue("team CAN NOT push!", team.canPush(repository));
+		
+		assertTrue("team CAN NOT create ref!", team.canCreateRef(repository));
+		assertTrue("team CAN NOT delete ref!", team.canDeleteRef(repository));
+		assertFalse("team CAN rewind ref!", team.canRewindRef(repository));
+		
+		assertEquals("team has wrong permission!", AccessPermission.DELETE, team.getRepositoryPermission(repository).permission);
+	}
+	
+	/**
+	 * VIEW_DELETE = VIEW access restriction, DELETE access permission
+	 */
+	@Test
+	public void testTeam_VIEW_DELETE() throws Exception {
+		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
+		repository.authorizationControl = AuthorizationControl.NAMED;
+		repository.accessRestriction = AccessRestrictionType.VIEW;
+
+		TeamModel team = new TeamModel("test");
+		team.setRepositoryPermission(repository.name, AccessPermission.DELETE);
+		
+		assertTrue("team CAN NOT view!", team.canView(repository));
+		assertTrue("team CAN NOT clone!", team.canClone(repository));
+		assertTrue("team CAN NOT push!", team.canPush(repository));
+		
+		assertTrue("team CAN NOT create ref!", team.canCreateRef(repository));
+		assertTrue("team CAN NOT delete ref!", team.canDeleteRef(repository));
+		assertFalse("team CAN rewind ref!", team.canRewindRef(repository));
+
+		assertEquals("team has wrong permission!", AccessPermission.DELETE, team.getRepositoryPermission(repository).permission);
+	}
+	
+	/**
+	 * NONE_REWIND = NO access restriction, REWIND access permission
+	 * (not useful scenario)
+	 */
+	@Test
+	public void testTeam_NONE_REWIND() throws Exception {
+		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
+		repository.authorizationControl = AuthorizationControl.NAMED;
+		repository.accessRestriction = AccessRestrictionType.NONE;
+
+		TeamModel team = new TeamModel("test");
+		team.setRepositoryPermission(repository.name, AccessPermission.REWIND);
+		
+		assertTrue("team CAN NOT view!", team.canView(repository));
+		assertTrue("team CAN NOT clone!", team.canClone(repository));
+		assertTrue("team CAN NOT push!", team.canPush(repository));
+		
+		assertTrue("team CAN NOT create ref!", team.canCreateRef(repository));
+		assertTrue("team CAN NOT delete ref!", team.canDeleteRef(repository));
+		assertTrue("team CAN NOT rewind ref!", team.canRewindRef(repository));
+
+		assertEquals("team has wrong permission!", AccessPermission.REWIND, team.getRepositoryPermission(repository).permission);
+	}
+	
+	/**
+	 * PUSH_REWIND = PUSH access restriction, REWIND access permission
+	 */
+	@Test
+	public void testTeam_PUSH_REWIND() throws Exception {
+		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
+		repository.authorizationControl = AuthorizationControl.NAMED;
+		repository.accessRestriction = AccessRestrictionType.PUSH;
+
+		TeamModel team = new TeamModel("test");
+		team.setRepositoryPermission(repository.name, AccessPermission.REWIND);
+		
+		assertTrue("team CAN NOT view!", team.canView(repository));
+		assertTrue("team CAN NOT clone!", team.canClone(repository));
+		assertTrue("team CAN NOT push!", team.canPush(repository));
+		
+		assertTrue("team CAN NOT create ref!", team.canCreateRef(repository));
+		assertTrue("team CAN NOT delete ref!", team.canDeleteRef(repository));
+		assertTrue("team CAN NOT rewind ref!", team.canRewindRef(repository));
+
+		assertEquals("team has wrong permission!", AccessPermission.REWIND, team.getRepositoryPermission(repository).permission);
+	}
+	
+	/**
+	 * CLONE_REWIND = CLONE access restriction, REWIND access permission
+	 */
+	@Test
+	public void testTeam_CLONE_REWIND() throws Exception {
+		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
+		repository.authorizationControl = AuthorizationControl.NAMED;
+		repository.accessRestriction = AccessRestrictionType.CLONE;
+
+		TeamModel team = new TeamModel("test");
+		team.setRepositoryPermission(repository.name, AccessPermission.REWIND);
+		
+		assertTrue("team CAN NOT view!", team.canView(repository));
+		assertTrue("team CAN NOT clone!", team.canClone(repository));
+		assertTrue("team CAN NOT push!", team.canPush(repository));
+		
+		assertTrue("team CAN NOT create ref!", team.canCreateRef(repository));
+		assertTrue("team CAN NOT delete ref!", team.canDeleteRef(repository));
+		assertTrue("team CAN NOT rewind ref!", team.canRewindRef(repository));
+
+		assertEquals("team has wrong permission!", AccessPermission.REWIND, team.getRepositoryPermission(repository).permission);
+	}
+	
+	/**
+	 * VIEW_REWIND = VIEW access restriction, REWIND access permission
+	 */
+	@Test
+	public void testTeam_VIEW_REWIND() throws Exception {
+		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
+		repository.authorizationControl = AuthorizationControl.NAMED;
+		repository.accessRestriction = AccessRestrictionType.VIEW;
+
+		TeamModel team = new TeamModel("test");
+		team.setRepositoryPermission(repository.name, AccessPermission.REWIND);
+		
+		assertTrue("team CAN NOT view!", team.canView(repository));
+		assertTrue("team CAN NOT clone!", team.canClone(repository));
+		assertTrue("team CAN NOT push!", team.canPush(repository));
+		
+		assertTrue("team CAN NOT create ref!", team.canCreateRef(repository));
+		assertTrue("team CAN NOT delete ref!", team.canDeleteRef(repository));
+		assertTrue("team CAN NOT rewind ref!", team.canRewindRef(repository));
+
+		assertEquals("team has wrong permission!", AccessPermission.REWIND, team.getRepositoryPermission(repository).permission);
+	}
+	
+	/**
+	 * NONE_CLONE = NO access restriction, CLONE access permission
+	 * (not useful scenario)
+	 */
+	@Test
+	public void testTeam_NONE_CLONE() throws Exception {
+		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
+		repository.authorizationControl = AuthorizationControl.NAMED;
+		repository.accessRestriction = AccessRestrictionType.NONE;
+
+		TeamModel team = new TeamModel("test");
+		team.setRepositoryPermission(repository.name, AccessPermission.CLONE);
+		
+		assertTrue("team CAN NOT view!", team.canView(repository));
+		assertTrue("team CAN NOT clone!", team.canClone(repository));
+		assertTrue("team CAN NOT push!", team.canPush(repository));
+		
+		assertTrue("team CAN NOT create ref!", team.canCreateRef(repository));
+		assertTrue("team CAN NOT delete ref!", team.canDeleteRef(repository));
+		assertTrue("team CAN NOT rewind ref!", team.canRewindRef(repository));
+
+		assertEquals("team has wrong permission!", AccessPermission.REWIND, team.getRepositoryPermission(repository).permission);
+	}
+
+	/**
+	 * PUSH_CLONE = PUSH access restriction, CLONE access permission
+	 */
+	@Test
+	public void testTeam_PUSH_CLONE() throws Exception {
+		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
+		repository.authorizationControl = AuthorizationControl.NAMED;
+		repository.accessRestriction = AccessRestrictionType.PUSH;
+
+		TeamModel team = new TeamModel("test");
+		team.setRepositoryPermission(repository.name, AccessPermission.CLONE);
+		
+		assertTrue("team CAN NOT view!", team.canView(repository));
+		assertTrue("team CAN NOT clone!", team.canClone(repository));
+		assertFalse("team CAN push!", team.canPush(repository));
+		
+		assertFalse("team CAN create ref!", team.canCreateRef(repository));
+		assertFalse("team CAN delete ref!", team.canDeleteRef(repository));
+		assertFalse("team CAN rewind ref!", team.canRewindRef(repository));
+
+		assertEquals("team has wrong permission!", AccessPermission.CLONE, team.getRepositoryPermission(repository).permission);
+	}
+
+	/**
+	 * CLONE_CLONE = CLONE access restriction, CLONE access permission
+	 */
+	@Test
+	public void testTeam_CLONE_CLONE() throws Exception {
+		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
+		repository.authorizationControl = AuthorizationControl.NAMED;
+		repository.accessRestriction = AccessRestrictionType.CLONE;
+
+		TeamModel team = new TeamModel("test");
+		team.setRepositoryPermission(repository.name, AccessPermission.CLONE);
+		
+		assertTrue("team CAN NOT view!", team.canView(repository));
+		assertTrue("team CAN NOT clone!", team.canClone(repository));
+		assertFalse("team CAN push!", team.canPush(repository));
+		
+		assertFalse("team CAN create ref!", team.canCreateRef(repository));
+		assertFalse("team CAN delete ref!", team.canDeleteRef(repository));
+		assertFalse("team CAN rewind ref!", team.canRewindRef(repository));
+
+		assertEquals("team has wrong permission!", AccessPermission.CLONE, team.getRepositoryPermission(repository).permission);
+	}
+	
+	/**
+	 * VIEW_CLONE = VIEW access restriction, CLONE access permission
+	 */
+	@Test
+	public void testTeam_VIEW_CLONE() throws Exception {
+		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
+		repository.authorizationControl = AuthorizationControl.NAMED;
+		repository.accessRestriction = AccessRestrictionType.VIEW;
+
+		TeamModel team = new TeamModel("test");
+		team.setRepositoryPermission(repository.name, AccessPermission.CLONE);
+		
+		assertTrue("team CAN NOT view!", team.canView(repository));
+		assertTrue("team CAN NOT clone!", team.canClone(repository));
+		assertFalse("team CAN push!", team.canPush(repository));
+		
+		assertFalse("team CAN create ref!", team.canCreateRef(repository));
+		assertFalse("team CAN delete ref!", team.canDeleteRef(repository));
+		assertFalse("team CAN rewind ref!", team.canRewindRef(repository));
+
+		assertEquals("team has wrong permission!", AccessPermission.CLONE, team.getRepositoryPermission(repository).permission);
+	}
+
+	/**
+	 * NONE_VIEW = NO access restriction, VIEW access permission
+	 * (not useful scenario)
+	 */
+	@Test
+	public void testTeam_NONE_VIEW() throws Exception {
+		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
+		repository.authorizationControl = AuthorizationControl.NAMED;
+		repository.accessRestriction = AccessRestrictionType.NONE;
+
+		TeamModel team = new TeamModel("test");
+		team.setRepositoryPermission(repository.name, AccessPermission.VIEW);
+		
+		assertTrue("team CAN NOT view!", team.canView(repository));
+		assertTrue("team CAN NOT clone!", team.canClone(repository));
+		assertTrue("team CAN NOT push!", team.canPush(repository));
+		
+		assertTrue("team CAN NOT create ref!", team.canCreateRef(repository));
+		assertTrue("team CAN NOT delete ref!", team.canDeleteRef(repository));
+		assertTrue("team CAN NOT rewind ref!", team.canRewindRef(repository));
+
+		assertEquals("team has wrong permission!", AccessPermission.REWIND, team.getRepositoryPermission(repository).permission);
+	}
+
+	/**
+	 * PUSH_VIEW = PUSH access restriction, VIEW access permission
+	 */
+	@Test
+	public void testTeam_PUSH_VIEW() throws Exception {
+		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
+		repository.authorizationControl = AuthorizationControl.NAMED;
+		repository.accessRestriction = AccessRestrictionType.PUSH;
+
+		TeamModel team = new TeamModel("test");
+		team.setRepositoryPermission(repository.name, AccessPermission.VIEW);
+		
+		assertTrue("team CAN NOT view!", team.canView(repository));
+		assertTrue("team CAN NOT clone!", team.canClone(repository));
+		assertFalse("team CAN push!", team.canPush(repository));
+		
+		assertFalse("team CAN create ref!", team.canCreateRef(repository));
+		assertFalse("team CAN delete ref!", team.canDeleteRef(repository));
+		assertFalse("team CAN rewind ref!", team.canRewindRef(repository));
+
+		assertEquals("team has wrong permission!", AccessPermission.CLONE, team.getRepositoryPermission(repository).permission);
+	}
+	
+	/**
+	 * CLONE_VIEW = CLONE access restriction, VIEW access permission
+	 */
+	@Test
+	public void testTeam_CLONE_VIEW() throws Exception {
+		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
+		repository.authorizationControl = AuthorizationControl.NAMED;
+		repository.accessRestriction = AccessRestrictionType.CLONE;
+
+		TeamModel team = new TeamModel("test");
+		team.setRepositoryPermission(repository.name, AccessPermission.VIEW);
+		
+		assertTrue("team CAN NOT view!", team.canView(repository));
+		assertFalse("team CAN clone!", team.canClone(repository));
+		assertFalse("team CAN push!", team.canPush(repository));
+		
+		assertFalse("team CAN create ref!", team.canCreateRef(repository));
+		assertFalse("team CAN delete ref!", team.canDeleteRef(repository));
+		assertFalse("team CAN rewind ref!", team.canRewindRef(repository));
+
+		assertEquals("team has wrong permission!", AccessPermission.VIEW, team.getRepositoryPermission(repository).permission);
+	}
+	
+	/**
+	 * VIEW_VIEW = VIEW access restriction, VIEW access permission
+	 */
+	@Test
+	public void testTeam_VIEW_VIEW() throws Exception {
+		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
+		repository.authorizationControl = AuthorizationControl.NAMED;
+		repository.accessRestriction = AccessRestrictionType.VIEW;
+
+		TeamModel team = new TeamModel("test");
+		team.setRepositoryPermission(repository.name, AccessPermission.VIEW);
+		
+		assertTrue("team CAN NOT view!", team.canView(repository));
+		assertFalse("team CAN clone!", team.canClone(repository));
+		assertFalse("team CAN push!", team.canPush(repository));
+		
+		assertFalse("team CAN create ref!", team.canCreateRef(repository));
+		assertFalse("team CAN delete ref!", team.canDeleteRef(repository));
+		assertFalse("team CAN rewind ref!", team.canRewindRef(repository));
+
+		assertEquals("team has wrong permission!", AccessPermission.VIEW, team.getRepositoryPermission(repository).permission);
+	}
+	
+	/**
+	 * NONE_NONE = NO access restriction, NO access permission
+	 */
+	@Test
+	public void testTeamMember_NONE_NONE() throws Exception {
+		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
+		repository.authorizationControl = AuthorizationControl.NAMED;
+		repository.accessRestriction = AccessRestrictionType.NONE;
+
+		TeamModel team = new TeamModel("test");
+		UserModel user = new UserModel("test");
+		user.teams.add(team);
+		
+		assertTrue("team member CAN NOT view!", user.canView(repository));
+		assertTrue("team member CAN NOT clone!", user.canClone(repository));
+		assertTrue("team member CAN NOT push!", user.canPush(repository));
+		
+		assertTrue("team member CAN NOT create ref!", user.canCreateRef(repository));
+		assertTrue("team member CAN NOT delete ref!", user.canDeleteRef(repository));
+		assertTrue("team member CAN NOT rewind ref!", user.canRewindRef(repository));
+
+		assertEquals("team member has wrong permission!", AccessPermission.REWIND, user.getRepositoryPermission(repository).permission);
+	}
+	
+	/**
+	 * PUSH_NONE = PUSH access restriction, NO access permission
+	 */
+	@Test
+	public void testTeamMember_PUSH_NONE() throws Exception {
+		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
+		repository.authorizationControl = AuthorizationControl.NAMED;
+		repository.accessRestriction = AccessRestrictionType.PUSH;
+
+		TeamModel team = new TeamModel("test");
+		UserModel user = new UserModel("test");
+		user.teams.add(team);
+		
+		assertTrue("team member CAN NOT view!", user.canView(repository));
+		assertTrue("team member CAN NOT clone!", user.canClone(repository));
+		assertFalse("team member CAN push!", user.canPush(repository));
+		
+		assertFalse("team member CAN create ref!", user.canCreateRef(repository));
+		assertFalse("team member CAN delete ref!", user.canDeleteRef(repository));
+		assertFalse("team member CAN rewind ref!", user.canRewindRef(repository));
+
+		assertEquals("team member has wrong permission!", AccessPermission.CLONE, user.getRepositoryPermission(repository).permission);
+	}
+
+	/**
+	 * CLONE_NONE = CLONE access restriction, NO access permission
+	 */
+	@Test
+	public void testTeamMember_CLONE_NONE() throws Exception {
+		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
+		repository.authorizationControl = AuthorizationControl.NAMED;
+		repository.accessRestriction = AccessRestrictionType.CLONE;
+
+		TeamModel team = new TeamModel("test");
+		UserModel user = new UserModel("test");
+		user.teams.add(team);
+		
+		assertTrue("team member CAN NOT view!", user.canView(repository));
+		assertFalse("team member CAN clone!", user.canClone(repository));
+		assertFalse("team member CAN push!", user.canPush(repository));
+		
+		assertFalse("team member CAN create ref!", user.canCreateRef(repository));
+		assertFalse("team member CAN delete ref!", user.canDeleteRef(repository));
+		assertFalse("team member CAN rewind ref!", user.canRewindRef(repository));
+
+		assertEquals("team member has wrong permission!", AccessPermission.VIEW, user.getRepositoryPermission(repository).permission);
+	}
+
+	/**
+	 * VIEW_NONE = VIEW access restriction, NO access permission
+	 */
+	@Test
+	public void testTeamMember_VIEW_NONE() throws Exception {
+		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
+		repository.authorizationControl = AuthorizationControl.NAMED;
+		repository.accessRestriction = AccessRestrictionType.VIEW;
+
+		TeamModel team = new TeamModel("test");
+		UserModel user = new UserModel("test");
+		user.teams.add(team);
+		
+		assertFalse("team member CAN view!", user.canView(repository));
+		assertFalse("team member CAN clone!", user.canClone(repository));
+		assertFalse("team member CAN push!", user.canPush(repository));
+		
+		assertFalse("team member CAN create ref!", user.canCreateRef(repository));
+		assertFalse("team member CAN delete ref!", user.canDeleteRef(repository));
+		assertFalse("team member CAN rewind ref!", user.canRewindRef(repository));
+
+		assertEquals("team member has wrong permission!", AccessPermission.NONE, user.getRepositoryPermission(repository).permission);
+	}
+	
+	/**
+	 * NONE_PUSH = NO access restriction, PUSH access permission
+	 * (not useful scenario)
+	 */
+	@Test
+	public void testTeamMember_NONE_PUSH() throws Exception {
+		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
+		repository.authorizationControl = AuthorizationControl.NAMED;
+		repository.accessRestriction = AccessRestrictionType.NONE;
+
+		TeamModel team = new TeamModel("test");
+		team.setRepositoryPermission(repository.name, AccessPermission.PUSH);
+		UserModel user = new UserModel("test");
+		user.teams.add(team);
+		
+		assertTrue("team member CAN NOT view!", user.canView(repository));
+		assertTrue("team member CAN NOT clone!", user.canClone(repository));
+		assertTrue("team member CAN NOT push!", user.canPush(repository));
+		
+		assertTrue("team member CAN NOT create ref!", user.canCreateRef(repository));
+		assertTrue("team member CAN NOT delete ref!", user.canDeleteRef(repository));
+		assertTrue("team member CAN NOT rewind ref!", user.canRewindRef(repository));
+
+		assertEquals("team member has wrong permission!", AccessPermission.REWIND, user.getRepositoryPermission(repository).permission);
+	}
+	
+	/**
+	 * PUSH_PUSH = PUSH access restriction, PUSH access permission
+	 */
+	@Test
+	public void testTeamMember_PUSH_PUSH() throws Exception {
+		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
+		repository.authorizationControl = AuthorizationControl.NAMED;
+		repository.accessRestriction = AccessRestrictionType.PUSH;
+
+		TeamModel team = new TeamModel("test");
+		team.setRepositoryPermission(repository.name, AccessPermission.PUSH);
+		UserModel user = new UserModel("test");
+		user.teams.add(team);
+
+		assertTrue("team member CAN NOT view!", user.canView(repository));
+		assertTrue("team member CAN NOT clone!", user.canClone(repository));
+		assertTrue("team member CAN NOT push!", user.canPush(repository));
+		
+		assertFalse("team member CAN create ref!", user.canCreateRef(repository));
+		assertFalse("team member CAN delete ref!", user.canDeleteRef(repository));
+		assertFalse("team member CAN rewind ref!", user.canRewindRef(repository));
+
+		assertEquals("team member has wrong permission!", AccessPermission.PUSH, user.getRepositoryPermission(repository).permission);
+	}
+	
+	/**
+	 * CLONE_PUSH = CLONE access restriction, PUSH access permission
+	 */
+	@Test
+	public void testTeamMember_CLONE_PUSH() throws Exception {
+		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
+		repository.authorizationControl = AuthorizationControl.NAMED;
+		repository.accessRestriction = AccessRestrictionType.CLONE;
+
+		TeamModel team = new TeamModel("test");
+		team.setRepositoryPermission(repository.name, AccessPermission.PUSH);
+		UserModel user = new UserModel("test");
+		user.teams.add(team);
+
+		assertTrue("team member CAN NOT view!", user.canView(repository));
+		assertTrue("team member CAN NOT clone!", user.canClone(repository));
+		assertTrue("team member CAN NOT push!", user.canPush(repository));
+		
+		assertFalse("team member CAN create ref!", user.canCreateRef(repository));
+		assertFalse("team member CAN delete ref!", user.canDeleteRef(repository));
+		assertFalse("team member CAN rewind ref!", user.canRewindRef(repository));
+
+		assertEquals("team member has wrong permission!", AccessPermission.PUSH, user.getRepositoryPermission(repository).permission);
+	}
+	
+	/**
+	 * VIEW_PUSH = VIEW access restriction, PUSH access permission
+	 */
+	@Test
+	public void testTeamMember_VIEW_PUSH() throws Exception {
+		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
+		repository.authorizationControl = AuthorizationControl.NAMED;
+		repository.accessRestriction = AccessRestrictionType.VIEW;
+
+		TeamModel team = new TeamModel("test");
+		team.setRepositoryPermission(repository.name, AccessPermission.PUSH);
+		UserModel user = new UserModel("test");
+		user.teams.add(team);
+
+		assertTrue("team member CAN NOT view!", user.canView(repository));
+		assertTrue("team member CAN NOT clone!", user.canClone(repository));
+		assertTrue("team member CAN NOT push!", user.canPush(repository));
+		
+		assertFalse("team member CAN create ref!", user.canCreateRef(repository));
+		assertFalse("team member CAN delete ref!", user.canDeleteRef(repository));
+		assertFalse("team member CAN rewind ref!", user.canRewindRef(repository));
+
+		assertEquals("team member has wrong permission!", AccessPermission.PUSH, user.getRepositoryPermission(repository).permission);
+	}
+	
+	/**
+	 * NONE_CREATE = NO access restriction, CREATE access permission
+	 * (not useful scenario)
+	 */
+	@Test
+	public void testTeamMember_NONE_CREATE() throws Exception {
+		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
+		repository.authorizationControl = AuthorizationControl.NAMED;
+		repository.accessRestriction = AccessRestrictionType.NONE;
+
+		TeamModel team = new TeamModel("test");
+		team.setRepositoryPermission(repository.name, AccessPermission.CREATE);
+		UserModel user = new UserModel("test");
+		user.teams.add(team);
+		
+		assertTrue("team member CAN NOT view!", user.canView(repository));
+		assertTrue("team member CAN NOT clone!", user.canClone(repository));
+		assertTrue("team member CAN NOT push!", user.canPush(repository));
+		
+		assertTrue("team member CAN NOT create ref!", user.canCreateRef(repository));
+		assertTrue("team member CAN NOT delete ref!", user.canDeleteRef(repository));
+		assertTrue("team member CAN NOT rewind ref!", user.canRewindRef(repository));
+
+		assertEquals("team member has wrong permission!", AccessPermission.REWIND, user.getRepositoryPermission(repository).permission);
+	}
+	
+	/**
+	 * PUSH_CREATE = PUSH access restriction, CREATE access permission
+	 */
+	@Test
+	public void testTeamMember_PUSH_CREATE() throws Exception {
+		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
+		repository.authorizationControl = AuthorizationControl.NAMED;
+		repository.accessRestriction = AccessRestrictionType.PUSH;
+
+		TeamModel team = new TeamModel("test");
+		team.setRepositoryPermission(repository.name, AccessPermission.CREATE);
+		UserModel user = new UserModel("test");
+		user.teams.add(team);
+
+		assertTrue("team member CAN NOT view!", user.canView(repository));
+		assertTrue("team member CAN NOT clone!", user.canClone(repository));
+		assertTrue("team member CAN NOT push!", user.canPush(repository));
+		
+		assertTrue("team member CAN NOT create ref!", user.canCreateRef(repository));
+		assertFalse("team member CAN delete ref!", user.canDeleteRef(repository));
+		assertFalse("team member CAN rewind ref!", user.canRewindRef(repository));
+
+		assertEquals("team member has wrong permission!", AccessPermission.CREATE, user.getRepositoryPermission(repository).permission);
+	}
+	
+	/**
+	 * CLONE_CREATE = CLONE access restriction, CREATE access permission
+	 */
+	@Test
+	public void testTeamMember_CLONE_CREATE() throws Exception {
+		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
+		repository.authorizationControl = AuthorizationControl.NAMED;
+		repository.accessRestriction = AccessRestrictionType.CLONE;
+
+		TeamModel team = new TeamModel("test");
+		team.setRepositoryPermission(repository.name, AccessPermission.CREATE);
+		UserModel user = new UserModel("test");
+		user.teams.add(team);
+
+		assertTrue("team member CAN NOT view!", user.canView(repository));
+		assertTrue("team member CAN NOT clone!", user.canClone(repository));
+		assertTrue("team member CAN NOT push!", user.canPush(repository));
+		
+		assertTrue("team member CAN NOT create ref!", user.canCreateRef(repository));
+		assertFalse("team member CAN delete ref!", user.canDeleteRef(repository));
+		assertFalse("team member CAN rewind ref!", user.canRewindRef(repository));
+
+		assertEquals("team member has wrong permission!", AccessPermission.CREATE, user.getRepositoryPermission(repository).permission);
+	}
+	
+	/**
+	 * VIEW_CREATE = VIEW access restriction, CREATE access permission
+	 */
+	@Test
+	public void testTeamMember_VIEW_CREATE() throws Exception {
+		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
+		repository.authorizationControl = AuthorizationControl.NAMED;
+		repository.accessRestriction = AccessRestrictionType.VIEW;
+
+		TeamModel team = new TeamModel("test");
+		team.setRepositoryPermission(repository.name, AccessPermission.CREATE);
+		UserModel user = new UserModel("test");
+		user.teams.add(team);
+
+		assertTrue("team member CAN NOT view!", user.canView(repository));
+		assertTrue("team member CAN NOT clone!", user.canClone(repository));
+		assertTrue("team member CAN NOT push!", user.canPush(repository));
+		
+		assertTrue("team member CAN NOT create ref!", user.canCreateRef(repository));
+		assertFalse("team member CAN delete ref!", user.canDeleteRef(repository));
+		assertFalse("team member CAN rewind ref!", user.canRewindRef(repository));
+
+		assertEquals("team member has wrong permission!", AccessPermission.CREATE, user.getRepositoryPermission(repository).permission);
+	}
+
+	/**
+	 * NONE_DELETE = NO access restriction, DELETE access permission
+	 * (not useful scenario)
+	 */
+	@Test
+	public void testTeamMember_NONE_DELETE() throws Exception {
+		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
+		repository.authorizationControl = AuthorizationControl.NAMED;
+		repository.accessRestriction = AccessRestrictionType.NONE;
+
+		TeamModel team = new TeamModel("test");
+		team.setRepositoryPermission(repository.name, AccessPermission.DELETE);
+		UserModel user = new UserModel("test");
+		user.teams.add(team);
+		
+		assertTrue("team member CAN NOT view!", user.canView(repository));
+		assertTrue("team member CAN NOT clone!", user.canClone(repository));
+		assertTrue("team member CAN NOT push!", user.canPush(repository));
+		
+		assertTrue("team member CAN NOT create ref!", user.canCreateRef(repository));
+		assertTrue("team member CAN NOT delete ref!", user.canDeleteRef(repository));
+		assertTrue("team member CAN NOT rewind ref!", user.canRewindRef(repository));
+
+		assertEquals("team member has wrong permission!", AccessPermission.REWIND, user.getRepositoryPermission(repository).permission);
+	}
+	
+	/**
+	 * PUSH_DELETE = PUSH access restriction, DELETE access permission
+	 */
+	@Test
+	public void testTeamMember_PUSH_DELETE() throws Exception {
+		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
+		repository.authorizationControl = AuthorizationControl.NAMED;
+		repository.accessRestriction = AccessRestrictionType.PUSH;
+
+		TeamModel team = new TeamModel("test");
+		team.setRepositoryPermission(repository.name, AccessPermission.DELETE);
+		UserModel user = new UserModel("test");
+		user.teams.add(team);
+
+		assertTrue("team member CAN NOT view!", user.canView(repository));
+		assertTrue("team member CAN NOT clone!", user.canClone(repository));
+		assertTrue("team member CAN NOT push!", user.canPush(repository));
+		
+		assertTrue("team member CAN NOT create ref!", user.canCreateRef(repository));
+		assertTrue("team member CAN NOT delete ref!", user.canDeleteRef(repository));
+		assertFalse("team member CAN rewind ref!", user.canRewindRef(repository));
+
+		assertEquals("team member has wrong permission!", AccessPermission.DELETE, user.getRepositoryPermission(repository).permission);
+	}
+	
+	/**
+	 * CLONE_DELETE = CLONE access restriction, DELETE access permission
+	 */
+	@Test
+	public void testTeamMember_CLONE_DELETE() throws Exception {
+		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
+		repository.authorizationControl = AuthorizationControl.NAMED;
+		repository.accessRestriction = AccessRestrictionType.CLONE;
+
+		TeamModel team = new TeamModel("test");
+		team.setRepositoryPermission(repository.name, AccessPermission.DELETE);
+		UserModel user = new UserModel("test");
+		user.teams.add(team);
+
+		assertTrue("team member CAN NOT view!", user.canView(repository));
+		assertTrue("team member CAN NOT clone!", user.canClone(repository));
+		assertTrue("team member CAN NOT push!", user.canPush(repository));
+		
+		assertTrue("team member CAN NOT create ref!", user.canCreateRef(repository));
+		assertTrue("team member CAN NOT delete ref!", user.canDeleteRef(repository));
+		assertFalse("team member CAN rewind ref!", user.canRewindRef(repository));
+
+		assertEquals("team member has wrong permission!", AccessPermission.DELETE, user.getRepositoryPermission(repository).permission);
+	}
+	
+	/**
+	 * VIEW_DELETE = VIEW access restriction, DELETE access permission
+	 */
+	@Test
+	public void testTeamMember_VIEW_DELETE() throws Exception {
+		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
+		repository.authorizationControl = AuthorizationControl.NAMED;
+		repository.accessRestriction = AccessRestrictionType.VIEW;
+
+		TeamModel team = new TeamModel("test");
+		team.setRepositoryPermission(repository.name, AccessPermission.DELETE);
+		UserModel user = new UserModel("test");
+		user.teams.add(team);
+
+		assertTrue("team member CAN NOT view!", user.canView(repository));
+		assertTrue("team member CAN NOT clone!", user.canClone(repository));
+		assertTrue("team member CAN NOT push!", user.canPush(repository));
+		
+		assertTrue("team member CAN NOT create ref!", user.canCreateRef(repository));
+		assertTrue("team member CAN NOT delete ref!", user.canDeleteRef(repository));
+		assertFalse("team member CAN rewind ref!", user.canRewindRef(repository));
+
+		assertEquals("team member has wrong permission!", AccessPermission.DELETE, user.getRepositoryPermission(repository).permission);
+	}
+
+	/**
+	 * NONE_REWIND = NO access restriction, REWIND access permission
+	 * (not useful scenario)
+	 */
+	@Test
+	public void testTeamMember_NONE_REWIND() throws Exception {
+		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
+		repository.authorizationControl = AuthorizationControl.NAMED;
+		repository.accessRestriction = AccessRestrictionType.NONE;
+
+		TeamModel team = new TeamModel("test");
+		team.setRepositoryPermission(repository.name, AccessPermission.REWIND);
+		UserModel user = new UserModel("test");
+		user.teams.add(team);
+		
+		assertTrue("team member CAN NOT view!", user.canView(repository));
+		assertTrue("team member CAN NOT clone!", user.canClone(repository));
+		assertTrue("team member CAN NOT push!", user.canPush(repository));
+		
+		assertTrue("team member CAN NOT create ref!", user.canCreateRef(repository));
+		assertTrue("team member CAN NOT delete ref!", user.canDeleteRef(repository));
+		assertTrue("team member CAN NOT rewind ref!", user.canRewindRef(repository));
+
+		assertEquals("team member has wrong permission!", AccessPermission.REWIND, user.getRepositoryPermission(repository).permission);
+	}
+	
+	/**
+	 * PUSH_REWIND = PUSH access restriction, REWIND access permission
+	 */
+	@Test
+	public void testTeamMember_PUSH_REWIND() throws Exception {
+		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
+		repository.authorizationControl = AuthorizationControl.NAMED;
+		repository.accessRestriction = AccessRestrictionType.PUSH;
+
+		TeamModel team = new TeamModel("test");
+		team.setRepositoryPermission(repository.name, AccessPermission.REWIND);
+		UserModel user = new UserModel("test");
+		user.teams.add(team);
+
+		assertTrue("team member CAN NOT view!", user.canView(repository));
+		assertTrue("team member CAN NOT clone!", user.canClone(repository));
+		assertTrue("team member CAN NOT push!", user.canPush(repository));
+		
+		assertTrue("team member CAN NOT create ref!", user.canCreateRef(repository));
+		assertTrue("team member CAN NOT delete ref!", user.canDeleteRef(repository));
+		assertTrue("team member CAN NOT rewind ref!", user.canRewindRef(repository));
+
+		assertEquals("team member has wrong permission!", AccessPermission.REWIND, user.getRepositoryPermission(repository).permission);
+	}
+	
+	/**
+	 * CLONE_REWIND = CLONE access restriction, REWIND access permission
+	 */
+	@Test
+	public void testTeamMember_CLONE_REWIND() throws Exception {
+		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
+		repository.authorizationControl = AuthorizationControl.NAMED;
+		repository.accessRestriction = AccessRestrictionType.CLONE;
+
+		TeamModel team = new TeamModel("test");
+		team.setRepositoryPermission(repository.name, AccessPermission.REWIND);
+		UserModel user = new UserModel("test");
+		user.teams.add(team);
+
+		assertTrue("team member CAN NOT view!", user.canView(repository));
+		assertTrue("team member CAN NOT clone!", user.canClone(repository));
+		assertTrue("team member CAN NOT push!", user.canPush(repository));
+		
+		assertTrue("team member CAN NOT create ref!", user.canCreateRef(repository));
+		assertTrue("team member CAN NOT delete ref!", user.canDeleteRef(repository));
+		assertTrue("team member CAN NOT rewind ref!", user.canRewindRef(repository));
+
+		assertEquals("team member has wrong permission!", AccessPermission.REWIND, user.getRepositoryPermission(repository).permission);
+	}
+	
+	/**
+	 * VIEW_REWIND = VIEW access restriction, REWIND access permission
+	 */
+	@Test
+	public void testTeamMember_VIEW_REWIND() throws Exception {
+		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
+		repository.authorizationControl = AuthorizationControl.NAMED;
+		repository.accessRestriction = AccessRestrictionType.VIEW;
+
+		TeamModel team = new TeamModel("test");
+		team.setRepositoryPermission(repository.name, AccessPermission.REWIND);
+		UserModel user = new UserModel("test");
+		user.teams.add(team);
+
+		assertTrue("team member CAN NOT view!", user.canView(repository));
+		assertTrue("team member CAN NOT clone!", user.canClone(repository));
+		assertTrue("team member CAN NOT push!", user.canPush(repository));
+		
+		assertTrue("team member CAN NOT create ref!", user.canCreateRef(repository));
+		assertTrue("team member CAN NOT delete ref!", user.canDeleteRef(repository));
+		assertTrue("team member CAN NOT rewind ref!", user.canRewindRef(repository));
+
+		assertEquals("team member has wrong permission!", AccessPermission.REWIND, user.getRepositoryPermission(repository).permission);
+	}
+	
+	/**
+	 * NONE_CLONE = NO access restriction, CLONE access permission
+	 * (not useful scenario)
+	 */
+	@Test
+	public void testTeamMember_NONE_CLONE() throws Exception {
+		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
+		repository.authorizationControl = AuthorizationControl.NAMED;
+		repository.accessRestriction = AccessRestrictionType.NONE;
+
+		TeamModel team = new TeamModel("test");
+		team.setRepositoryPermission(repository.name, AccessPermission.CLONE);
+		UserModel user = new UserModel("test");
+		user.teams.add(team);
+
+		assertTrue("team member CAN NOT view!", user.canView(repository));
+		assertTrue("team member CAN NOT clone!", user.canClone(repository));
+		assertTrue("team member CAN NOT push!", user.canPush(repository));
+		
+		assertTrue("team member CAN NOT create ref!", user.canCreateRef(repository));
+		assertTrue("team member CAN NOT delete ref!", user.canDeleteRef(repository));
+		assertTrue("team member CAN NOT rewind ref!", user.canRewindRef(repository));
+
+		assertEquals("team member has wrong permission!", AccessPermission.REWIND, user.getRepositoryPermission(repository).permission);
+	}
+
+	/**
+	 * PUSH_CLONE = PUSH access restriction, CLONE access permission
+	 */
+	@Test
+	public void testTeamMember_PUSH_CLONE() throws Exception {
+		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
+		repository.authorizationControl = AuthorizationControl.NAMED;
+		repository.accessRestriction = AccessRestrictionType.PUSH;
+
+		TeamModel team = new TeamModel("test");
+		team.setRepositoryPermission(repository.name, AccessPermission.CLONE);
+		UserModel user = new UserModel("test");
+		user.teams.add(team);
+
+		assertTrue("team member CAN NOT view!", user.canView(repository));
+		assertTrue("team member CAN NOT clone!", user.canClone(repository));
+		assertFalse("team member CAN push!", user.canPush(repository));
+		
+		assertFalse("team member CAN create ref!", user.canCreateRef(repository));
+		assertFalse("team member CAN delete ref!", user.canDeleteRef(repository));
+		assertFalse("team member CAN rewind ref!", user.canRewindRef(repository));
+
+		assertEquals("team member has wrong permission!", AccessPermission.CLONE, user.getRepositoryPermission(repository).permission);
+	}
+
+	/**
+	 * CLONE_CLONE = CLONE access restriction, CLONE access permission
+	 */
+	@Test
+	public void testTeamMember_CLONE_CLONE() throws Exception {
+		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
+		repository.authorizationControl = AuthorizationControl.NAMED;
+		repository.accessRestriction = AccessRestrictionType.CLONE;
+
+		TeamModel team = new TeamModel("test");
+		team.setRepositoryPermission(repository.name, AccessPermission.CLONE);
+		UserModel user = new UserModel("test");
+		user.teams.add(team);
+
+		assertTrue("team member CAN NOT view!", user.canView(repository));
+		assertTrue("team member CAN NOT clone!", user.canClone(repository));
+		assertFalse("team member CAN push!", user.canPush(repository));
+		
+		assertFalse("team member CAN create ref!", user.canCreateRef(repository));
+		assertFalse("team member CAN delete ref!", user.canDeleteRef(repository));
+		assertFalse("team member CAN rewind ref!", user.canRewindRef(repository));
+
+		assertEquals("team member has wrong permission!", AccessPermission.CLONE, user.getRepositoryPermission(repository).permission);
+	}
+	
+	/**
+	 * VIEW_CLONE = VIEW access restriction, CLONE access permission
+	 */
+	@Test
+	public void testTeamMember_VIEW_CLONE() throws Exception {
+		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
+		repository.authorizationControl = AuthorizationControl.NAMED;
+		repository.accessRestriction = AccessRestrictionType.VIEW;
+
+		TeamModel team = new TeamModel("test");
+		team.setRepositoryPermission(repository.name, AccessPermission.CLONE);
+		UserModel user = new UserModel("test");
+		user.teams.add(team);
+
+		assertTrue("team member CAN NOT view!", user.canView(repository));
+		assertTrue("team member CAN NOT clone!", user.canClone(repository));
+		assertFalse("team member CAN push!", user.canPush(repository));
+		
+		assertFalse("team member CAN create ref!", user.canCreateRef(repository));
+		assertFalse("team member CAN delete ref!", user.canDeleteRef(repository));
+		assertFalse("team member CAN rewind ref!", user.canRewindRef(repository));
+
+		assertEquals("team member has wrong permission!", AccessPermission.CLONE, user.getRepositoryPermission(repository).permission);
+	}
+
+	/**
+	 * NONE_VIEW = NO access restriction, VIEW access permission
+	 * (not useful scenario)
+	 */
+	@Test
+	public void testTeamMember_NONE_VIEW() throws Exception {
+		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
+		repository.authorizationControl = AuthorizationControl.NAMED;
+		repository.accessRestriction = AccessRestrictionType.NONE;
+
+		TeamModel team = new TeamModel("test");
+		team.setRepositoryPermission(repository.name, AccessPermission.VIEW);
+		UserModel user = new UserModel("test");
+		user.teams.add(team);
+
+		assertTrue("team member CAN NOT view!", user.canView(repository));
+		assertTrue("team member CAN NOT clone!", user.canClone(repository));
+		assertTrue("team member CAN NOT push!", user.canPush(repository));
+		
+		assertTrue("team member CAN NOT create ref!", user.canCreateRef(repository));
+		assertTrue("team member CAN NOT delete ref!", user.canDeleteRef(repository));
+		assertTrue("team member CAN NOT rewind ref!", user.canRewindRef(repository));
+
+		assertEquals("team member has wrong permission!", AccessPermission.REWIND, user.getRepositoryPermission(repository).permission);
+	}
+
+	/**
+	 * PUSH_VIEW = PUSH access restriction, VIEW access permission
+	 */
+	@Test
+	public void testTeamMember_PUSH_VIEW() throws Exception {
+		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
+		repository.authorizationControl = AuthorizationControl.NAMED;
+		repository.accessRestriction = AccessRestrictionType.PUSH;
+
+		TeamModel team = new TeamModel("test");
+		team.setRepositoryPermission(repository.name, AccessPermission.VIEW);
+		UserModel user = new UserModel("test");
+		user.teams.add(team);
+
+		assertTrue("team member CAN NOT view!", user.canView(repository));
+		assertTrue("team member CAN NOT clone!", user.canClone(repository));
+		assertFalse("team member CAN push!", user.canPush(repository));
+		
+		assertFalse("team member CAN create ref!", user.canCreateRef(repository));
+		assertFalse("team member CAN delete ref!", user.canDeleteRef(repository));
+		assertFalse("team member CAN rewind ref!", user.canRewindRef(repository));
+
+		assertEquals("team member has wrong permission!", AccessPermission.CLONE, user.getRepositoryPermission(repository).permission);
+	}
+	
+	/**
+	 * CLONE_VIEW = CLONE access restriction, VIEW access permission
+	 */
+	@Test
+	public void testTeamMember_CLONE_VIEW() throws Exception {
+		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
+		repository.authorizationControl = AuthorizationControl.NAMED;
+		repository.accessRestriction = AccessRestrictionType.CLONE;
+
+		TeamModel team = new TeamModel("test");
+		team.setRepositoryPermission(repository.name, AccessPermission.VIEW);
+		UserModel user = new UserModel("test");
+		user.teams.add(team);
+
+		assertTrue("team member CAN NOT view!", user.canView(repository));
+		assertFalse("team member CAN clone!", user.canClone(repository));
+		assertFalse("team member CAN push!", user.canPush(repository));
+		
+		assertFalse("team member CAN create ref!", user.canCreateRef(repository));
+		assertFalse("team member CAN delete ref!", user.canDeleteRef(repository));
+		assertFalse("team member CAN rewind ref!", user.canRewindRef(repository));
+
+		assertEquals("team member has wrong permission!", AccessPermission.VIEW, user.getRepositoryPermission(repository).permission);
+	}
+	
+	/**
+	 * VIEW_VIEW = VIEW access restriction, VIEW access permission
+	 */
+	@Test
+	public void testTeamMember_VIEW_VIEW() throws Exception {
+		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
+		repository.authorizationControl = AuthorizationControl.NAMED;
+		repository.accessRestriction = AccessRestrictionType.VIEW;
+
+		TeamModel team = new TeamModel("test");
+		team.setRepositoryPermission(repository.name, AccessPermission.VIEW);
+		UserModel user = new UserModel("test");
+		user.teams.add(team);
+
+		assertTrue("team member CAN NOT view!", user.canView(repository));
+		assertFalse("team member CAN clone!", user.canClone(repository));
+		assertFalse("team member CAN push!", user.canPush(repository));
+		
+		assertFalse("team member CAN create ref!", user.canCreateRef(repository));
+		assertFalse("team member CAN delete ref!", user.canDeleteRef(repository));
+		assertFalse("team member CAN rewind ref!", user.canRewindRef(repository));
+
+		assertEquals("team member has wrong permission!", AccessPermission.VIEW, user.getRepositoryPermission(repository).permission);
+	}
+	
+	@Test
+	public void testOwner() throws Exception {
+		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
+		repository.authorizationControl = AuthorizationControl.NAMED;
+		repository.accessRestriction = AccessRestrictionType.VIEW;
+
+		UserModel user = new UserModel("test");
+		repository.addOwner(user.username);
+
+		assertFalse("user SHOULD NOT HAVE a repository permission!", user.hasRepositoryPermission(repository.name));
+		assertTrue("owner CAN NOT view!", user.canView(repository));
+		assertTrue("owner CAN NOT clone!", user.canClone(repository));
+		assertTrue("owner CAN NOT push!", user.canPush(repository));
+		
+		assertTrue("owner CAN NOT create ref!", user.canCreateRef(repository));
+		assertTrue("owner CAN NOT delete ref!", user.canDeleteRef(repository));
+		assertTrue("owner CAN NOT rewind ref!", user.canRewindRef(repository));
+
+		assertEquals("owner has wrong permission!", AccessPermission.REWIND, user.getRepositoryPermission(repository).permission);
+
+		assertTrue("owner CAN NOT fork!", user.canFork(repository));
+		
+		assertFalse("owner CAN NOT delete!", user.canDelete(repository));
+		assertTrue("owner CAN NOT edit!", user.canEdit(repository));
+	}
+	
+	@Test
+	public void testMultipleOwners() throws Exception {
+		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
+		repository.authorizationControl = AuthorizationControl.NAMED;
+		repository.accessRestriction = AccessRestrictionType.VIEW;
+
+		UserModel user = new UserModel("test");
+		repository.addOwner(user.username);
+		UserModel user2 = new UserModel("test2");
+		repository.addOwner(user2.username);
+
+		// first owner
+		assertFalse("user SHOULD NOT HAVE a repository permission!", user.hasRepositoryPermission(repository.name));
+		assertTrue("owner CAN NOT view!", user.canView(repository));
+		assertTrue("owner CAN NOT clone!", user.canClone(repository));
+		assertTrue("owner CAN NOT push!", user.canPush(repository));
+		
+		assertTrue("owner CAN NOT create ref!", user.canCreateRef(repository));
+		assertTrue("owner CAN NOT delete ref!", user.canDeleteRef(repository));
+		assertTrue("owner CAN NOT rewind ref!", user.canRewindRef(repository));
+
+		assertEquals("owner has wrong permission!", AccessPermission.REWIND, user.getRepositoryPermission(repository).permission);
+
+		assertTrue("owner CAN NOT fork!", user.canFork(repository));
+		
+		assertFalse("owner CAN NOT delete!", user.canDelete(repository));
+		assertTrue("owner CAN NOT edit!", user.canEdit(repository));
+		
+		// second owner
+		assertFalse("user SHOULD NOT HAVE a repository permission!", user2.hasRepositoryPermission(repository.name));
+		assertTrue("owner CAN NOT view!", user2.canView(repository));
+		assertTrue("owner CAN NOT clone!", user2.canClone(repository));
+		assertTrue("owner CAN NOT push!", user2.canPush(repository));
+		
+		assertTrue("owner CAN NOT create ref!", user2.canCreateRef(repository));
+		assertTrue("owner CAN NOT delete ref!", user2.canDeleteRef(repository));
+		assertTrue("owner CAN NOT rewind ref!", user2.canRewindRef(repository));
+
+		assertEquals("owner has wrong permission!", AccessPermission.REWIND, user2.getRepositoryPermission(repository).permission);
+
+		assertTrue("owner CAN NOT fork!", user2.canFork(repository));
+		
+		assertFalse("owner CAN NOT delete!", user2.canDelete(repository));
+		assertTrue("owner CAN NOT edit!", user2.canEdit(repository));
+		
+		assertTrue(repository.isOwner(user.username));
+		assertTrue(repository.isOwner(user2.username));	
+	}
+	
+	@Test
+	public void testOwnerPersonalRepository() throws Exception {
+		RepositoryModel repository = new RepositoryModel("~test/myrepo.git", null, null, new Date());
+		repository.authorizationControl = AuthorizationControl.NAMED;
+		repository.accessRestriction = AccessRestrictionType.VIEW;
+
+		UserModel user = new UserModel("test");
+		repository.addOwner(user.username);
+
+		assertFalse("user SHOULD NOT HAVE a repository permission!", user.hasRepositoryPermission(repository.name));
+		assertTrue("user CAN NOT view!", user.canView(repository));
+		assertTrue("user CAN NOT clone!", user.canClone(repository));
+		assertTrue("user CAN NOT push!", user.canPush(repository));
+		
+		assertTrue("user CAN NOT create ref!", user.canCreateRef(repository));
+		assertTrue("user CAN NOT delete ref!", user.canDeleteRef(repository));
+		assertTrue("user CAN NOT rewind ref!", user.canRewindRef(repository));
+
+		assertEquals("user has wrong permission!", AccessPermission.REWIND, user.getRepositoryPermission(repository).permission);
+
+		assertFalse("user CAN fork!", user.canFork(repository));
+		
+		assertTrue("user CAN NOT delete!", user.canDelete(repository));
+		assertTrue("user CAN NOT edit!", user.canEdit(repository));
+	}
+
+	@Test
+	public void testVisitorPersonalRepository() throws Exception {
+		RepositoryModel repository = new RepositoryModel("~test/myrepo.git", null, null, new Date());
+		repository.authorizationControl = AuthorizationControl.NAMED;
+		repository.accessRestriction = AccessRestrictionType.VIEW;
+
+		UserModel user = new UserModel("visitor");
+		repository.addOwner("test");
+
+		assertFalse("user HAS a repository permission!", user.hasRepositoryPermission(repository.name));
+		assertFalse("user CAN view!", user.canView(repository));
+		assertFalse("user CAN clone!", user.canClone(repository));
+		assertFalse("user CAN push!", user.canPush(repository));
+		
+		assertFalse("user CAN create ref!", user.canCreateRef(repository));
+		assertFalse("user CAN delete ref!", user.canDeleteRef(repository));
+		assertFalse("user CAN rewind ref!", user.canRewindRef(repository));
+
+		assertEquals("user has wrong permission!", AccessPermission.NONE, user.getRepositoryPermission(repository).permission);
+
+		assertFalse("user CAN fork!", user.canFork(repository));
+		
+		assertFalse("user CAN delete!", user.canDelete(repository));
+		assertFalse("user CAN edit!", user.canEdit(repository));
+	}
+	
+	@Test
+	public void testRegexMatching() throws Exception {
+		RepositoryModel repository = new RepositoryModel("ubercool/_my-r/e~po.git", null, null, new Date());
+		repository.authorizationControl = AuthorizationControl.NAMED;
+		repository.accessRestriction = AccessRestrictionType.VIEW;
+
+		UserModel user = new UserModel("test");
+		user.setRepositoryPermission("ubercool/[A-Z0-9-~_\\./]+", AccessPermission.CLONE);
+
+		assertTrue("user DOES NOT HAVE a repository permission!", user.hasRepositoryPermission(repository.name));
+		assertTrue("user CAN NOT view!", user.canView(repository));
+		assertTrue("user CAN NOT clone!", user.canClone(repository));
+		assertFalse("user CAN push!", user.canPush(repository));
+		
+		assertFalse("user CAN create ref!", user.canCreateRef(repository));
+		assertFalse("user CAN delete ref!", user.canDeleteRef(repository));
+		assertFalse("user CAN rewind ref!", user.canRewindRef(repository));
+
+		assertEquals("user has wrong permission!", AccessPermission.CLONE, user.getRepositoryPermission(repository).permission);
+
+		assertFalse("user CAN fork!", user.canFork(repository));
+		
+		assertFalse("user CAN delete!", user.canDelete(repository));
+		assertFalse("user CAN edit!", user.canEdit(repository));
+	}
+
+	@Test
+	public void testRegexIncludeCommonExcludePersonal() throws Exception {
+		
+		UserModel user = new UserModel("test");
+		user.setRepositoryPermission("[^~].*", AccessPermission.CLONE);
+
+		// common
+		RepositoryModel common = new RepositoryModel("ubercool/_my-r/e~po.git", null, null, new Date());
+		common.authorizationControl = AuthorizationControl.NAMED;
+		common.accessRestriction = AccessRestrictionType.VIEW;
+		
+		assertTrue("user DOES NOT HAVE a repository permission!", user.hasRepositoryPermission(common.name));
+		assertTrue("user CAN NOT view!", user.canView(common));
+		assertTrue("user CAN NOT clone!", user.canClone(common));
+		assertFalse("user CAN push!", user.canPush(common));
+		
+		assertFalse("user CAN create ref!", user.canCreateRef(common));
+		assertFalse("user CAN delete ref!", user.canDeleteRef(common));
+		assertFalse("user CAN rewind ref!", user.canRewindRef(common));
+
+		assertEquals("user has wrong permission!", AccessPermission.CLONE, user.getRepositoryPermission(common).permission);
+
+		assertFalse("user CAN fork!", user.canFork(common));
+		
+		assertFalse("user CAN delete!", user.canDelete(common));
+		assertFalse("user CAN edit!", user.canEdit(common));
+
+		// personal
+		RepositoryModel personal = new RepositoryModel("~ubercool/_my-r/e~po.git", null, null, new Date());
+		personal.authorizationControl = AuthorizationControl.NAMED;
+		personal.accessRestriction = AccessRestrictionType.VIEW;
+		
+		assertFalse("user HAS a repository permission!", user.hasRepositoryPermission(personal.name));
+		assertFalse("user CAN NOT view!", user.canView(personal));
+		assertFalse("user CAN NOT clone!", user.canClone(personal));
+		assertFalse("user CAN push!", user.canPush(personal));
+		
+		assertFalse("user CAN create ref!", user.canCreateRef(personal));
+		assertFalse("user CAN delete ref!", user.canDeleteRef(personal));
+		assertFalse("user CAN rewind ref!", user.canRewindRef(personal));
+
+		assertEquals("user has wrong permission!", AccessPermission.NONE, user.getRepositoryPermission(personal).permission);
+
+		assertFalse("user CAN fork!", user.canFork(personal));
+		
+		assertFalse("user CAN delete!", user.canDelete(personal));
+		assertFalse("user CAN edit!", user.canEdit(personal));
+	}
+	
+	@Test
+	public void testRegexMatching2() throws Exception {
+		RepositoryModel personal = new RepositoryModel("~ubercool/_my-r/e~po.git", null, null, new Date());
+		personal.authorizationControl = AuthorizationControl.NAMED;
+		personal.accessRestriction = AccessRestrictionType.VIEW;
+
+		UserModel user = new UserModel("test");
+		// permit all repositories excluding all personal rpeositories
+		user.setRepositoryPermission("[^~].*", AccessPermission.CLONE);
+		// permitall  ~ubercool repositories
+		user.setRepositoryPermission("~ubercool/.*", AccessPermission.CLONE);
+		
+		// personal
+		assertTrue("user DOES NOT HAVE a repository permission!", user.hasRepositoryPermission(personal.name));
+		assertTrue("user CAN NOT view!", user.canView(personal));
+		assertTrue("user CAN NOT clone!", user.canClone(personal));
+		assertFalse("user CAN push!", user.canPush(personal));
+		
+		assertFalse("user CAN create ref!", user.canCreateRef(personal));
+		assertFalse("user CAN delete ref!", user.canDeleteRef(personal));
+		assertFalse("user CAN rewind ref!", user.canRewindRef(personal));
+
+		assertEquals("user has wrong permission!", AccessPermission.CLONE, user.getRepositoryPermission(personal).permission);
+
+		assertFalse("user CAN fork!", user.canFork(personal));
+		
+		assertFalse("user CAN delete!", user.canDelete(personal));
+		assertFalse("user CAN edit!", user.canEdit(personal));
+	}
+	
+	@Test
+	public void testRegexOrder() throws Exception {
+		RepositoryModel personal = new RepositoryModel("~ubercool/_my-r/e~po.git", null, null, new Date());
+		personal.authorizationControl = AuthorizationControl.NAMED;
+		personal.accessRestriction = AccessRestrictionType.VIEW;
+
+		UserModel user = new UserModel("test");
+		user.setRepositoryPermission(".*", AccessPermission.PUSH);
+		user.setRepositoryPermission("~ubercool/.*", AccessPermission.CLONE);
+		
+		// has PUSH access because first match is PUSH permission 
+		assertTrue("user HAS a repository permission!", user.hasRepositoryPermission(personal.name));
+		assertTrue("user CAN NOT view!", user.canView(personal));
+		assertTrue("user CAN NOT clone!", user.canClone(personal));
+		assertTrue("user CAN NOT push!", user.canPush(personal));
+		
+		assertFalse("user CAN create ref!", user.canCreateRef(personal));
+		assertFalse("user CAN delete ref!", user.canDeleteRef(personal));
+		assertFalse("user CAN rewind ref!", user.canRewindRef(personal));
+
+		assertEquals("user has wrong permission!", AccessPermission.PUSH, user.getRepositoryPermission(personal).permission);
+
+		assertFalse("user CAN fork!", user.canFork(personal));
+		
+		assertFalse("user CAN delete!", user.canDelete(personal));
+		assertFalse("user CAN edit!", user.canEdit(personal));
+				
+		user.permissions.clear();
+		user.setRepositoryPermission("~ubercool/.*", AccessPermission.CLONE);
+		user.setRepositoryPermission(".*", AccessPermission.PUSH);
+		
+		// has CLONE access because first match is CLONE permission
+		assertTrue("user HAS a repository permission!", user.hasRepositoryPermission(personal.name));
+		assertTrue("user CAN NOT view!", user.canView(personal));
+		assertTrue("user CAN NOT clone!", user.canClone(personal));
+		assertFalse("user CAN push!", user.canPush(personal));
+				
+		assertFalse("user CAN create ref!", user.canCreateRef(personal));
+		assertFalse("user CAN delete ref!", user.canDeleteRef(personal));
+		assertFalse("user CAN rewind ref!", user.canRewindRef(personal));
+
+		assertEquals("user has wrong permission!", AccessPermission.CLONE, user.getRepositoryPermission(personal).permission);
+
+		assertFalse("user CAN fork!", user.canFork(personal));
+				
+		assertFalse("user CAN delete!", user.canDelete(personal));
+		assertFalse("user CAN edit!", user.canEdit(personal));
+	}
+	
+	@Test
+	public void testExclusion() throws Exception {
+		RepositoryModel personal = new RepositoryModel("~ubercool/_my-r/e~po.git", null, null, new Date());
+		personal.authorizationControl = AuthorizationControl.NAMED;
+		personal.accessRestriction = AccessRestrictionType.VIEW;
+
+		UserModel user = new UserModel("test");
+		user.setRepositoryPermission("~ubercool/.*", AccessPermission.EXCLUDE);
+		user.setRepositoryPermission(".*", AccessPermission.PUSH);
+		
+		// has EXCLUDE access because first match is EXCLUDE permission
+		assertTrue("user DOES NOT HAVE a repository permission!", user.hasRepositoryPermission(personal.name));
+		assertFalse("user CAN NOT view!", user.canView(personal));
+		assertFalse("user CAN NOT clone!", user.canClone(personal));
+		assertFalse("user CAN push!", user.canPush(personal));
+				
+		assertFalse("user CAN create ref!", user.canCreateRef(personal));
+		assertFalse("user CAN delete ref!", user.canDeleteRef(personal));
+		assertFalse("user CAN rewind ref!", user.canRewindRef(personal));
+
+		assertEquals("user has wrong permission!", AccessPermission.EXCLUDE, user.getRepositoryPermission(personal).permission);
+
+		assertFalse("user CAN fork!", user.canFork(personal));
+				
+		assertFalse("user CAN delete!", user.canDelete(personal));
+		assertFalse("user CAN edit!", user.canEdit(personal));
+	}
+
+	@Test
+	public void testAdminTeamInheritance() throws Exception {
+		UserModel user = new UserModel("test");
+		TeamModel team = new TeamModel("team");
+		team.canAdmin = true;
+		user.teams.add(team);
+		assertTrue("User did not inherit admin privileges", user.canAdmin());
+	}
+	
+	@Test
+	public void testForkTeamInheritance() throws Exception {
+		UserModel user = new UserModel("test");
+		TeamModel team = new TeamModel("team");
+		team.canFork = true;
+		user.teams.add(team);
+		assertTrue("User did not inherit fork privileges", user.canFork());
+	}
+
+	@Test
+	public void testCreateTeamInheritance() throws Exception {
+		UserModel user = new UserModel("test");
+		TeamModel team = new TeamModel("team");
+		team.canCreate= true;
+		user.teams.add(team);
+		assertTrue("User did not inherit create privileges", user.canCreate());
+	}
+
+	@Test
+	public void testIsFrozen() throws Exception {
+		RepositoryModel repo = new RepositoryModel("somerepo.git", null, null, new Date());
+		repo.authorizationControl = AuthorizationControl.NAMED;
+		repo.accessRestriction = AccessRestrictionType.NONE;
+
+		UserModel user = new UserModel("test");
+		TeamModel team = new TeamModel("team");
+
+		assertEquals("user has wrong permission!", AccessPermission.REWIND, user.getRepositoryPermission(repo).permission);
+		assertEquals("team has wrong permission!", AccessPermission.REWIND, team.getRepositoryPermission(repo).permission);
+		
+		// freeze repo
+		repo.isFrozen = true;
+		assertEquals("user has wrong permission!", AccessPermission.CLONE, user.getRepositoryPermission(repo).permission);
+		assertEquals("team has wrong permission!", AccessPermission.CLONE, team.getRepositoryPermission(repo).permission);
+	}
+	
+	@Test
+	public void testIsBare() throws Exception {
+		RepositoryModel repo = new RepositoryModel("somerepo.git", null, null, new Date());
+		repo.authorizationControl = AuthorizationControl.NAMED;
+		repo.accessRestriction = AccessRestrictionType.NONE;
+
+		UserModel user = new UserModel("test");
+		TeamModel team = new TeamModel("team");
+
+		assertEquals("user has wrong permission!", AccessPermission.REWIND, user.getRepositoryPermission(repo).permission);
+		assertEquals("team has wrong permission!", AccessPermission.REWIND, team.getRepositoryPermission(repo).permission);
+		
+		// set repo to have a working copy, pushes prohibited
+		repo.isBare = false;
+		assertEquals("user has wrong permission!", AccessPermission.CLONE, user.getRepositoryPermission(repo).permission);
+		assertEquals("team has wrong permission!", AccessPermission.CLONE, team.getRepositoryPermission(repo).permission);
+	}
+}
diff --git a/src/test/java/com/gitblit/tests/PushLogTest.java b/src/test/java/com/gitblit/tests/PushLogTest.java
new file mode 100644
index 0000000..f5d5965
--- /dev/null
+++ b/src/test/java/com/gitblit/tests/PushLogTest.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2013 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit.tests;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.RepositoryCache.FileKey;
+import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
+import org.eclipse.jgit.util.FS;
+import org.junit.Test;
+
+import com.gitblit.models.RefLogEntry;
+import com.gitblit.utils.RefLogUtils;
+
+public class PushLogTest {
+
+	@Test
+	public void testPushLog() throws IOException {
+		String name = "~james/helloworld.git";
+		File gitDir = FileKey.resolve(new File(GitBlitSuite.REPOSITORIES, name), FS.DETECTED);
+		Repository repository = new FileRepositoryBuilder().setGitDir(gitDir).build();
+		List<RefLogEntry> pushes = RefLogUtils.getRefLog(name, repository);
+		GitBlitSuite.close(repository);
+	}
+}
\ No newline at end of file
diff --git a/tests/com/gitblit/tests/RedmineUserServiceTest.java b/src/test/java/com/gitblit/tests/RedmineUserServiceTest.java
similarity index 100%
rename from tests/com/gitblit/tests/RedmineUserServiceTest.java
rename to src/test/java/com/gitblit/tests/RedmineUserServiceTest.java
diff --git a/tests/com/gitblit/tests/RepositoryModelTest.java b/src/test/java/com/gitblit/tests/RepositoryModelTest.java
similarity index 100%
rename from tests/com/gitblit/tests/RepositoryModelTest.java
rename to src/test/java/com/gitblit/tests/RepositoryModelTest.java
diff --git a/src/test/java/com/gitblit/tests/RpcTests.java b/src/test/java/com/gitblit/tests/RpcTests.java
new file mode 100644
index 0000000..bd7f277
--- /dev/null
+++ b/src/test/java/com/gitblit/tests/RpcTests.java
@@ -0,0 +1,390 @@
+/*
+ * Copyright 2011 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit.tests;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import com.gitblit.Constants.AccessPermission;
+import com.gitblit.Constants.AccessRestrictionType;
+import com.gitblit.Constants.AuthorizationControl;
+import com.gitblit.Constants.PermissionType;
+import com.gitblit.Constants.RegistrantType;
+import com.gitblit.GitBlitException.UnauthorizedException;
+import com.gitblit.Keys;
+import com.gitblit.RpcServlet;
+import com.gitblit.models.FederationModel;
+import com.gitblit.models.FederationProposal;
+import com.gitblit.models.FederationSet;
+import com.gitblit.models.RegistrantAccessPermission;
+import com.gitblit.models.RepositoryModel;
+import com.gitblit.models.ServerSettings;
+import com.gitblit.models.ServerStatus;
+import com.gitblit.models.TeamModel;
+import com.gitblit.models.UserModel;
+import com.gitblit.utils.RpcUtils;
+
+/**
+ * Tests all the rpc client utility methods, the rpc filter and rpc servlet.
+ * 
+ * @author James Moger
+ * 
+ */
+public class RpcTests {
+
+	String url = GitBlitSuite.url;
+	String account = GitBlitSuite.account;
+	String password = GitBlitSuite.password;
+
+	private static final AtomicBoolean started = new AtomicBoolean(false);
+
+	@BeforeClass
+	public static void startGitblit() throws Exception {
+		started.set(GitBlitSuite.startGitblit());
+	}
+
+	@AfterClass
+	public static void stopGitblit() throws Exception {
+		if (started.get()) {
+			GitBlitSuite.stopGitblit();
+		}
+	}
+
+	@Test
+	public void testGetProtocolVersion() throws IOException {
+		int protocol = RpcUtils.getProtocolVersion(url, null, null);
+		assertEquals(RpcServlet.PROTOCOL_VERSION, protocol);
+	}
+
+	@Test
+	public void testListRepositories() throws IOException {
+		Map<String, RepositoryModel> map = RpcUtils.getRepositories(url, null, null);
+		assertNotNull("Repository list is null!", map);
+		assertTrue("Repository list is empty!", map.size() > 0);
+	}
+
+	@Test
+	public void testListUsers() throws IOException {
+		List<UserModel> list = null;
+		try {
+			list = RpcUtils.getUsers(url, null, null);
+		} catch (UnauthorizedException e) {
+		}
+		assertNull("Server allows anyone to admin!", list);
+
+		list = RpcUtils.getUsers(url, "admin", "admin".toCharArray());
+		assertTrue("User list is empty!", list.size() > 0);
+	}
+
+	@Test
+	public void testListTeams() throws IOException {
+		List<TeamModel> list = null;
+		try {
+			list = RpcUtils.getTeams(url, null, null);
+		} catch (UnauthorizedException e) {
+		}
+		assertNull("Server allows anyone to admin!", list);
+
+		list = RpcUtils.getTeams(url, "admin", "admin".toCharArray());
+		assertTrue("Team list is empty!", list.size() > 0);
+		assertEquals("admins", list.get(0).name);
+	}
+
+	@Test
+	public void testUserAdministration() throws IOException {
+		UserModel user = new UserModel("garbage");
+		user.canAdmin = true;
+		user.password = "whocares";
+
+		// create
+		assertTrue("Failed to create user!",
+				RpcUtils.createUser(user, url, account, password.toCharArray()));
+
+		UserModel retrievedUser = findUser(user.username);
+		assertNotNull("Failed to find " + user.username, retrievedUser);
+		assertTrue("Retrieved user can not administer Gitblit", retrievedUser.canAdmin);
+
+		// rename and toggle admin permission
+		String originalName = user.username;
+		user.username = "garbage2";
+		user.canAdmin = false;
+		assertTrue("Failed to update user!",
+				RpcUtils.updateUser(originalName, user, url, account, password.toCharArray()));
+
+		retrievedUser = findUser(user.username);
+		assertNotNull("Failed to find " + user.username, retrievedUser);
+		assertTrue("Retrieved user did not update", !retrievedUser.canAdmin);
+
+		// delete
+		assertTrue("Failed to delete " + user.username,
+				RpcUtils.deleteUser(retrievedUser, url, account, password.toCharArray()));
+
+		retrievedUser = findUser(user.username);
+		assertNull("Failed to delete " + user.username, retrievedUser);
+	}
+
+	private UserModel findUser(String name) throws IOException {
+		List<UserModel> users = RpcUtils.getUsers(url, account, password.toCharArray());
+		UserModel retrievedUser = null;
+		for (UserModel model : users) {
+			if (model.username.equalsIgnoreCase(name)) {
+				retrievedUser = model;
+				break;
+			}
+		}
+		return retrievedUser;
+	}
+
+	@Test
+	public void testRepositoryAdministration() throws IOException {
+		RepositoryModel model = new RepositoryModel();
+		model.name = "garbagerepo.git";
+		model.description = "created by RpcUtils";
+		model.addOwner("garbage");
+		model.accessRestriction = AccessRestrictionType.VIEW;
+		model.authorizationControl = AuthorizationControl.AUTHENTICATED;
+
+		// create
+		RpcUtils.deleteRepository(model, url, account, password.toCharArray());
+		assertTrue("Failed to create repository!",
+				RpcUtils.createRepository(model, url, account, password.toCharArray()));
+
+		RepositoryModel retrievedRepository = findRepository(model.name);
+		assertNotNull("Failed to find " + model.name, retrievedRepository);
+		assertEquals(AccessRestrictionType.VIEW, retrievedRepository.accessRestriction);
+		assertEquals(AuthorizationControl.AUTHENTICATED, retrievedRepository.authorizationControl);
+
+		// rename and change access restriciton
+		String originalName = model.name;
+		model.name = "garbagerepo2.git";
+		model.accessRestriction = AccessRestrictionType.CLONE;
+		model.authorizationControl = AuthorizationControl.NAMED;
+		RpcUtils.deleteRepository(model, url, account, password.toCharArray());
+		assertTrue("Failed to update repository!", RpcUtils.updateRepository(originalName, model,
+				url, account, password.toCharArray()));
+
+		retrievedRepository = findRepository(model.name);
+		assertNotNull("Failed to find " + model.name, retrievedRepository);
+		assertTrue("Access retriction type is wrong",
+				AccessRestrictionType.CLONE.equals(retrievedRepository.accessRestriction));
+
+		// restore VIEW restriction
+		retrievedRepository.accessRestriction = AccessRestrictionType.VIEW;
+		assertTrue("Failed to update repository!", RpcUtils.updateRepository(retrievedRepository.name, retrievedRepository,
+				url, account, password.toCharArray()));
+		retrievedRepository = findRepository(retrievedRepository.name);
+		
+		// memberships
+		UserModel testMember = new UserModel("justadded");
+		assertTrue(RpcUtils.createUser(testMember, url, account, password.toCharArray()));
+
+		List<RegistrantAccessPermission> permissions = RpcUtils.getRepositoryMemberPermissions(retrievedRepository, url, account,
+				password.toCharArray());
+		assertEquals("Unexpected permissions! " + permissions.toString(), 1, permissions.size());
+		permissions.add(new RegistrantAccessPermission(testMember.username, AccessPermission.VIEW, PermissionType.EXPLICIT, RegistrantType.USER, null, true));
+		assertTrue(
+				"Failed to set member permissions!",
+				RpcUtils.setRepositoryMemberPermissions(retrievedRepository, permissions, url, account,
+						password.toCharArray()));
+		permissions = RpcUtils.getRepositoryMemberPermissions(retrievedRepository, url, account,
+				password.toCharArray());
+		boolean foundMember = false;
+		for (RegistrantAccessPermission permission : permissions) {
+			if (permission.registrant.equalsIgnoreCase(testMember.username)) {
+				foundMember = true;
+				assertEquals(AccessPermission.VIEW, permission.permission);
+				break;
+			}
+		}
+		assertTrue("Failed to find member!", foundMember);
+
+		// delete
+		assertTrue("Failed to delete " + model.name, RpcUtils.deleteRepository(retrievedRepository,
+				url, account, password.toCharArray()));
+
+		retrievedRepository = findRepository(model.name);
+		assertNull("Failed to delete " + model.name, retrievedRepository);
+
+		for (UserModel u : RpcUtils.getUsers(url, account, password.toCharArray())) {
+			if (u.username.equals(testMember.username)) {
+				assertTrue(RpcUtils.deleteUser(u, url, account, password.toCharArray()));
+				break;
+			}
+		}
+	}
+
+	private RepositoryModel findRepository(String name) throws IOException {
+		Map<String, RepositoryModel> repositories = RpcUtils.getRepositories(url, account,
+				password.toCharArray());
+		RepositoryModel retrievedRepository = null;
+		for (RepositoryModel model : repositories.values()) {
+			if (model.name.equalsIgnoreCase(name)) {
+				retrievedRepository = model;
+				break;
+			}
+		}
+		return retrievedRepository;
+	}
+
+	@Test
+	public void testTeamAdministration() throws IOException {
+		List<TeamModel> teams = RpcUtils.getTeams(url, account, password.toCharArray());
+		assertEquals(1, teams.size());
+		
+		// Create the A-Team
+		TeamModel aTeam = new TeamModel("A-Team");
+		aTeam.users.add("admin");
+		aTeam.addRepositoryPermission("helloworld.git");
+		assertTrue(RpcUtils.createTeam(aTeam, url, account, password.toCharArray()));
+
+		aTeam = null;
+		teams = RpcUtils.getTeams(url, account, password.toCharArray());
+		assertEquals(2, teams.size());
+		for (TeamModel team : teams) {
+			if (team.name.equals("A-Team")) {
+				aTeam = team;
+				break;
+			}
+		}
+		assertNotNull(aTeam);
+		assertTrue(aTeam.hasUser("admin"));
+		assertTrue(aTeam.hasRepositoryPermission("helloworld.git"));
+
+		RepositoryModel helloworld = null;
+		Map<String, RepositoryModel> repositories = RpcUtils.getRepositories(url, account,
+				password.toCharArray());
+		for (RepositoryModel repository : repositories.values()) {
+			if (repository.name.equals("helloworld.git")) {
+				helloworld = repository;
+				break;
+			}
+		}
+		assertNotNull(helloworld);
+		
+		// Confirm that we have added the team
+		List<String> helloworldTeams = RpcUtils.getRepositoryTeams(helloworld, url, account,
+				password.toCharArray());
+		assertEquals(1, helloworldTeams.size());
+		assertTrue(helloworldTeams.contains(aTeam.name));
+
+		// set no teams
+		List<RegistrantAccessPermission> permissions = new ArrayList<RegistrantAccessPermission>();
+		for (String team : helloworldTeams) {
+			permissions.add(new RegistrantAccessPermission(team, AccessPermission.NONE, PermissionType.EXPLICIT, RegistrantType.TEAM, null, true));
+		}
+		assertTrue(RpcUtils.setRepositoryTeamPermissions(helloworld, permissions, url, account,
+				password.toCharArray()));
+		helloworldTeams = RpcUtils.getRepositoryTeams(helloworld, url, account,
+				password.toCharArray());
+		assertEquals(0, helloworldTeams.size());
+		
+		// delete the A-Team
+		assertTrue(RpcUtils.deleteTeam(aTeam, url, account, password.toCharArray()));
+
+		teams = RpcUtils.getTeams(url, account, password.toCharArray());
+		assertEquals(1, teams.size());
+	}
+
+	@Test
+	public void testFederationRegistrations() throws Exception {
+		List<FederationModel> registrations = RpcUtils.getFederationRegistrations(url, account,
+				password.toCharArray());
+		assertTrue("No federation registrations were retrieved!", registrations.size() >= 0);
+	}
+
+	@Test
+	public void testFederationResultRegistrations() throws Exception {
+		List<FederationModel> registrations = RpcUtils.getFederationResultRegistrations(url,
+				account, password.toCharArray());
+		assertTrue("No federation result registrations were retrieved!", registrations.size() >= 0);
+	}
+
+	@Test
+	public void testFederationProposals() throws Exception {
+		List<FederationProposal> proposals = RpcUtils.getFederationProposals(url, account,
+				password.toCharArray());
+		assertTrue("No federation proposals were retrieved!", proposals.size() >= 0);
+	}
+
+	@Test
+	public void testFederationSets() throws Exception {
+		List<FederationSet> sets = RpcUtils.getFederationSets(url, account, password.toCharArray());
+		assertTrue("No federation sets were retrieved!", sets.size() >= 0);
+	}
+
+	@Test
+	public void testSettings() throws Exception {
+		ServerSettings settings = RpcUtils.getSettings(url, account, password.toCharArray());
+		assertNotNull("No settings were retrieved!", settings);
+	}
+
+	@Test
+	public void testServerStatus() throws Exception {
+		ServerStatus status = RpcUtils.getStatus(url, account, password.toCharArray());
+		assertNotNull("No status was retrieved!", status);
+	}
+
+	@Test
+	public void testUpdateSettings() throws Exception {
+		Map<String, String> updated = new HashMap<String, String>();
+
+		// grab current setting
+		ServerSettings settings = RpcUtils.getSettings(url, account, password.toCharArray());
+		boolean showSizes = settings.get(Keys.web.showRepositorySizes).getBoolean(true);
+		showSizes = !showSizes;
+
+		// update setting
+		updated.put(Keys.web.showRepositorySizes, String.valueOf(showSizes));
+		boolean success = RpcUtils.updateSettings(updated, url, account, password.toCharArray());
+		assertTrue("Failed to update server settings", success);
+
+		// confirm setting change
+		settings = RpcUtils.getSettings(url, account, password.toCharArray());
+		boolean newValue = settings.get(Keys.web.showRepositorySizes).getBoolean(false);
+		assertEquals(newValue, showSizes);
+
+		// restore setting
+		newValue = !newValue;
+		updated.put(Keys.web.showRepositorySizes, String.valueOf(newValue));
+		success = RpcUtils.updateSettings(updated, url, account, password.toCharArray());
+		assertTrue("Failed to update server settings", success);
+		settings = RpcUtils.getSettings(url, account, password.toCharArray());
+		showSizes = settings.get(Keys.web.showRepositorySizes).getBoolean(true);
+		assertEquals(newValue, showSizes);
+	}
+
+	@Test
+	public void testBranches() throws Exception {
+		Map<String, Collection<String>> branches = RpcUtils.getBranches(url, account,
+				password.toCharArray());
+		assertNotNull(branches);
+		assertTrue(branches.size() > 0);
+	}
+}
diff --git a/tests/com/gitblit/tests/StringUtilsTest.java b/src/test/java/com/gitblit/tests/StringUtilsTest.java
similarity index 100%
rename from tests/com/gitblit/tests/StringUtilsTest.java
rename to src/test/java/com/gitblit/tests/StringUtilsTest.java
diff --git a/tests/com/gitblit/tests/SyndicationUtilsTest.java b/src/test/java/com/gitblit/tests/SyndicationUtilsTest.java
similarity index 100%
rename from tests/com/gitblit/tests/SyndicationUtilsTest.java
rename to src/test/java/com/gitblit/tests/SyndicationUtilsTest.java
diff --git a/tests/com/gitblit/tests/TicgitUtilsTest.java b/src/test/java/com/gitblit/tests/TicgitUtilsTest.java
similarity index 100%
rename from tests/com/gitblit/tests/TicgitUtilsTest.java
rename to src/test/java/com/gitblit/tests/TicgitUtilsTest.java
diff --git a/src/test/java/com/gitblit/tests/TimeUtilsTest.java b/src/test/java/com/gitblit/tests/TimeUtilsTest.java
new file mode 100644
index 0000000..851fb45
--- /dev/null
+++ b/src/test/java/com/gitblit/tests/TimeUtilsTest.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2011 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit.tests;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Date;
+
+import org.junit.Test;
+
+import com.gitblit.utils.TimeUtils;
+
+public class TimeUtilsTest {
+
+	private Date offset(long subtract) {
+		return new Date(System.currentTimeMillis() - subtract);
+	}
+
+	@Test
+	public void testBasicTimeFunctions() throws Exception {
+		assertEquals(2, TimeUtils.minutesAgo(offset(2 * TimeUtils.MIN), false));
+		assertEquals(3, TimeUtils.minutesAgo(offset((2 * TimeUtils.MIN) + (35 * 1000L)), true));
+
+		assertEquals(2, TimeUtils.hoursAgo(offset(2 * TimeUtils.ONEHOUR), false));
+		assertEquals(3, TimeUtils.hoursAgo(offset(5 * TimeUtils.HALFHOUR), true));
+
+		assertEquals(4, TimeUtils.daysAgo(offset(4 * TimeUtils.ONEDAY)));
+	}
+
+	@Test
+	public void testToday() throws Exception {
+		assertTrue(TimeUtils.isToday(new Date(), null));
+	}
+
+	@Test
+	public void testYesterday() throws Exception {
+		assertTrue(TimeUtils.isYesterday(offset(TimeUtils.ONEDAY), null));
+	}
+
+	@Test
+	public void testDurations() throws Exception {
+		TimeUtils timeUtils = new TimeUtils();
+		assertEquals("1 day", timeUtils.duration(1));
+		assertEquals("5 days", timeUtils.duration(5));
+		assertEquals("3 months", timeUtils.duration(75));
+		assertEquals("12 months", timeUtils.duration(364));
+		assertEquals("1 year", timeUtils.duration(365 + 0));
+		assertEquals("1 year", timeUtils.duration(365 + 10));
+		assertEquals("1 year, 1 month", timeUtils.duration(365 + 15));
+		assertEquals("1 year, 1 month", timeUtils.duration(365 + 30));
+		assertEquals("1 year, 1 month", timeUtils.duration(365 + 44));
+		assertEquals("1 year, 2 months", timeUtils.duration(365 + 45));
+		assertEquals("1 year, 2 months", timeUtils.duration(365 + 60));
+
+		assertEquals("2 years", timeUtils.duration(2 * 365 + 0));
+		assertEquals("2 years", timeUtils.duration(2 * 365 + 10));
+		assertEquals("2 years, 1 month", timeUtils.duration(2 * 365 + 15));
+		assertEquals("2 years, 1 month", timeUtils.duration(2 * 365 + 30));
+		assertEquals("2 years, 1 month", timeUtils.duration(2 * 365 + 44));
+		assertEquals("2 years, 2 months", timeUtils.duration(2 * 365 + 45));
+		assertEquals("2 years, 2 months", timeUtils.duration(2 * 365 + 60));
+	}
+
+	@Test
+	public void testTimeAgo() throws Exception {
+		// standard time ago tests
+		TimeUtils timeUtils = new TimeUtils();
+		assertEquals("just now", timeUtils.timeAgo(offset(1 * TimeUtils.MIN)));
+		assertEquals("60 mins ago", timeUtils.timeAgo(offset(60 * TimeUtils.MIN)));
+		assertEquals("2 hours ago", timeUtils.timeAgo(offset(120 * TimeUtils.MIN)));
+		assertEquals("15 hours ago", timeUtils.timeAgo(offset(15 * TimeUtils.ONEHOUR)));
+		assertEquals("yesterday", timeUtils.timeAgo(offset(24 * TimeUtils.ONEHOUR)));
+		assertEquals("2 days ago", timeUtils.timeAgo(offset(2 * TimeUtils.ONEDAY)));
+		assertEquals("5 weeks ago", timeUtils.timeAgo(offset(35 * TimeUtils.ONEDAY)));
+		assertEquals("3 months ago", timeUtils.timeAgo(offset(84 * TimeUtils.ONEDAY)));
+		assertEquals("3 months ago", timeUtils.timeAgo(offset(95 * TimeUtils.ONEDAY)));
+		assertEquals("4 months ago", timeUtils.timeAgo(offset(104 * TimeUtils.ONEDAY)));
+		assertEquals("1 year ago", timeUtils.timeAgo(offset(365 * TimeUtils.ONEDAY)));
+		assertEquals("13 months ago", timeUtils.timeAgo(offset(395 * TimeUtils.ONEDAY)));
+		assertEquals("2 years ago", timeUtils.timeAgo(offset((2 * 365 + 30) * TimeUtils.ONEDAY)));
+
+		// css class tests
+		assertEquals("age0", timeUtils.timeAgoCss(offset(1 * TimeUtils.MIN)));
+		assertEquals("age0", timeUtils.timeAgoCss(offset(60 * TimeUtils.MIN)));
+		assertEquals("age1", timeUtils.timeAgoCss(offset(120 * TimeUtils.MIN)));
+		assertEquals("age1", timeUtils.timeAgoCss(offset(24 * TimeUtils.ONEHOUR)));
+		assertEquals("age2", timeUtils.timeAgoCss(offset(2 * TimeUtils.ONEDAY)));
+	}
+
+	@Test
+	public void testFrequency() {
+		assertEquals(5, TimeUtils.convertFrequencyToMinutes("2 mins"));
+		assertEquals(10, TimeUtils.convertFrequencyToMinutes("10 mins"));
+		assertEquals(600, TimeUtils.convertFrequencyToMinutes("10 hours"));
+		assertEquals(14400, TimeUtils.convertFrequencyToMinutes(" 10 days "));
+	}
+}
diff --git a/tests/com/gitblit/tests/UserServiceTest.java b/src/test/java/com/gitblit/tests/UserServiceTest.java
similarity index 100%
rename from tests/com/gitblit/tests/UserServiceTest.java
rename to src/test/java/com/gitblit/tests/UserServiceTest.java
diff --git a/tests/com/gitblit/tests/X509UtilsTest.java b/src/test/java/com/gitblit/tests/X509UtilsTest.java
similarity index 100%
rename from tests/com/gitblit/tests/X509UtilsTest.java
rename to src/test/java/com/gitblit/tests/X509UtilsTest.java
diff --git a/tests/com/gitblit/tests/mock/MemorySettings.java b/src/test/java/com/gitblit/tests/mock/MemorySettings.java
similarity index 100%
rename from tests/com/gitblit/tests/mock/MemorySettings.java
rename to src/test/java/com/gitblit/tests/mock/MemorySettings.java
diff --git a/tests/com/gitblit/tests/resources/ldapUserServiceSampleData.ldif b/src/test/java/com/gitblit/tests/resources/ldapUserServiceSampleData.ldif
similarity index 100%
rename from tests/com/gitblit/tests/resources/ldapUserServiceSampleData.ldif
rename to src/test/java/com/gitblit/tests/resources/ldapUserServiceSampleData.ldif
diff --git a/src/test/java/de/akquinet/devops/GitBlit4UITests.java b/src/test/java/de/akquinet/devops/GitBlit4UITests.java
new file mode 100644
index 0000000..966d72b
--- /dev/null
+++ b/src/test/java/de/akquinet/devops/GitBlit4UITests.java
@@ -0,0 +1,25 @@
+package de.akquinet.devops;
+
+import java.util.concurrent.TimeUnit;
+
+import com.gitblit.GitBlit;
+
+public class GitBlit4UITests extends GitBlit {
+
+	private boolean luceneIndexingEnabled;
+
+	public GitBlit4UITests(boolean luceneIndexingEnabled) {
+		this.luceneIndexingEnabled = luceneIndexingEnabled;
+	}
+
+	@Override
+	protected void configureLuceneIndexing() {
+		if (luceneIndexingEnabled) {
+			getScheduledExecutor().scheduleAtFixedRate(getLuceneExecutor(), 1,
+					2, TimeUnit.MINUTES);
+			getLogger()
+					.info("Lucene executor is scheduled to process indexed branches every 2 minutes.");
+		}
+	}
+	
+}
diff --git a/src/test/java/de/akquinet/devops/GitBlitServer4UITests.java b/src/test/java/de/akquinet/devops/GitBlitServer4UITests.java
new file mode 100644
index 0000000..2d54be2
--- /dev/null
+++ b/src/test/java/de/akquinet/devops/GitBlitServer4UITests.java
@@ -0,0 +1,62 @@
+package de.akquinet.devops;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import com.beust.jcommander.JCommander;
+import com.beust.jcommander.ParameterException;
+import com.gitblit.GitBlit;
+import com.gitblit.GitBlitServer;
+
+public class GitBlitServer4UITests extends GitBlitServer {
+
+	public static void main(String... args) {
+		GitBlitServer4UITests server = new GitBlitServer4UITests();
+
+		// filter out the baseFolder parameter
+		List<String> filtered = new ArrayList<String>();
+		String folder = "data";
+		for (int i = 0; i < args.length; i++) {
+			String arg = args[i];
+			if (arg.equals("--baseFolder")) {
+				if (i + 1 == args.length) {
+					System.out.println("Invalid --baseFolder parameter!");
+					System.exit(-1);
+				} else if (args[i + 1] != ".") {
+					folder = args[i + 1];
+				}
+				i = i + 1;
+			} else {
+				filtered.add(arg);
+			}
+		}
+
+		Params.baseFolder = folder;
+		Params params = new Params();
+		JCommander jc = new JCommander(params);
+		try {
+			jc.parse(filtered.toArray(new String[filtered.size()]));
+			if (params.help) {
+				server.usage(jc, null);
+			}
+		} catch (ParameterException t) {
+			server.usage(jc, t);
+		}
+
+		if (params.stop) {
+			server.stop(params);
+		} else {
+			server.start(params);
+		}
+	}
+
+	private GitBlit4UITests instance;
+
+	@Override
+	protected GitBlit getGitBlitInstance() {
+		if (instance == null) {
+			instance = new GitBlit4UITests(false);
+		}
+		return instance;
+	}
+}
diff --git a/src/test/java/de/akquinet/devops/GitblitRunnable.java b/src/test/java/de/akquinet/devops/GitblitRunnable.java
new file mode 100644
index 0000000..32983d8
--- /dev/null
+++ b/src/test/java/de/akquinet/devops/GitblitRunnable.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright 2013 akquinet tech@spree GmbH
+ *
+ * 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 de.akquinet.devops;
+
+import java.net.InetAddress;
+import java.net.ServerSocket;
+
+import com.gitblit.tests.GitBlitSuite;
+
+/**
+ * This is a runnable implementation, that is used to run a gitblit server in a
+ * separate thread (e.g. alongside test cases)
+ * 
+ * @author saheba
+ * 
+ */
+public class GitblitRunnable implements Runnable {
+
+	private int httpPort, httpsPort, shutdownPort;
+	private String userPropertiesPath, gitblitPropertiesPath;
+	private boolean startFailed = false;
+
+	/**
+	 * constructor with reduced set of start params
+	 * 
+	 * @param httpPort
+	 * @param httpsPort
+	 * @param shutdownPort
+	 * @param gitblitPropertiesPath
+	 * @param userPropertiesPath
+	 */
+	public GitblitRunnable(int httpPort, int httpsPort, int shutdownPort,
+			String gitblitPropertiesPath, String userPropertiesPath) {
+		this.httpPort = httpPort;
+		this.httpsPort = httpsPort;
+		this.shutdownPort = shutdownPort;
+		this.userPropertiesPath = userPropertiesPath;
+		this.gitblitPropertiesPath = gitblitPropertiesPath;
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see java.lang.Runnable#run()
+	 */
+	public void run() {
+		boolean portsFree = false;
+		long lastRun = -1;
+		while (!portsFree) {
+			long current = System.currentTimeMillis();
+			if (lastRun == -1 || lastRun + 100 < current) {
+				portsFree = areAllPortsFree(new int[] { httpPort, httpsPort,
+						shutdownPort }, "127.0.0.1");
+			}
+			lastRun = current;
+
+		}
+		try {
+			GitBlitServer4UITests.main("--httpPort", "" + httpPort, "--httpsPort", ""
+					+ httpsPort, "--shutdownPort", "" + shutdownPort,
+					"--repositoriesFolder",
+					"\"" + GitBlitSuite.REPOSITORIES.getAbsolutePath() + "\"",
+					"--userService", userPropertiesPath, "--settings",
+					gitblitPropertiesPath);
+			setStartFailed(false);
+		} catch (Exception iex) {
+			System.out.println("Gitblit server start failed");
+			setStartFailed(true);
+		}
+	}
+
+	/**
+	 * Method used to ensure that all ports are free, if the runnable is used
+	 * JUnit test classes. Be aware that JUnit's setUpClass and tearDownClass
+	 * methods, which are executed before and after a test class (consisting of
+	 * several test cases), may be executed parallely if they are part of a test
+	 * suite consisting of several test classes. Therefore the run method of
+	 * this class calls areAllPortsFree to check port availability before
+	 * starting another gitblit instance.
+	 * 
+	 * @param ports
+	 * @param inetAddress
+	 * @return
+	 */
+	public static boolean areAllPortsFree(int[] ports, String inetAddress) {
+		System.out
+				.println("\n"
+						+ System.currentTimeMillis()
+						+ " ----------------------------------- testing if all ports are free ...");
+		String blockedPorts = "";
+		for (int i = 0; i < ports.length; i++) {
+			ServerSocket s;
+			try {
+				s = new ServerSocket(ports[i], 1,
+						InetAddress.getByName(inetAddress));
+				s.close();
+			} catch (Exception e) {
+				if (!blockedPorts.equals("")) {
+					blockedPorts += ", ";
+				}
+			}
+		}
+		if (blockedPorts.equals("")) {
+			System.out
+					.println(" ----------------------------------- ... verified");
+			return true;
+		}
+		System.out.println(" ----------------------------------- ... "
+				+ blockedPorts + " are still blocked");
+		return false;
+	}
+
+	private void setStartFailed(boolean startFailed) {
+		this.startFailed = startFailed;
+	}
+
+	public boolean isStartFailed() {
+		return startFailed;
+	}
+}
diff --git a/src/test/java/de/akquinet/devops/LaunchWithUITestConfig.java b/src/test/java/de/akquinet/devops/LaunchWithUITestConfig.java
new file mode 100644
index 0000000..d144a24
--- /dev/null
+++ b/src/test/java/de/akquinet/devops/LaunchWithUITestConfig.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2013 akquinet tech@spree GmbH
+ *
+ * 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 de.akquinet.devops;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.net.UnknownHostException;
+
+import junit.framework.Assert;
+
+import org.junit.Test;
+
+import com.gitblit.Constants;
+
+/**
+ * This test checks if it is possible to run two server instances in the same
+ * JVM sequentially
+ * 
+ * @author saheba
+ * 
+ */
+public class LaunchWithUITestConfig {
+
+	@Test
+	public void testSequentialLaunchOfSeveralInstances()
+			throws InterruptedException {
+		// different ports than in testParallelLaunchOfSeveralInstances to
+		// ensure that both test cases do not affect each others test results
+		int httpPort = 9191, httpsPort = 9292, shutdownPort = 9393;
+		String gitblitPropertiesPath = "src/test/config/test-ui-gitblit.properties",
+				usersPropertiesPath = "src/test/config/test-ui-users.conf";
+
+		GitblitRunnable gitblitRunnable = new GitblitRunnable(httpPort,
+				httpsPort, shutdownPort, gitblitPropertiesPath,
+				usersPropertiesPath);
+		Thread serverThread = new Thread(gitblitRunnable);
+		serverThread.start();
+		Thread.sleep(2000);
+		Assert.assertFalse(gitblitRunnable.isStartFailed());
+		LaunchWithUITestConfig.shutdownGitBlitServer(shutdownPort);
+
+		Thread.sleep(5000);
+
+		GitblitRunnable gitblitRunnable2 = new GitblitRunnable(httpPort,
+				httpsPort, shutdownPort, gitblitPropertiesPath,
+				usersPropertiesPath);
+		Thread serverThread2 = new Thread(gitblitRunnable2);
+		serverThread2.start();
+		Thread.sleep(2000);
+		Assert.assertFalse(gitblitRunnable2.isStartFailed());
+		LaunchWithUITestConfig.shutdownGitBlitServer(shutdownPort);
+	}
+
+	@Test
+	public void testParallelLaunchOfSeveralInstances()
+			throws InterruptedException {
+		// different ports than in testSequentialLaunchOfSeveralInstances to
+		// ensure that both test cases do not affect each others test results
+		int httpPort = 9797, httpsPort = 9898, shutdownPort = 9999;
+		int httpPort2 = 9494, httpsPort2 = 9595, shutdownPort2 = 9696;
+		String gitblitPropertiesPath = "src/test/config/test-ui-gitblit.properties",
+				usersPropertiesPath = "src/test/config/test-ui-users.conf";
+
+		GitblitRunnable gitblitRunnable = new GitblitRunnable(httpPort,
+				httpsPort, shutdownPort, gitblitPropertiesPath,
+				usersPropertiesPath);
+		Thread serverThread = new Thread(gitblitRunnable);
+		serverThread.start();
+		Thread.sleep(2000);
+		Assert.assertFalse(gitblitRunnable.isStartFailed());
+
+		GitblitRunnable gitblitRunnable2 = new GitblitRunnable(httpPort2,
+				httpsPort2, shutdownPort2, gitblitPropertiesPath,
+				usersPropertiesPath);
+		Thread serverThread2 = new Thread(gitblitRunnable2);
+		serverThread2.start();
+		Thread.sleep(2000);
+		Assert.assertFalse(gitblitRunnable2.isStartFailed());
+
+		LaunchWithUITestConfig.shutdownGitBlitServer(shutdownPort);
+		LaunchWithUITestConfig.shutdownGitBlitServer(shutdownPort2);
+	}
+
+	/**
+	 * main runs the tests without assert checks. You have to check the console
+	 * output manually.
+	 * 
+	 * @param args
+	 * @throws InterruptedException
+	 */
+	public static void main(String[] args) throws InterruptedException {
+		new LaunchWithUITestConfig().testSequentialLaunchOfSeveralInstances();
+		new LaunchWithUITestConfig().testParallelLaunchOfSeveralInstances();
+	}
+
+	private static void shutdownGitBlitServer(int shutdownPort) {
+		try {
+			Socket s = new Socket(InetAddress.getByName("127.0.0.1"),
+					shutdownPort);
+			OutputStream out = s.getOutputStream();
+			System.out.println("Sending Shutdown Request to " + Constants.NAME);
+			out.write("\r\n".getBytes());
+			out.flush();
+			s.close();
+		} catch (UnknownHostException e) {
+			e.printStackTrace();
+		} catch (IOException e) {
+			e.printStackTrace();
+		}
+	}
+}
diff --git a/src/test/java/de/akquinet/devops/ManualUITestLaunch.java b/src/test/java/de/akquinet/devops/ManualUITestLaunch.java
new file mode 100644
index 0000000..809360a
--- /dev/null
+++ b/src/test/java/de/akquinet/devops/ManualUITestLaunch.java
@@ -0,0 +1,15 @@
+package de.akquinet.devops;
+
+public class ManualUITestLaunch {
+public static void main(String[] args) {
+	int httpPort = 8080, httpsPort = 8443, shutdownPort = 8081;
+	String gitblitPropertiesPath = "src/test/config/test-ui-gitblit.properties",
+			usersPropertiesPath = "src/test/config/test-ui-users.conf";
+
+	GitblitRunnable gitblitRunnable = new GitblitRunnable(httpPort,
+			httpsPort, shutdownPort, gitblitPropertiesPath,
+			usersPropertiesPath);
+	Thread serverThread = new Thread(gitblitRunnable);
+	serverThread.start();
+}
+}
diff --git a/src/test/java/de/akquinet/devops/test/ui/TestUISuite.java b/src/test/java/de/akquinet/devops/test/ui/TestUISuite.java
new file mode 100644
index 0000000..08d7a00
--- /dev/null
+++ b/src/test/java/de/akquinet/devops/test/ui/TestUISuite.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2013 akquinet tech@spree GmbH
+ *
+ * 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 de.akquinet.devops.test.ui;
+
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+
+import de.akquinet.devops.test.ui.cases.UI_MultiAdminSupportTest;
+
+/**
+ * the test suite including all selenium-based ui-tests.
+ * 
+ * @author saheba
+ *
+ */
+@RunWith(Suite.class)
+@Suite.SuiteClasses({ UI_MultiAdminSupportTest.class, UI_MultiAdminSupportTest.class })
+public class TestUISuite {
+
+}
diff --git a/src/test/java/de/akquinet/devops/test/ui/cases/UI_MultiAdminSupportTest.java b/src/test/java/de/akquinet/devops/test/ui/cases/UI_MultiAdminSupportTest.java
new file mode 100644
index 0000000..a392571
--- /dev/null
+++ b/src/test/java/de/akquinet/devops/test/ui/cases/UI_MultiAdminSupportTest.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2013 akquinet tech@spree GmbH
+ *
+ * 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 de.akquinet.devops.test.ui.cases;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import de.akquinet.devops.test.ui.generic.AbstractUITest;
+import de.akquinet.devops.test.ui.view.RepoEditView;
+import de.akquinet.devops.test.ui.view.RepoListView;
+
+/**
+ * tests the multi admin per repo feature.
+ * 
+ * @author saheba
+ * 
+ */
+public class UI_MultiAdminSupportTest extends AbstractUITest {
+
+	String baseUrl = "https://localhost:8443";
+	RepoListView view;
+	RepoEditView editView;
+	private static final String TEST_MULTI_ADMIN_SUPPORT_REPO_NAME = "testmultiadminsupport";
+	private static final String TEST_MULTI_ADMIN_SUPPORT_REPO_PATH = "~repocreator/"
+			+ TEST_MULTI_ADMIN_SUPPORT_REPO_NAME + ".git";
+	private static final String TEST_MULTI_ADMIN_SUPPORT_REPO_PATH_WITHOUT_SUFFIX = "~repocreator/"
+			+ TEST_MULTI_ADMIN_SUPPORT_REPO_NAME;
+
+	@Before
+	public void before() {
+		System.out.println("IN BEFORE");
+		this.view = new RepoListView(AbstractUITest.getDriver(), baseUrl);
+		this.editView = new RepoEditView(AbstractUITest.getDriver());
+		AbstractUITest.getDriver().navigate().to(baseUrl);
+	}
+
+	@Test
+	public void test_MultiAdminSelectionInStandardRepo() {
+		// login
+		view.login("repocreator", "repocreator");
+
+		// create new repo
+		view.navigateToNewRepo(1);
+		editView.changeName(TEST_MULTI_ADMIN_SUPPORT_REPO_PATH);
+		Assert.assertTrue(editView.navigateToPermissionsTab());
+
+		Assert.assertTrue(editView
+				.changeAccessRestriction(RepoEditView.RESTRICTION_AUTHENTICATED_VCP));
+		Assert.assertTrue(editView
+				.changeAuthorizationControl(RepoEditView.AUTHCONTROL_RWALL));
+
+		// with a second admin
+		editView.addOwner("admin");
+		Assert.assertTrue(editView.save());
+		// user is automatically forwarded to repo list view
+		Assert.assertTrue(view.isEmptyRepo(TEST_MULTI_ADMIN_SUPPORT_REPO_PATH));
+		Assert.assertTrue(view
+				.isEditableRepo(TEST_MULTI_ADMIN_SUPPORT_REPO_PATH));
+		Assert.assertTrue(view
+				.isDeletableRepo(TEST_MULTI_ADMIN_SUPPORT_REPO_PATH_WITHOUT_SUFFIX));
+		// logout repocreator
+		view.logout();
+
+		// check with admin account if second admin has the same rights
+		view.login("admin", "admin");
+		Assert.assertTrue(view.isEmptyRepo(TEST_MULTI_ADMIN_SUPPORT_REPO_PATH));
+		Assert.assertTrue(view
+				.isEditableRepo(TEST_MULTI_ADMIN_SUPPORT_REPO_PATH));
+		Assert.assertTrue(view
+				.isDeletableRepo(TEST_MULTI_ADMIN_SUPPORT_REPO_PATH_WITHOUT_SUFFIX));
+		// delete repo to reach state as before test execution
+		view.navigateToDeleteRepo(TEST_MULTI_ADMIN_SUPPORT_REPO_PATH_WITHOUT_SUFFIX);
+		view.acceptAlertDialog();
+		view.logout();
+
+		Assert.assertTrue(view.isLoginPartVisible());
+	}
+
+}
diff --git a/tests/de/akquinet/devops/test/ui/generic/AbstractUITest.java b/src/test/java/de/akquinet/devops/test/ui/generic/AbstractUITest.java
similarity index 100%
rename from tests/de/akquinet/devops/test/ui/generic/AbstractUITest.java
rename to src/test/java/de/akquinet/devops/test/ui/generic/AbstractUITest.java
diff --git a/tests/de/akquinet/devops/test/ui/view/Exp.java b/src/test/java/de/akquinet/devops/test/ui/view/Exp.java
similarity index 100%
rename from tests/de/akquinet/devops/test/ui/view/Exp.java
rename to src/test/java/de/akquinet/devops/test/ui/view/Exp.java
diff --git a/tests/de/akquinet/devops/test/ui/view/GitblitDashboardView.java b/src/test/java/de/akquinet/devops/test/ui/view/GitblitDashboardView.java
similarity index 100%
rename from tests/de/akquinet/devops/test/ui/view/GitblitDashboardView.java
rename to src/test/java/de/akquinet/devops/test/ui/view/GitblitDashboardView.java
diff --git a/tests/de/akquinet/devops/test/ui/view/GitblitPageView.java b/src/test/java/de/akquinet/devops/test/ui/view/GitblitPageView.java
similarity index 100%
rename from tests/de/akquinet/devops/test/ui/view/GitblitPageView.java
rename to src/test/java/de/akquinet/devops/test/ui/view/GitblitPageView.java
diff --git a/src/test/java/de/akquinet/devops/test/ui/view/RepoEditView.java b/src/test/java/de/akquinet/devops/test/ui/view/RepoEditView.java
new file mode 100644
index 0000000..a3365d1
--- /dev/null
+++ b/src/test/java/de/akquinet/devops/test/ui/view/RepoEditView.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright 2013 akquinet tech@spree GmbH
+ *
+ * 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 de.akquinet.devops.test.ui.view;
+
+import java.util.List;
+
+import org.openqa.selenium.By;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.ui.WebDriverWait;
+
+/**
+ * class representing the tabs you can access when you edit a repo.
+ * 
+ * @author saheba
+ * 
+ */
+public class RepoEditView extends GitblitDashboardView {
+
+	public static final String PERMISSION_VIEW_USERS_NAME_PREFIX = "users:";
+	public static final String PERMISSION_VIEW_TEAMS_NAME_PREFIX = "teams:";
+
+	public static final String PERMISSION_VIEW_MUTABLE = "permissionToggleForm:showMutable";
+	public static final String PERMISSION_VIEW_SPECIFIED = "permissionToggleForm:showSpecified";
+	public static final String PERMISSION_VIEW_EFFECTIVE = "permissionToggleForm:showEffective";
+
+	public static final int RESTRICTION_ANONYMOUS_VCP = 0;
+	public static final int RESTRICTION_AUTHENTICATED_P = 1;
+	public static final int RESTRICTION_AUTHENTICATED_CP = 2;
+	public static final int RESTRICTION_AUTHENTICATED_VCP = 3;
+
+	public static final int AUTHCONTROL_RWALL = 0;
+	public static final int AUTHOCONTROL_FINE = 1;
+
+	public RepoEditView(WebDriver driver) {
+		super(driver, null);
+	}
+
+	public void changeName(String newName) {
+		String pathName = "//input[@id = \"name\" ]";
+		WebElement field = getDriver().findElement(By.xpath(pathName));
+		field.clear();
+		field.sendKeys(newName);
+	}
+
+	public boolean navigateToPermissionsTab() {
+		String linkText = "access permissions";
+		List<WebElement> found = getDriver().findElements(
+				By.partialLinkText(linkText));
+		System.out.println("PERM TABS found =" + found.size());
+		if (found != null && found.size() == 1) {
+			found.get(0).click();
+			return true;
+		}
+		return false;
+	}
+
+	private void changeOwners(String action,
+			String affectedSelection, String username) {
+		String xpath = "//select[@name=\"" + affectedSelection
+				+ "\"]/option[@value = \"" + username + "\" ]";
+		WebElement option = getDriver().findElement(By.xpath(xpath));
+		option.click();
+		String buttonPath = "//button[@class=\"button " + action + "\"]";
+		WebElement button = getDriver().findElement(By.xpath(buttonPath));
+		button.click();
+	}
+
+	public void removeOwner(String username) {
+		changeOwners("remove", "owners:selection",
+				username);
+	}
+
+	public void addOwner(String username) {
+		changeOwners("add", "owners:choices", username);
+	}
+
+	public WebElement getAccessRestrictionSelection() {
+		String xpath = "//select[@name =\"accessRestriction\"]";
+		List<WebElement> found = getDriver().findElements(By.xpath(xpath));
+		if (found != null && found.size() == 1) {
+			return found.get(0);
+		}
+		return null;
+	}
+
+	public boolean changeAccessRestriction(int option) {
+		WebElement accessRestrictionSelection = getAccessRestrictionSelection();
+		if (accessRestrictionSelection == null) {
+			return false;
+		}
+		accessRestrictionSelection.click();
+		sleep(100);
+		String xpath = "//select[@name =\"accessRestriction\"]/option[@value=\""
+				+ option + "\"]";
+		List<WebElement> found = getDriver().findElements(By.xpath(xpath));
+		if (found == null || found.size() == 0 || found.size() > 1) {
+			return false;
+		}
+		found.get(0).click();
+		return true;
+	}
+
+	public boolean changeAuthorizationControl(int option) {
+		System.out.println("try to change auth control");
+		String xpath = "//input[@name =\"authorizationControl\" and @value=\""
+				+ option + "\"]";
+		List<WebElement> found = getDriver().findElements(By.xpath(xpath));
+		System.out.println("found auth CONTROL options " + found.size());
+		if (found == null || found.size() == 0 || found.size() > 1) {
+			return false;
+		}
+		found.get(0).click();
+		return true;
+	}
+
+	private boolean isPermissionViewDisabled(String prefix, String view) {
+		String xpath = "//[@name =\"" + prefix + view + "\"]";
+		List<WebElement> found = getDriver().findElements(By.xpath(xpath));
+		if (found == null || found.size() == 0 || found.size() > 1) {
+			return false;
+		}
+		String attrValue = found.get(0).getAttribute("disabled");
+		return (attrValue != null) && (attrValue.equals("disabled"));
+	}
+
+	public boolean isPermissionViewSectionDisabled(String prefix) {
+		return isPermissionViewDisabled(prefix, PERMISSION_VIEW_MUTABLE)
+				&& isPermissionViewDisabled(prefix, PERMISSION_VIEW_SPECIFIED)
+				&& isPermissionViewDisabled(prefix, PERMISSION_VIEW_EFFECTIVE);
+	}
+
+	public boolean save() {
+		String xpath = "//div[@class=\"form-actions\"]/input[@name =\""
+				+ "save" + "\"]";
+		List<WebElement> found = getDriver().findElements(By.xpath(xpath));
+		if (found == null || found.size() == 0 || found.size() > 1) {
+			return false;
+		}
+		found.get(0).click();
+		WebDriverWait webDriverWait = new WebDriverWait(getDriver(), 1);
+		webDriverWait.until(new Exp.RepoListViewLoaded());
+		return true;
+	}
+}
diff --git a/tests/de/akquinet/devops/test/ui/view/RepoListView.java b/src/test/java/de/akquinet/devops/test/ui/view/RepoListView.java
similarity index 100%
rename from tests/de/akquinet/devops/test/ui/view/RepoListView.java
rename to src/test/java/de/akquinet/devops/test/ui/view/RepoListView.java
diff --git a/src/test/resources/issue0259.conf b/src/test/resources/issue0259.conf
new file mode 100644
index 0000000..ad6f0c3
--- /dev/null
+++ b/src/test/resources/issue0259.conf
@@ -0,0 +1,32 @@
+[user "A"]
+	password = "#externalAccount"
+	role = "#none"
+[user "B"]
+	password = "#externalAccount"
+	role = "#none"
+[user "C"]
+	password = "#externalAccount"
+	role = "#admin"
+[team "git_1560"]
+	role = "#none"
+	repository = RWC:trade/.*
+	repository = RWC:dev.support/admin.behelfsskripte.git
+	repository = RWD:projects/.*
+	repository = RWD:birt/.*
+	repository = RWD:fact/.*
+	repository = RWD:of/.*
+	repository = RWD:portal.solutions/.*
+	repository = RWD:rem/.*
+	repository = RWD:transparency/.*
+	repository = RWD:process/.*
+	user = B
+	user = A
+	user = C
+[team "git_EcgUser"]
+	role = "#create"
+	repository = R:.*
+	repository = RWD:test.projects/.*
+	repository = R:~krulls/regressionstests.base.git
+	user = A
+	user = C
+	user = B
diff --git a/src/test/resources/issue0271.conf b/src/test/resources/issue0271.conf
new file mode 100644
index 0000000..c77ca92
--- /dev/null
+++ b/src/test/resources/issue0271.conf
@@ -0,0 +1,20 @@
+[user "A"]
+	password = apassword
+	role = "#none"
+[user "B"]
+	password = apassword
+	role = "#none"
+[user "C"]
+	password = apassword
+	role = "#none"
+	repository = RWC:teama/.*
+[team "developers"]
+	role = "#none"
+	repository = V:.*
+	user = A
+	user = B
+	user = C
+[team "teama"]
+	repository = RW:teama/.*
+	user = B
+	user = C
diff --git a/test-gitblit.properties b/test-gitblit.properties
deleted file mode 100644
index f16f5c5..0000000
--- a/test-gitblit.properties
+++ /dev/null
@@ -1,88 +0,0 @@
-#
-# Gitblit Unit Testing properties
-#
-
-git.repositoriesFolder = ${baseFolder}/git
-git.searchRepositoriesSubfolders = true
-git.enableGitServlet = true
-groovy.scriptsFolder = ${baseFolder}/groovy
-groovy.preReceiveScripts = blockpush
-groovy.postReceiveScripts = sendmail
-web.authenticateViewPages = false
-web.authenticateAdminPages = true
-web.allowCookieAuthentication = true
-realm.userService = test-users.conf
-realm.passwordStorage = md5
-realm.minPasswordLength = 5
-web.siteName =
-web.allowAdministration = true
-web.enableRpcServlet = true
-web.enableRpcManagement = true
-web.enableRpcAdministration = true
-web.allowGravatar = true
-web.allowZipDownloads = true
-web.syndicationEntries = 25
-web.showRepositorySizes = true
-web.showFederationRegistrations = false
-web.loginMessage = gitblit
-web.repositoriesMessage = gitblit
-web.useClientTimezone = false
-web.timeFormat = HH:mm
-web.datestampShortFormat = yyyy-MM-dd
-web.datestampLongFormat = EEEE, MMMM d, yyyy
-web.datetimestampLongFormat = EEEE, MMMM d, yyyy h:mm a z
-web.mountParameters = true
-web.forwardSlashCharacter = /
-web.otherUrls = 
-web.repositoryListType = grouped
-web.repositoryRootGroupName = main
-web.repositoryListSwatches = true
-web.diffStyle = gitblit
-web.showEmailAddresses = true
-web.showSearchTypeSelection = false
-web.generateActivityGraph = true
-web.activityDuration = 14
-web.summaryCommitCount = 16
-web.summaryRefsCount = 5
-web.itemsPerPage = 50
-web.prettyPrintExtensions = c cpp cs css htm html java js php pl prefs properties py rb sh sql xml vb
-web.markdownExtensions = md mkd markdown MD MKD
-web.imageExtensions = bmp jpg gif png 
-web.binaryExtensions = jar pdf tar.gz zip
-web.aggressiveHeapManagement = false
-web.debugMode = false
-regex.global = true
-regex.global.bug = \\b(Bug:)(\\s*[#]?|-){0,1}(\\d+)\\b!!!<a href="http://somehost/bug/$3">Bug-Id: $3</a>
-regex.global.changeid = \\b(Change-Id:\\s*)([A-Za-z0-9]*)\\b!!!<a href="http://somehost/changeid/$2">Change-Id: $2</a>
-regex.myrepository.bug = \\b(Bug:)(\\s*[#]?|-){0,1}(\\d+)\\b!!!<a href="http://elsewhere/bug/$3">Bug-Id: $3</a>
-mail.server =
-mail.port = 25
-mail.debug = false
-mail.username =
-mail.password =
-mail.fromAddress = 
-mail.adminAddresses = 
-mail.mailingLists = x@test.com y@test.com z@test.com
-federation.name = Unit Test
-federation.passphrase = Unit Testing
-federation.allowProposals = false
-federation.proposalsFolder = proposals
-federation.defaultFrequency = 60 mins
-federation.sets = animal mineral vegetable
-#federation.example1.url = https://go.gitblit.com
-#federation.example1.token = 6f3b8a24bf970f17289b234284c94f43eb42f0e4
-#federation.example1.frequency = 120 mins
-#federation.example1.folder =
-#federation.example1.bare = true 
-#federation.example1.mirror = true 
-#federation.example1.mergeAccounts = true
-
-server.tempFolder = ${baseFolder}/temp
-server.useNio = true
-server.contextPath = /
-server.httpPort = 0
-server.httpsPort = 8443
-server.httpBindInterface = localhost
-server.httpsBindInterface = localhost
-server.storePassword = gitblit
-server.shutdownPort = 8081
diff --git a/tests/com/gitblit/tests/FanoutServiceTest.java b/tests/com/gitblit/tests/FanoutServiceTest.java
deleted file mode 100644
index 28e5d82..0000000
--- a/tests/com/gitblit/tests/FanoutServiceTest.java
+++ /dev/null
@@ -1,172 +0,0 @@
-/*
- * Copyright 2013 gitblit.com.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.gitblit.tests;
-
-import static org.junit.Assert.assertEquals;
-
-import java.text.MessageFormat;
-import java.util.Date;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.atomic.AtomicInteger;
-
-import org.junit.Test;
-
-import com.gitblit.fanout.FanoutService;
-import com.gitblit.fanout.FanoutClient;
-import com.gitblit.fanout.FanoutClient.FanoutAdapter;
-import com.gitblit.fanout.FanoutNioService;
-import com.gitblit.fanout.FanoutService;
-import com.gitblit.fanout.FanoutSocketService;
-
-public class FanoutServiceTest {
-	
-	int fanoutPort = FanoutService.DEFAULT_PORT;
-	
-	@Test
-	public void testNioPubSub() throws Exception {
-		testPubSub(new FanoutNioService(fanoutPort));
-	}
-
-	@Test
-	public void testSocketPubSub() throws Exception {
-		testPubSub(new FanoutSocketService(fanoutPort));
-	}
-	
-	@Test
-	public void testNioDisruptionAndRecovery() throws Exception {
-		testDisruption(new FanoutNioService(fanoutPort));
-	}
-
-	@Test
-	public void testSocketDisruptionAndRecovery() throws Exception {
-		testDisruption(new FanoutSocketService(fanoutPort));
-	}
-	
-	protected void testPubSub(FanoutService service) throws Exception {
-		System.out.println(MessageFormat.format("\n\n========================================\nPUBSUB TEST {0}\n========================================\n\n", service.toString()));
-		service.startSynchronously();
-		
-		final Map<String, String> announcementsA = new ConcurrentHashMap<String, String>();
-		FanoutClient clientA = new FanoutClient("localhost", fanoutPort);
-		clientA.addListener(new FanoutAdapter() {
-			
-			@Override
-			public void announcement(String channel, String message) {
-				announcementsA.put(channel, message);
-			}
-		});
-		
-		clientA.startSynchronously();
-
-		final Map<String, String> announcementsB = new ConcurrentHashMap<String, String>();
-		FanoutClient clientB = new FanoutClient("localhost", fanoutPort);
-		clientB.addListener(new FanoutAdapter() {
-			@Override
-			public void announcement(String channel, String message) {
-				announcementsB.put(channel, message);
-			}
-		});
-		clientB.startSynchronously();
-
-		
-		// subscribe clients A and B to the channels
-		clientA.subscribe("a");
-		clientA.subscribe("b");
-		clientA.subscribe("c");
-		
-		clientB.subscribe("a");
-		clientB.subscribe("b");
-		clientB.subscribe("c");
-		
-		// give async messages a chance to be delivered
-		Thread.sleep(1000);
-		
-		clientA.announce("a", "apple");
-		clientA.announce("b", "banana");
-		clientA.announce("c", "cantelope");
-		
-		clientB.announce("a", "avocado");
-		clientB.announce("b", "beet");
-		clientB.announce("c", "carrot");
-
-		// give async messages a chance to be delivered
-		Thread.sleep(2000);
-
-		// confirm that client B received client A's announcements
-		assertEquals("apple", announcementsB.get("a"));
-		assertEquals("banana", announcementsB.get("b"));
-		assertEquals("cantelope", announcementsB.get("c"));
-
-		// confirm that client A received client B's announcements
-		assertEquals("avocado", announcementsA.get("a"));
-		assertEquals("beet", announcementsA.get("b"));
-		assertEquals("carrot", announcementsA.get("c"));
-		
-		clientA.stop();
-		clientB.stop();
-		service.stop();		
-	}
-	
-	protected void testDisruption(FanoutService service) throws Exception  {
-		System.out.println(MessageFormat.format("\n\n========================================\nDISRUPTION TEST {0}\n========================================\n\n", service.toString()));
-		service.startSynchronously();
-		
-		final AtomicInteger pongCount = new AtomicInteger(0);
-		FanoutClient client = new FanoutClient("localhost", fanoutPort);
-		client.addListener(new FanoutAdapter() {
-			@Override
-			public void pong(Date timestamp) {
-				pongCount.incrementAndGet();
-			}
-		});
-		client.startSynchronously();
-		
-		// ping and wait for pong
-		client.ping();	
-		Thread.sleep(500);
-		
-		// restart client
-		client.stop();
-		Thread.sleep(1000);
-		client.startSynchronously();		
-		
-		// ping and wait for pong
-		client.ping();	
-		Thread.sleep(500);
-				
-		assertEquals(2, pongCount.get());
-		
-		// now disrupt service
-		service.stop();		
-		Thread.sleep(2000);
-		service.startSynchronously();
-		
-		// wait for reconnect
-		Thread.sleep(2000);
-
-		// ping and wait for pong
-		client.ping();
-		Thread.sleep(500);
-
-		// kill all
-		client.stop();
-		service.stop();
-		
-		// confirm expected pong count
-		assertEquals(3, pongCount.get());
-	}
-}
\ No newline at end of file
diff --git a/tests/com/gitblit/tests/FederationTests.java b/tests/com/gitblit/tests/FederationTests.java
deleted file mode 100644
index ced500a..0000000
--- a/tests/com/gitblit/tests/FederationTests.java
+++ /dev/null
@@ -1,166 +0,0 @@
-/*
- * Copyright 2011 gitblit.com.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.gitblit.tests;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-
-import java.util.Date;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-import org.junit.Test;
-
-import com.gitblit.Constants.AccessRestrictionType;
-import com.gitblit.Constants.FederationProposalResult;
-import com.gitblit.Constants.FederationRequest;
-import com.gitblit.Constants.FederationToken;
-import com.gitblit.models.FederationModel;
-import com.gitblit.models.FederationProposal;
-import com.gitblit.models.RepositoryModel;
-import com.gitblit.models.TeamModel;
-import com.gitblit.models.UserModel;
-import com.gitblit.utils.FederationUtils;
-import com.gitblit.utils.JsonUtils;
-import com.gitblit.utils.RpcUtils;
-
-public class FederationTests {
-
-	String url = GitBlitSuite.url;
-	String account = GitBlitSuite.account;
-	String password = GitBlitSuite.password;
-	String token = "d7cc58921a80b37e0329a4dae2f9af38bf61ef5c";
-
-	private static final AtomicBoolean started = new AtomicBoolean(false);
-
-	@BeforeClass
-	public static void startGitblit() throws Exception {
-		started.set(GitBlitSuite.startGitblit());
-	}
-
-	@AfterClass
-	public static void stopGitblit() throws Exception {
-		if (started.get()) {
-			GitBlitSuite.stopGitblit();
-		}
-	}
-
-	@Test
-	public void testProposal() throws Exception {
-		// create dummy repository data
-		Map<String, RepositoryModel> repositories = new HashMap<String, RepositoryModel>();
-		for (int i = 0; i < 5; i++) {
-			RepositoryModel model = new RepositoryModel();
-			model.accessRestriction = AccessRestrictionType.VIEW;
-			model.description = "cloneable repository " + i;
-			model.lastChange = new Date();
-			model.addOwner("adminuser");
-			model.name = "repo" + i + ".git";
-			model.size = "5 MB";
-			model.hasCommits = true;
-			repositories.put(model.name, model);
-		}
-
-		FederationProposal proposal = new FederationProposal("http://testurl", FederationToken.ALL,
-				"testtoken", repositories);
-
-		// propose federation
-		assertEquals("proposal refused", FederationUtils.propose(url, proposal),
-				FederationProposalResult.NO_PROPOSALS);
-	}
-
-	@Test
-	public void testJsonRepositories() throws Exception {
-		String requrl = FederationUtils.asLink(url, token, FederationRequest.PULL_REPOSITORIES);
-		String json = JsonUtils.retrieveJsonString(requrl, null, null);
-		assertNotNull(json);
-	}
-
-	@Test
-	public void testJsonUsers() throws Exception {
-		String requrl = FederationUtils.asLink(url, token, FederationRequest.PULL_USERS);
-		String json = JsonUtils.retrieveJsonString(requrl, null, null);
-		assertNotNull(json);
-	}
-
-	@Test
-	public void testJsonTeams() throws Exception {
-		String requrl = FederationUtils.asLink(url, token, FederationRequest.PULL_TEAMS);
-		String json = JsonUtils.retrieveJsonString(requrl, null, null);
-		assertNotNull(json);
-	}
-
-	private FederationModel getRegistration() {
-		FederationModel model = new FederationModel("localhost");
-		model.url = this.url;
-		model.token = this.token;
-		return model;
-	}
-
-	@Test
-	public void testPullRepositories() throws Exception {
-		Map<String, RepositoryModel> repos = FederationUtils.getRepositories(getRegistration(),
-				false);
-		assertNotNull(repos);
-		assertTrue(repos.size() > 0);
-	}
-
-	@Test
-	public void testPullUsers() throws Exception {
-		List<UserModel> users = FederationUtils.getUsers(getRegistration());
-		assertNotNull(users);
-		// admin is excluded
-		assertEquals(0, users.size());
-		
-		UserModel newUser = new UserModel("test");
-		newUser.password = "whocares";
-		assertTrue(RpcUtils.createUser(newUser, url, account, password.toCharArray()));
-		
-		TeamModel team = new TeamModel("testteam");
-		team.addUser("test");
-		team.addRepositoryPermission("helloworld.git");
-		assertTrue(RpcUtils.createTeam(team, url, account, password.toCharArray()));
-		
-		users = FederationUtils.getUsers(getRegistration());
-		assertNotNull(users);
-		assertEquals(1, users.size());
-		
-		newUser = users.get(0);
-		assertTrue(newUser.isTeamMember("testteam"));		
-		
-		assertTrue(RpcUtils.deleteUser(newUser, url, account, password.toCharArray()));
-		assertTrue(RpcUtils.deleteTeam(team, url, account, password.toCharArray()));
-	}
-
-	@Test
-	public void testPullTeams() throws Exception {
-		List<TeamModel> teams = FederationUtils.getTeams(getRegistration());
-		assertNotNull(teams);
-		assertTrue(teams.size() > 0);
-	}
-	
-	@Test
-	public void testPullScripts() throws Exception {
-		Map<String, String> scripts = FederationUtils.getScripts(getRegistration());
-		assertNotNull(scripts);
-		assertTrue(scripts.keySet().contains("sendmail"));
-	}
-}
diff --git a/tests/com/gitblit/tests/FileUtilsTest.java b/tests/com/gitblit/tests/FileUtilsTest.java
deleted file mode 100644
index 8e5cf8a..0000000
--- a/tests/com/gitblit/tests/FileUtilsTest.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright 2011 gitblit.com.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.gitblit.tests;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-import java.io.File;
-
-import org.junit.Test;
-
-import com.gitblit.utils.FileUtils;
-
-public class FileUtilsTest {
-
-	@Test
-	public void testReadContent() throws Exception {
-		File dir = new File(System.getProperty("user.dir"));
-		String rawContent = FileUtils.readContent(new File(dir, "LICENSE"), "\n");
-		assertTrue(rawContent.trim().startsWith("Apache License"));
-	}
-
-	@Test
-	public void testWriteContent() throws Exception {
-		String contentA = "this is a test";
-		File tmp = File.createTempFile("gitblit-", ".test");
-		FileUtils.writeContent(tmp, contentA);
-		String contentB = FileUtils.readContent(tmp, "\n").trim();
-		assertEquals(contentA, contentB);
-	}
-
-	@Test
-	public void testFolderSize() throws Exception {
-		assertEquals(-1, FileUtils.folderSize(null));
-		assertEquals(-1, FileUtils.folderSize(new File(System.getProperty("user.dir"), "pretend")));
-
-		File dir = new File(System.getProperty("user.dir"), "distrib");
-		long size = FileUtils.folderSize(dir);
-		assertTrue("size is actually " + size, size >= 470000L);
-
-		File file = new File(System.getProperty("user.dir"), "LICENSE");
-		size = FileUtils.folderSize(file);
-		assertEquals("size is actually " + size, 11556L, size);
-	}
-	
-	@Test
-	public void testStringSizes() throws Exception {
-		assertEquals(50 * FileUtils.KB, FileUtils.convertSizeToInt("50k", 0));
-		assertEquals(50 * FileUtils.MB, FileUtils.convertSizeToInt("50m", 0));
-		assertEquals(2 * FileUtils.GB, FileUtils.convertSizeToInt("2g", 0));
-
-		assertEquals(50 * FileUtils.KB, FileUtils.convertSizeToInt("50kb", 0));
-		assertEquals(50 * FileUtils.MB, FileUtils.convertSizeToInt("50mb", 0));
-		assertEquals(2 * FileUtils.GB, FileUtils.convertSizeToInt("2gb", 0));
-
-		assertEquals(50L * FileUtils.KB, FileUtils.convertSizeToLong("50k", 0));
-		assertEquals(50L * FileUtils.MB, FileUtils.convertSizeToLong("50m", 0));
-		assertEquals(50L * FileUtils.GB, FileUtils.convertSizeToLong("50g", 0));
-
-		assertEquals(50L * FileUtils.KB, FileUtils.convertSizeToLong("50kb", 0));
-		assertEquals(50L * FileUtils.MB, FileUtils.convertSizeToLong("50mb", 0));
-		assertEquals(50L * FileUtils.GB, FileUtils.convertSizeToLong("50gb", 0));
-		
-		assertEquals(50 * FileUtils.KB, FileUtils.convertSizeToInt("50 k", 0));
-		assertEquals(50 * FileUtils.MB, FileUtils.convertSizeToInt("50 m", 0));
-		assertEquals(2 * FileUtils.GB, FileUtils.convertSizeToInt("2 g", 0));
-
-		assertEquals(50 * FileUtils.KB, FileUtils.convertSizeToInt("50 kb", 0));
-		assertEquals(50 * FileUtils.MB, FileUtils.convertSizeToInt("50 mb", 0));
-		assertEquals(2 * FileUtils.GB, FileUtils.convertSizeToInt("2 gb", 0));
-
-	}
-}
\ No newline at end of file
diff --git a/tests/com/gitblit/tests/GitBlitSuite.java b/tests/com/gitblit/tests/GitBlitSuite.java
deleted file mode 100644
index b0179c3..0000000
--- a/tests/com/gitblit/tests/GitBlitSuite.java
+++ /dev/null
@@ -1,232 +0,0 @@
-/*
- * Copyright 2011 gitblit.com.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.gitblit.tests;
-
-import java.io.File;
-import java.lang.reflect.Field;
-import java.util.concurrent.Executors;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicInteger;
-
-import org.eclipse.jgit.api.Git;
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.lib.RepositoryCache;
-import org.eclipse.jgit.lib.RepositoryCache.FileKey;
-import org.eclipse.jgit.storage.file.FileRepository;
-import org.eclipse.jgit.util.FS;
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-import org.junit.runner.RunWith;
-import org.junit.runners.Suite;
-import org.junit.runners.Suite.SuiteClasses;
-
-import com.gitblit.GitBlit;
-import com.gitblit.GitBlitException;
-import com.gitblit.GitBlitServer;
-import com.gitblit.models.RepositoryModel;
-import com.gitblit.utils.JGitUtils;
-
-/**
- * The GitBlitSuite uses test-gitblit.properties and test-users.conf. The suite
- * is fairly comprehensive for all lower-level functionality. Wicket pages are
- * currently not unit-tested.
- * 
- * This suite starts a Gitblit server instance within the same JVM instance as
- * the unit tests. This allows the unit tests to access the GitBlit static
- * singleton while also being able to communicate with the instance via tcp/ip
- * for testing rpc requests, federation requests, and git servlet operations.
- * 
- * @author James Moger
- * 
- */
-@RunWith(Suite.class)
-@SuiteClasses({ ArrayUtilsTest.class, FileUtilsTest.class, TimeUtilsTest.class,
-		StringUtilsTest.class, Base64Test.class, JsonUtilsTest.class, ByteFormatTest.class,
-		ObjectCacheTest.class, PermissionsTest.class, UserServiceTest.class, LdapUserServiceTest.class,
-		MarkdownUtilsTest.class, JGitUtilsTest.class, SyndicationUtilsTest.class,
-		DiffUtilsTest.class, MetricUtilsTest.class, TicgitUtilsTest.class, X509UtilsTest.class,
-		GitBlitTest.class, FederationTests.class, RpcTests.class, GitServletTest.class,
-		GroovyScriptTest.class, LuceneExecutorTest.class, IssuesTest.class, RepositoryModelTest.class,
-		FanoutServiceTest.class })
-public class GitBlitSuite {
-
-	public static final File REPOSITORIES = new File("data/git");
-
-	static int port = 8280;
-	static int shutdownPort = 8281;
-
-	public static String url = "http://localhost:" + port;
-	public static String account = "admin";
-	public static String password = "admin";
-
-	private static AtomicBoolean started = new AtomicBoolean(false);
-
-	public static Repository getHelloworldRepository() throws Exception {
-		return new FileRepository(new File(REPOSITORIES, "helloworld.git"));
-	}
-
-	public static Repository getTicgitRepository() throws Exception {
-		return new FileRepository(new File(REPOSITORIES, "ticgit.git"));
-	}
-
-	public static Repository getJGitRepository() throws Exception {
-		return new FileRepository(new File(REPOSITORIES, "test/jgit.git"));
-	}
-
-	public static Repository getAmbitionRepository() throws Exception {
-		return new FileRepository(new File(REPOSITORIES, "test/ambition.git"));
-	}
-
-	public static Repository getTheoreticalPhysicsRepository() throws Exception {
-		return new FileRepository(new File(REPOSITORIES, "test/theoretical-physics.git"));
-	}
-
-	public static Repository getIssuesTestRepository() throws Exception {
-		JGitUtils.createRepository(REPOSITORIES, "gb-issues.git").close();
-		return new FileRepository(new File(REPOSITORIES, "gb-issues.git"));
-	}
-	
-	public static Repository getGitectiveRepository() throws Exception {
-		return new FileRepository(new File(REPOSITORIES, "test/gitective.git"));
-	}
-
-	public static boolean startGitblit() throws Exception {
-		if (started.get()) {
-			// already started
-			return false;
-		}
-		
-		GitServletTest.deleteWorkingFolders();
-		
-		// Start a Gitblit instance
-		Executors.newSingleThreadExecutor().execute(new Runnable() {
-			public void run() {
-				GitBlitServer.main("--httpPort", "" + port, "--httpsPort", "0", "--shutdownPort",
-						"" + shutdownPort, "--repositoriesFolder",
-						"\"" + GitBlitSuite.REPOSITORIES.getAbsolutePath() + "\"", "--userService",
-						"test-users.conf", "--settings", "test-gitblit.properties",
-						"--baseFolder", "data");
-			}
-		});
-
-		// Wait a few seconds for it to be running
-		Thread.sleep(2500);
-
-		started.set(true);
-		return true;
-	}
-
-	public static void stopGitblit() throws Exception {
-		// Stop Gitblit
-		GitBlitServer.main("--stop", "--shutdownPort", "" + shutdownPort);
-
-		// Wait a few seconds for it to be running
-		Thread.sleep(5000);
-	}
-
-	@BeforeClass
-	public static void setUp() throws Exception {
-		startGitblit();
-
-		if (REPOSITORIES.exists() || REPOSITORIES.mkdirs()) {
-			cloneOrFetch("helloworld.git", "https://github.com/git/hello-world.git");
-			cloneOrFetch("ticgit.git", "https://github.com/schacon/ticgit.git");
-			cloneOrFetch("test/jgit.git", "https://github.com/eclipse/jgit.git");
-			cloneOrFetch("test/helloworld.git", "https://github.com/git/hello-world.git");
-			cloneOrFetch("test/ambition.git", "https://github.com/defunkt/ambition.git");
-			cloneOrFetch("test/theoretical-physics.git", "https://github.com/certik/theoretical-physics.git");
-			cloneOrFetch("test/gitective.git", "https://github.com/kevinsawicki/gitective.git");
-			
-			enableTickets("ticgit.git");
-			enableDocs("ticgit.git");
-			showRemoteBranches("ticgit.git");
-			showRemoteBranches("test/jgit.git");
-		}
-	}
-
-	@AfterClass
-	public static void tearDown() throws Exception {
-		stopGitblit();
-	}
-
-	private static void cloneOrFetch(String name, String fromUrl) throws Exception {
-		System.out.print("Fetching " + name + "... ");
-		JGitUtils.cloneRepository(REPOSITORIES, name, fromUrl);
-		System.out.println("done.");
-	}
-
-	private static void enableTickets(String repositoryName) {
-		try {
-			RepositoryModel model = GitBlit.self().getRepositoryModel(repositoryName);
-			model.useTickets = true;
-			GitBlit.self().updateRepositoryModel(model.name, model, false);
-		} catch (GitBlitException g) {
-			g.printStackTrace();
-		}
-	}
-
-	private static void enableDocs(String repositoryName) {
-		try {
-			RepositoryModel model = GitBlit.self().getRepositoryModel(repositoryName);
-			model.useDocs = true;
-			GitBlit.self().updateRepositoryModel(model.name, model, false);
-		} catch (GitBlitException g) {
-			g.printStackTrace();
-		}
-	}
-
-	private static void showRemoteBranches(String repositoryName) {
-		try {
-			RepositoryModel model = GitBlit.self().getRepositoryModel(repositoryName);
-			model.showRemoteBranches = true;
-			GitBlit.self().updateRepositoryModel(model.name, model, false);
-		} catch (GitBlitException g) {
-			g.printStackTrace();
-		}
-	}
-	
-	public static void close(File repository) {
-		try {
-			File gitDir = FileKey.resolve(repository, FS.detect());
-			if (gitDir != null && gitDir.exists()) {
-				close(RepositoryCache.open(FileKey.exact(gitDir, FS.detect())));
-			}
-		} catch (Exception e) {
-			e.printStackTrace();
-		}
-	}
-	
-	public static void close(Git git) {
-		close(git.getRepository());
-	}
-	
-	public static void close(Repository r) {
-		RepositoryCache.close(r);
-
-		// assume 2 uses in case reflection fails
-		int uses = 2;
-		try {
-			Field useCnt = Repository.class.getDeclaredField("useCnt");
-			useCnt.setAccessible(true);
-			uses = ((AtomicInteger) useCnt.get(r)).get();
-		} catch (Exception e) {
-			e.printStackTrace();
-		}
-		for (int i = 0; i < uses; i++) {
-			r.close();
-		}
-	}
-}
diff --git a/tests/com/gitblit/tests/GitBlitTest.java b/tests/com/gitblit/tests/GitBlitTest.java
deleted file mode 100644
index 786614f..0000000
--- a/tests/com/gitblit/tests/GitBlitTest.java
+++ /dev/null
@@ -1,187 +0,0 @@
-/*
- * Copyright 2011 gitblit.com.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.gitblit.tests;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-
-import java.util.List;
-
-import org.junit.Test;
-
-import com.gitblit.Constants.AccessRestrictionType;
-import com.gitblit.FileSettings;
-import com.gitblit.GitBlit;
-import com.gitblit.models.RepositoryModel;
-import com.gitblit.models.UserModel;
-
-public class GitBlitTest {
-
-	@Test
-	public void testRepositoryModel() throws Exception {
-		List<String> repositories = GitBlit.self().getRepositoryList();
-		assertTrue("Repository list is empty!", repositories.size() > 0);
-		assertTrue(
-				"Missing Helloworld repository!",
-				repositories.contains(GitBlitSuite.getHelloworldRepository().getDirectory()
-						.getName()));
-		RepositoryModel model = GitBlit.self().getRepositoryModel(
-				GitBlitSuite.getHelloworldRepository().getDirectory().getName());
-		assertTrue("Helloworld model is null!", model != null);
-		assertEquals(GitBlitSuite.getHelloworldRepository().getDirectory().getName(), model.name);
-		assertTrue(GitBlit.self().calculateSize(model) > 22000L);
-	}
-
-	@Test
-	public void testUserModel() throws Exception {
-		List<String> users = GitBlit.self().getAllUsernames();
-		assertTrue("No users found!", users.size() > 0);
-		assertTrue("Admin not found", users.contains("admin"));
-		UserModel user = GitBlit.self().getUserModel("admin");
-		assertEquals("admin", user.toString());
-		assertTrue("Admin missing #admin role!", user.canAdmin);
-		user.canAdmin = false;
-		assertFalse("Admin should not have #admin!", user.canAdmin);
-		String repository = GitBlitSuite.getHelloworldRepository().getDirectory().getName();
-		RepositoryModel repositoryModel = GitBlit.self().getRepositoryModel(repository);
-		repositoryModel.accessRestriction = AccessRestrictionType.VIEW;
-		assertFalse("Admin can still access repository!",
-				user.canView(repositoryModel));
-		user.addRepositoryPermission(repository);
-		assertTrue("Admin can't access repository!", user.canView(repositoryModel));
-		assertEquals(GitBlit.self().getRepositoryModel(user, "pretend"), null);
-		assertNotNull(GitBlit.self().getRepositoryModel(user, repository));
-		assertTrue(GitBlit.self().getRepositoryModels(user).size() > 0);
-	}
-	
-	@Test
-	public void testUserModelVerification() throws Exception {
-		UserModel user = new UserModel("james");
-		user.displayName = "James Moger";
-		
-		assertTrue(user.is("James", null));
-		assertTrue(user.is("James", ""));
-		assertTrue(user.is("JaMeS", "anything"));
-		
-		assertTrue(user.is("james moger", null));
-		assertTrue(user.is("james moger", ""));
-		assertTrue(user.is("james moger", "anything"));
-		
-		assertFalse(user.is("joe", null));
-		assertFalse(user.is("joe", ""));
-		assertFalse(user.is("joe", "anything"));
-
-		// specify email address which results in address verification
-		user.emailAddress = "something";
-
-		assertFalse(user.is("James", null));
-		assertFalse(user.is("James", ""));
-		assertFalse(user.is("JaMeS", "anything"));
-		
-		assertFalse(user.is("james moger", null));
-		assertFalse(user.is("james moger", ""));
-		assertFalse(user.is("james moger", "anything"));
-
-		assertTrue(user.is("JaMeS", user.emailAddress));
-		assertTrue(user.is("JaMeS mOgEr", user.emailAddress));
-	}
-
-	@Test
-	public void testAccessRestrictionTypes() throws Exception {
-		assertTrue(AccessRestrictionType.PUSH.exceeds(AccessRestrictionType.NONE));
-		assertTrue(AccessRestrictionType.CLONE.exceeds(AccessRestrictionType.PUSH));
-		assertTrue(AccessRestrictionType.VIEW.exceeds(AccessRestrictionType.CLONE));
-
-		assertFalse(AccessRestrictionType.NONE.exceeds(AccessRestrictionType.PUSH));
-		assertFalse(AccessRestrictionType.PUSH.exceeds(AccessRestrictionType.CLONE));
-		assertFalse(AccessRestrictionType.CLONE.exceeds(AccessRestrictionType.VIEW));
-
-		assertTrue(AccessRestrictionType.PUSH.atLeast(AccessRestrictionType.NONE));
-		assertTrue(AccessRestrictionType.CLONE.atLeast(AccessRestrictionType.PUSH));
-		assertTrue(AccessRestrictionType.VIEW.atLeast(AccessRestrictionType.CLONE));
-
-		assertFalse(AccessRestrictionType.NONE.atLeast(AccessRestrictionType.PUSH));
-		assertFalse(AccessRestrictionType.PUSH.atLeast(AccessRestrictionType.CLONE));
-		assertFalse(AccessRestrictionType.CLONE.atLeast(AccessRestrictionType.VIEW));
-
-		assertTrue(AccessRestrictionType.PUSH.toString().equals("PUSH"));
-		assertTrue(AccessRestrictionType.CLONE.toString().equals("CLONE"));
-		assertTrue(AccessRestrictionType.VIEW.toString().equals("VIEW"));
-
-		assertEquals(AccessRestrictionType.NONE, AccessRestrictionType.fromName("none"));
-		assertEquals(AccessRestrictionType.PUSH, AccessRestrictionType.fromName("push"));
-		assertEquals(AccessRestrictionType.CLONE, AccessRestrictionType.fromName("clone"));
-		assertEquals(AccessRestrictionType.VIEW, AccessRestrictionType.fromName("view"));
-	}
-
-	@Test
-	public void testFileSettings() throws Exception {
-		FileSettings settings = new FileSettings("distrib/gitblit.properties");
-		assertEquals(true, settings.getBoolean("missing", true));
-		assertEquals("default", settings.getString("missing", "default"));
-		assertEquals(10, settings.getInteger("missing", 10));
-		assertEquals(5, settings.getInteger("realm.realmFile", 5));
-
-		assertTrue(settings.getBoolean("git.enableGitServlet", false));
-		assertEquals("${baseFolder}/users.conf", settings.getString("realm.userService", null));
-		assertEquals(5, settings.getInteger("realm.minPasswordLength", 0));
-		List<String> mdExtensions = settings.getStrings("web.markdownExtensions");
-		assertTrue(mdExtensions.size() > 0);
-		assertTrue(mdExtensions.contains("md"));
-
-		List<String> keys = settings.getAllKeys("server");
-		assertTrue(keys.size() > 0);
-		assertTrue(keys.contains("server.httpsPort"));
-
-		assertTrue(settings.getChar("web.forwardSlashCharacter", ' ') == '/');
-	}
-
-	@Test
-	public void testGitblitSettings() throws Exception {
-		// These are already tested by above test method.
-		assertTrue(GitBlit.getBoolean("missing", true));
-		assertEquals("default", GitBlit.getString("missing", "default"));
-		assertEquals(10, GitBlit.getInteger("missing", 10));
-		assertEquals(5, GitBlit.getInteger("realm.userService", 5));
-
-		assertTrue(GitBlit.getBoolean("git.enableGitServlet", false));
-		assertEquals("test-users.conf", GitBlit.getString("realm.userService", null));
-		assertEquals(5, GitBlit.getInteger("realm.minPasswordLength", 0));
-		List<String> mdExtensions = GitBlit.getStrings("web.markdownExtensions");
-		assertTrue(mdExtensions.size() > 0);
-		assertTrue(mdExtensions.contains("md"));
-
-		List<String> keys = GitBlit.getAllKeys("server");
-		assertTrue(keys.size() > 0);
-		assertTrue(keys.contains("server.httpsPort"));
-
-		assertTrue(GitBlit.getChar("web.forwardSlashCharacter", ' ') == '/');
-		assertFalse(GitBlit.isDebugMode());
-	}
-
-	@Test
-	public void testAuthentication() throws Exception {
-		assertTrue(GitBlit.self().authenticate("admin", "admin".toCharArray()) != null);
-	}
-
-	@Test
-	public void testRepositories() throws Exception {
-		assertTrue(GitBlit.self().getRepository("missing") == null);
-		assertTrue(GitBlit.self().getRepositoryModel("missing") == null);
-	}
-}
diff --git a/tests/com/gitblit/tests/GitServletTest.java b/tests/com/gitblit/tests/GitServletTest.java
deleted file mode 100644
index a05b365..0000000
--- a/tests/com/gitblit/tests/GitServletTest.java
+++ /dev/null
@@ -1,775 +0,0 @@
-package com.gitblit.tests;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import java.io.BufferedWriter;
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.OutputStreamWriter;
-import java.text.MessageFormat;
-import java.util.Date;
-import java.util.List;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-import org.eclipse.jgit.api.CloneCommand;
-import org.eclipse.jgit.api.Git;
-import org.eclipse.jgit.api.ResetCommand.ResetType;
-import org.eclipse.jgit.api.errors.GitAPIException;
-import org.eclipse.jgit.lib.Constants;
-import org.eclipse.jgit.revwalk.RevCommit;
-import org.eclipse.jgit.storage.file.FileRepository;
-import org.eclipse.jgit.transport.CredentialsProvider;
-import org.eclipse.jgit.transport.PushResult;
-import org.eclipse.jgit.transport.RefSpec;
-import org.eclipse.jgit.transport.RemoteRefUpdate;
-import org.eclipse.jgit.transport.RemoteRefUpdate.Status;
-import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;
-import org.eclipse.jgit.util.FileUtils;
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-import org.junit.Test;
-
-import com.gitblit.Constants.AccessPermission;
-import com.gitblit.Constants.AccessRestrictionType;
-import com.gitblit.Constants.AuthorizationControl;
-import com.gitblit.GitBlit;
-import com.gitblit.Keys;
-import com.gitblit.models.PushLogEntry;
-import com.gitblit.models.RepositoryModel;
-import com.gitblit.models.UserModel;
-import com.gitblit.utils.ArrayUtils;
-import com.gitblit.utils.JGitUtils;
-import com.gitblit.utils.PushLogUtils;
-
-public class GitServletTest {
-
-	static File ticgitFolder = new File(GitBlitSuite.REPOSITORIES, "working/ticgit");
-	
-	static File ticgit2Folder = new File(GitBlitSuite.REPOSITORIES, "working/ticgit2");
-
-	static File jgitFolder = new File(GitBlitSuite.REPOSITORIES, "working/jgit");
-	
-	static File jgit2Folder = new File(GitBlitSuite.REPOSITORIES, "working/jgit2");
-
-	String url = GitBlitSuite.url;
-	String account = GitBlitSuite.account;
-	String password = GitBlitSuite.password;
-
-	private static final AtomicBoolean started = new AtomicBoolean(false);
-
-	@BeforeClass
-	public static void startGitblit() throws Exception {
-		started.set(GitBlitSuite.startGitblit());
-	}
-
-	@AfterClass
-	public static void stopGitblit() throws Exception {
-		if (started.get()) {
-			GitBlitSuite.stopGitblit();
-			deleteWorkingFolders();
-		}
-	}
-	
-	public static void deleteWorkingFolders() throws Exception {
-		if (ticgitFolder.exists()) {
-			GitBlitSuite.close(ticgitFolder);
-			FileUtils.delete(ticgitFolder, FileUtils.RECURSIVE);
-		}
-		if (ticgit2Folder.exists()) {
-			GitBlitSuite.close(ticgit2Folder);
-			FileUtils.delete(ticgit2Folder, FileUtils.RECURSIVE);
-		}
-		if (jgitFolder.exists()) {
-			GitBlitSuite.close(jgitFolder);
-			FileUtils.delete(jgitFolder, FileUtils.RECURSIVE);
-		}
-		if (jgit2Folder.exists()) {
-			GitBlitSuite.close(jgit2Folder);
-			FileUtils.delete(jgit2Folder, FileUtils.RECURSIVE);
-		}
-	}
-
-	@Test
-	public void testClone() throws Exception {
-		GitBlitSuite.close(ticgitFolder);
-		if (ticgitFolder.exists()) {
-			FileUtils.delete(ticgitFolder, FileUtils.RECURSIVE | FileUtils.RETRY);
-		}
-		
-		CloneCommand clone = Git.cloneRepository();
-		clone.setURI(MessageFormat.format("{0}/git/ticgit.git", url));
-		clone.setDirectory(ticgitFolder);
-		clone.setBare(false);
-		clone.setCloneAllBranches(true);
-		clone.setCredentialsProvider(new UsernamePasswordCredentialsProvider(account, password));
-		GitBlitSuite.close(clone.call());		
-		assertTrue(true);
-	}
-
-	@Test
-	public void testBogusLoginClone() throws Exception {
-		// restrict repository access
-		RepositoryModel model = GitBlit.self().getRepositoryModel("ticgit.git");
-		model.accessRestriction = AccessRestrictionType.CLONE;
-		GitBlit.self().updateRepositoryModel(model.name, model, false);
-
-		// delete any existing working folder		
-		boolean cloned = false;
-		try {
-			CloneCommand clone = Git.cloneRepository();
-			clone.setURI(MessageFormat.format("{0}/git/ticgit.git", url));
-			clone.setDirectory(ticgit2Folder);
-			clone.setBare(false);
-			clone.setCloneAllBranches(true);
-			clone.setCredentialsProvider(new UsernamePasswordCredentialsProvider("bogus", "bogus"));
-			GitBlitSuite.close(clone.call());
-			cloned = true;
-		} catch (Exception e) {
-			// swallow the exception which we expect
-		}
-
-		// restore anonymous repository access
-		model.accessRestriction = AccessRestrictionType.NONE;
-		GitBlit.self().updateRepositoryModel(model.name, model, false);
-
-		assertFalse("Bogus login cloned a repository?!", cloned);
-	}
-	
-	@Test
-	public void testUnauthorizedLoginClone() throws Exception {
-		// restrict repository access
-		RepositoryModel model = GitBlit.self().getRepositoryModel("ticgit.git");
-		model.accessRestriction = AccessRestrictionType.CLONE;
-		model.authorizationControl = AuthorizationControl.NAMED;
-		UserModel user = new UserModel("james");
-		user.password = "james";
-		GitBlit.self().updateUserModel(user.username, user, true);
-		GitBlit.self().updateRepositoryModel(model.name, model, false);
-
-		FileUtils.delete(ticgit2Folder, FileUtils.RECURSIVE);
-		
-		// delete any existing working folder		
-		boolean cloned = false;
-		try {
-			CloneCommand clone = Git.cloneRepository();
-			clone.setURI(MessageFormat.format("{0}/git/ticgit.git", url));
-			clone.setDirectory(ticgit2Folder);
-			clone.setBare(false);
-			clone.setCloneAllBranches(true);
-			clone.setCredentialsProvider(new UsernamePasswordCredentialsProvider(user.username, user.password));
-			GitBlitSuite.close(clone.call());
-			cloned = true;
-		} catch (Exception e) {
-			// swallow the exception which we expect
-		}
-
-		assertFalse("Unauthorized login cloned a repository?!", cloned);
-
-		FileUtils.delete(ticgit2Folder, FileUtils.RECURSIVE);
-		
-		// switch to authenticated
-		model.authorizationControl = AuthorizationControl.AUTHENTICATED;
-		GitBlit.self().updateRepositoryModel(model.name, model, false);
-		
-		// try clone again
-		cloned = false;
-		CloneCommand clone = Git.cloneRepository();
-		clone.setURI(MessageFormat.format("{0}/git/ticgit.git", url));
-		clone.setDirectory(ticgit2Folder);
-		clone.setBare(false);
-		clone.setCloneAllBranches(true);
-		clone.setCredentialsProvider(new UsernamePasswordCredentialsProvider(user.username, user.password));
-		GitBlitSuite.close(clone.call());
-		cloned = true;
-
-		assertTrue("Authenticated login could not clone!", cloned);
-		
-		FileUtils.delete(ticgit2Folder, FileUtils.RECURSIVE);
-		
-		// restore anonymous repository access
-		model.accessRestriction = AccessRestrictionType.NONE;
-		model.authorizationControl = AuthorizationControl.NAMED;
-		GitBlit.self().updateRepositoryModel(model.name, model, false);
-		GitBlit.self().deleteUser(user.username);
-	}
-
-	@Test
-	public void testAnonymousPush() throws Exception {
-		GitBlitSuite.close(ticgitFolder);
-		if (ticgitFolder.exists()) {
-			FileUtils.delete(ticgitFolder, FileUtils.RECURSIVE | FileUtils.RETRY);
-		}
-
-		CloneCommand clone = Git.cloneRepository();
-		clone.setURI(MessageFormat.format("{0}/git/ticgit.git", url));
-		clone.setDirectory(ticgitFolder);
-		clone.setBare(false);
-		clone.setCloneAllBranches(true);
-		clone.setCredentialsProvider(new UsernamePasswordCredentialsProvider(account, password));
-		GitBlitSuite.close(clone.call());		
-		assertTrue(true);
-		
-		Git git = Git.open(ticgitFolder);
-		File file = new File(ticgitFolder, "TODO");
-		OutputStreamWriter os = new OutputStreamWriter(new FileOutputStream(file, true), Constants.CHARSET);
-		BufferedWriter w = new BufferedWriter(os);
-		w.write("// hellol中文 " + new Date().toString() + "\n");
-		w.close();
-		git.add().addFilepattern(file.getName()).call();
-		git.commit().setMessage("test commit").call();
-		git.push().setPushAll().call();
-		GitBlitSuite.close(git);
-	}
-
-	@Test
-	public void testSubfolderPush() throws Exception {
-		GitBlitSuite.close(jgitFolder);
-		if (jgitFolder.exists()) {
-			FileUtils.delete(jgitFolder, FileUtils.RECURSIVE | FileUtils.RETRY);
-		}
-		
-		CloneCommand clone = Git.cloneRepository();
-		clone.setURI(MessageFormat.format("{0}/git/test/jgit.git", url));
-		clone.setDirectory(jgitFolder);
-		clone.setBare(false);
-		clone.setCloneAllBranches(true);
-		clone.setCredentialsProvider(new UsernamePasswordCredentialsProvider(account, password));
-		GitBlitSuite.close(clone.call());
-		assertTrue(true);
-
-		Git git = Git.open(jgitFolder);
-		File file = new File(jgitFolder, "TODO");
-		OutputStreamWriter os = new OutputStreamWriter(new FileOutputStream(file, true), Constants.CHARSET);
-		BufferedWriter w = new BufferedWriter(os);
-		w.write("// " + new Date().toString() + "\n");
-		w.close();
-		git.add().addFilepattern(file.getName()).call();
-		git.commit().setMessage("test commit").call();
-		git.push().setPushAll().call();
-		GitBlitSuite.close(git);
-	}
-	
-	@Test
-	public void testPushToFrozenRepo() throws Exception {
-		GitBlitSuite.close(jgitFolder);
-		if (jgitFolder.exists()) {
-			FileUtils.delete(jgitFolder, FileUtils.RECURSIVE | FileUtils.RETRY);
-		}
-		
-		CloneCommand clone = Git.cloneRepository();
-		clone.setURI(MessageFormat.format("{0}/git/test/jgit.git", url));
-		clone.setDirectory(jgitFolder);
-		clone.setBare(false);
-		clone.setCloneAllBranches(true);
-		clone.setCredentialsProvider(new UsernamePasswordCredentialsProvider(account, password));
-		GitBlitSuite.close(clone.call());
-		assertTrue(true);
-		
-		// freeze repo
-		RepositoryModel model = GitBlit.self().getRepositoryModel("test/jgit.git");
-		model.isFrozen = true;
-		GitBlit.self().updateRepositoryModel(model.name, model, false);
-
-		Git git = Git.open(jgitFolder);
-		File file = new File(jgitFolder, "TODO");
-		OutputStreamWriter os = new OutputStreamWriter(new FileOutputStream(file, true), Constants.CHARSET);
-		BufferedWriter w = new BufferedWriter(os);
-		w.write("// " + new Date().toString() + "\n");
-		w.close();
-		git.add().addFilepattern(file.getName()).call();
-		git.commit().setMessage("test commit").call();
-		
-		try {
-			git.push().setPushAll().call();
-			assertTrue(false);
-		} catch (Exception e) {
-			assertTrue(e.getCause().getMessage().contains("access forbidden"));
-		}
-		
-		// unfreeze repo
-		model.isFrozen = false;
-		GitBlit.self().updateRepositoryModel(model.name, model, false);
-
-		git.push().setPushAll().call();
-		GitBlitSuite.close(git);
-	}
-	
-	@Test
-	public void testPushToNonBareRepository() throws Exception {
-		CloneCommand clone = Git.cloneRepository();
-		clone.setURI(MessageFormat.format("{0}/git/working/jgit", url));
-		clone.setDirectory(jgit2Folder);
-		clone.setBare(false);
-		clone.setCloneAllBranches(true);
-		clone.setCredentialsProvider(new UsernamePasswordCredentialsProvider(account, password));
-		GitBlitSuite.close(clone.call());
-		assertTrue(true);
-
-		Git git = Git.open(jgit2Folder);
-		File file = new File(jgit2Folder, "NONBARE");
-		OutputStreamWriter os = new OutputStreamWriter(new FileOutputStream(file, true), Constants.CHARSET);
-		BufferedWriter w = new BufferedWriter(os);
-		w.write("// " + new Date().toString() + "\n");
-		w.close();
-		git.add().addFilepattern(file.getName()).call();
-		git.commit().setMessage("test commit followed by push to non-bare repository").call();
-		try {
-			git.push().setPushAll().call();
-			assertTrue(false);
-		} catch (Exception e) {
-			assertTrue(e.getCause().getMessage().contains("git-receive-pack not permitted"));
-		}
-		GitBlitSuite.close(git);
-	}
-
-	@Test
-	public void testCommitterVerification() throws Exception {
-		UserModel user = new UserModel("james");
-		user.password = "james";
-
-		// account only uses account name to verify
-		testCommitterVerification(user, user.username, null, true);
-		// committer email address is ignored because account does not specify email
-		testCommitterVerification(user, user.username, "something", true);
-		// completely different committer
-		testCommitterVerification(user, "joe", null, false);
-
-		// test display name verification
-		user.displayName = "James Moger";
-		testCommitterVerification(user, user.displayName, null, true);
-		testCommitterVerification(user, user.displayName, "something", true);
-		testCommitterVerification(user, "joe", null, false);
-		
-		// test email address verification
-		user.emailAddress = "something";
-		testCommitterVerification(user, user.displayName, null, false);
-		testCommitterVerification(user, user.displayName, "somethingelse", false);
-		testCommitterVerification(user, user.displayName, user.emailAddress, true);
-		
-		// use same email address but with different committer
-		testCommitterVerification(user, "joe", "somethingelse", false);
-	}
-	
-	private void testCommitterVerification(UserModel user, String displayName, String emailAddress, boolean expectedSuccess) throws Exception {
-		
-		if (GitBlit.self().getUserModel(user.username) != null) {
-			GitBlit.self().deleteUser(user.username);
-		}
-		
-		CredentialsProvider cp = new UsernamePasswordCredentialsProvider(user.username, user.password);
-		
-		// fork from original to a temporary bare repo
-		File verification = new File(GitBlitSuite.REPOSITORIES, "refchecks/verify-committer.git");
-		if (verification.exists()) {
-			FileUtils.delete(verification, FileUtils.RECURSIVE);
-		}
-		CloneCommand clone = Git.cloneRepository();
-		clone.setURI(MessageFormat.format("{0}/git/ticgit.git", url));
-		clone.setDirectory(verification);
-		clone.setBare(true);
-		clone.setCloneAllBranches(true);
-		clone.setCredentialsProvider(cp);
-		GitBlitSuite.close(clone.call());
-		
-		// require push permissions and committer verification
-		RepositoryModel model = GitBlit.self().getRepositoryModel("refchecks/verify-committer.git");
-		model.authorizationControl = AuthorizationControl.NAMED;
-		model.accessRestriction = AccessRestrictionType.PUSH;
-		model.verifyCommitter = true;
-		
-		// grant user push permission
-		user.setRepositoryPermission(model.name, AccessPermission.PUSH);
-		
-		GitBlit.self().updateUserModel(user.username, user, true);
-		GitBlit.self().updateRepositoryModel(model.name, model, false);
-
-		// clone temp bare repo to working copy
-		File local = new File(GitBlitSuite.REPOSITORIES, "refchecks/verify-wc");
-		if (local.exists()) {
-			FileUtils.delete(local, FileUtils.RECURSIVE);
-		}
-		clone = Git.cloneRepository();
-		clone.setURI(MessageFormat.format("{0}/git/{1}", url, model.name));
-		clone.setDirectory(local);
-		clone.setBare(false);
-		clone.setCloneAllBranches(true);
-		clone.setCredentialsProvider(cp);
-		GitBlitSuite.close(clone.call());
-		
-		Git git = Git.open(local);
-		
-		// force an identity which may or may not match the account's identity
-		git.getRepository().getConfig().setString("user", null, "name", displayName);
-		git.getRepository().getConfig().setString("user", null, "email", emailAddress);
-		git.getRepository().getConfig().save();
-		
-		// commit a file and push it
-		File file = new File(local, "PUSHCHK");
-		OutputStreamWriter os = new OutputStreamWriter(new FileOutputStream(file, true), Constants.CHARSET);
-		BufferedWriter w = new BufferedWriter(os);
-		w.write("// " + new Date().toString() + "\n");
-		w.close();
-		git.add().addFilepattern(file.getName()).call();
-		git.commit().setMessage("push test").call();
-		Iterable<PushResult> results = git.push().setCredentialsProvider(cp).setRemote("origin").call();
-		
-		for (PushResult result : results) {
-			RemoteRefUpdate ref = result.getRemoteUpdate("refs/heads/master");
-			Status status = ref.getStatus();
-			if (expectedSuccess) {
-				assertTrue("Verification failed! User was NOT able to push commit! " + status.name(), Status.OK.equals(status));
-			} else {
-				assertTrue("Verification failed! User was able to push commit! " + status.name(), Status.REJECTED_OTHER_REASON.equals(status));
-			}
-		}
-		
-		GitBlitSuite.close(git);
-		// close serving repository
-		GitBlitSuite.close(verification);
-	}
-
-	@Test
-	public void testBlockClone() throws Exception {
-		testRefChange(AccessPermission.VIEW, null, null, null);
-	}
-
-	@Test
-	public void testBlockPush() throws Exception {
-		testRefChange(AccessPermission.CLONE, null, null, null);
-	}
-
-	@Test
-	public void testBlockBranchCreation() throws Exception {
-		testRefChange(AccessPermission.PUSH, Status.REJECTED_OTHER_REASON, null, null);
-	}
-
-	@Test
-	public void testBlockBranchDeletion() throws Exception {
-		testRefChange(AccessPermission.CREATE, Status.OK, Status.REJECTED_OTHER_REASON, null);
-	}
-	
-	@Test
-	public void testBlockBranchRewind() throws Exception {
-		testRefChange(AccessPermission.DELETE, Status.OK, Status.OK, Status.REJECTED_OTHER_REASON);
-	}
-
-	@Test
-	public void testBranchRewind() throws Exception {		
-		testRefChange(AccessPermission.REWIND, Status.OK, Status.OK, Status.OK);
-	}
-
-	private void testRefChange(AccessPermission permission, Status expectedCreate, Status expectedDelete, Status expectedRewind) throws Exception {
-
-		UserModel user = new UserModel("james");
-		user.password = "james";
-		
-		if (GitBlit.self().getUserModel(user.username) != null) {
-			GitBlit.self().deleteUser(user.username);
-		}
-		
-		CredentialsProvider cp = new UsernamePasswordCredentialsProvider(user.username, user.password);
-		
-		// fork from original to a temporary bare repo
-		File refChecks = new File(GitBlitSuite.REPOSITORIES, "refchecks/ticgit.git");
-		if (refChecks.exists()) {
-			FileUtils.delete(refChecks, FileUtils.RECURSIVE);
-		}
-		CloneCommand clone = Git.cloneRepository();
-		clone.setURI(MessageFormat.format("{0}/git/ticgit.git", url));
-		clone.setDirectory(refChecks);
-		clone.setBare(true);
-		clone.setCloneAllBranches(true);
-		clone.setCredentialsProvider(cp);
-		GitBlitSuite.close(clone.call());
-
-		// elevate repository to clone permission
-		RepositoryModel model = GitBlit.self().getRepositoryModel("refchecks/ticgit.git");
-		switch (permission) {
-			case VIEW:
-				model.accessRestriction = AccessRestrictionType.CLONE;
-				break;
-			case CLONE:
-				model.accessRestriction = AccessRestrictionType.CLONE;
-				break;
-			default:
-				model.accessRestriction = AccessRestrictionType.PUSH;
-		}
-		model.authorizationControl = AuthorizationControl.NAMED;
-		
-		// grant user specified
-		user.setRepositoryPermission(model.name, permission);
-
-		GitBlit.self().updateUserModel(user.username, user, true);
-		GitBlit.self().updateRepositoryModel(model.name, model, false);
-
-		// clone temp bare repo to working copy
-		File local = new File(GitBlitSuite.REPOSITORIES, "refchecks/ticgit-wc");
-		if (local.exists()) {
-			FileUtils.delete(local, FileUtils.RECURSIVE);
-		}
-		clone = Git.cloneRepository();
-		clone.setURI(MessageFormat.format("{0}/git/{1}", url, model.name));
-		clone.setDirectory(local);
-		clone.setBare(false);
-		clone.setCloneAllBranches(true);
-		clone.setCredentialsProvider(cp);
-		
-		try {
-			GitBlitSuite.close(clone.call());
-		} catch (GitAPIException e) {
-			if (permission.atLeast(AccessPermission.CLONE)) {
-				throw e;
-			} else {
-				// close serving repository
-				GitBlitSuite.close(refChecks);
-				
-				// user does not have clone permission
-				assertTrue(e.getMessage(), e.getMessage().contains("not permitted"));	
-				return;
-			}
-		}
-		
-		Git git = Git.open(local);
-		
-		// commit a file and push it
-		File file = new File(local, "PUSHCHK");
-		OutputStreamWriter os = new OutputStreamWriter(new FileOutputStream(file, true), Constants.CHARSET);
-		BufferedWriter w = new BufferedWriter(os);
-		w.write("// " + new Date().toString() + "\n");
-		w.close();
-		git.add().addFilepattern(file.getName()).call();
-		git.commit().setMessage("push test").call();
-		Iterable<PushResult> results = null;
-		try {
-			results = git.push().setCredentialsProvider(cp).setRemote("origin").call();
-		} catch (GitAPIException e) {
-			if (permission.atLeast(AccessPermission.PUSH)) {
-				throw e;
-			} else {
-				// close serving repository
-				GitBlitSuite.close(refChecks);
-				
-				// user does not have push permission
-				assertTrue(e.getMessage(), e.getMessage().contains("not permitted"));
-				GitBlitSuite.close(git);
-				return;
-			}
-		}
-		
-		for (PushResult result : results) {
-			RemoteRefUpdate ref = result.getRemoteUpdate("refs/heads/master");
-			Status status = ref.getStatus();
-			if (permission.atLeast(AccessPermission.PUSH)) {
-				assertTrue("User failed to push commit?! " + status.name(), Status.OK.equals(status));
-			} else {
-				// close serving repository
-				GitBlitSuite.close(refChecks);
-
-				assertTrue("User was able to push commit! " + status.name(), Status.REJECTED_OTHER_REASON.equals(status));
-				GitBlitSuite.close(git);
-				// skip delete test
-				return;
-			}
-		}
-		
-		// create a local branch and push the new branch back to the origin				
-		git.branchCreate().setName("protectme").call();
-		RefSpec refSpec = new RefSpec("refs/heads/protectme:refs/heads/protectme");
-		results = git.push().setCredentialsProvider(cp).setRefSpecs(refSpec).setRemote("origin").call();
-		for (PushResult result : results) {
-			RemoteRefUpdate ref = result.getRemoteUpdate("refs/heads/protectme");
-			Status status = ref.getStatus();
-			if (Status.OK.equals(expectedCreate)) {
-				assertTrue("User failed to push creation?! " + status.name(), status.equals(expectedCreate));
-			} else {
-				// close serving repository
-				GitBlitSuite.close(refChecks);
-
-				assertTrue("User was able to push ref creation! " + status.name(), status.equals(expectedCreate));
-				GitBlitSuite.close(git);
-				// skip delete test
-				return;
-			}
-		}
-		
-		// delete the branch locally
-		git.branchDelete().setBranchNames("protectme").call();
-		
-		// push a delete ref command
-		refSpec = new RefSpec(":refs/heads/protectme");
-		results = git.push().setCredentialsProvider(cp).setRefSpecs(refSpec).setRemote("origin").call();
-		for (PushResult result : results) {
-			RemoteRefUpdate ref = result.getRemoteUpdate("refs/heads/protectme");
-			Status status = ref.getStatus();
-			if (Status.OK.equals(expectedDelete)) {
-				assertTrue("User failed to push ref deletion?! " + status.name(), status.equals(Status.OK));
-			} else {
-				// close serving repository
-				GitBlitSuite.close(refChecks);
-
-				assertTrue("User was able to push ref deletion?! " + status.name(), status.equals(expectedDelete));
-				GitBlitSuite.close(git);
-				// skip rewind test
-				return;
-			}
-		}
-		
-		// rewind master by two commits
-		git.reset().setRef("HEAD~2").setMode(ResetType.HARD).call();
-		
-		// commit a change on this detached HEAD
-		file = new File(local, "REWINDCHK");
-		os = new OutputStreamWriter(new FileOutputStream(file, true), Constants.CHARSET);
-		w = new BufferedWriter(os);
-		w.write("// " + new Date().toString() + "\n");
-		w.close();
-		git.add().addFilepattern(file.getName()).call();
-		RevCommit commit = git.commit().setMessage("rewind master and new commit").call();
-		
-		// Reset master to our new commit now we our local branch tip is no longer
-		// upstream of the remote branch tip.  It is an alternate tip of the branch.
-		JGitUtils.setBranchRef(git.getRepository(), "refs/heads/master", commit.getName());
-		
-		// Try pushing our new tip to the origin.
-		// This requires the server to "rewind" it's master branch and update it
-		// to point to our alternate tip.  This leaves the original master tip
-		// unreferenced.
-		results = git.push().setCredentialsProvider(cp).setRemote("origin").setForce(true).call();
-		for (PushResult result : results) {
-			RemoteRefUpdate ref = result.getRemoteUpdate("refs/heads/master");
-			Status status = ref.getStatus();
-			if (Status.OK.equals(expectedRewind)) {
-				assertTrue("User failed to rewind master?! " + status.name(), status.equals(expectedRewind));
-			} else {
-				assertTrue("User was able to rewind master?! " + status.name(), status.equals(expectedRewind));
-			}
-		}
-		GitBlitSuite.close(git);
-		
-		// close serving repository
-		GitBlitSuite.close(refChecks);
-
-		GitBlit.self().deleteUser(user.username);
-	}
-	
-	@Test
-	public void testCreateOnPush() throws Exception {
-		testCreateOnPush(false, false);
-		testCreateOnPush(true, false);
-		testCreateOnPush(false, true);
-	}
-	
-	private void testCreateOnPush(boolean canCreate, boolean canAdmin) throws Exception {
-
-		UserModel user = new UserModel("sampleuser");
-		user.password = user.username;
-		
-		if (GitBlit.self().getUserModel(user.username) != null) {
-			GitBlit.self().deleteUser(user.username);
-		}
-		
-		user.canCreate = canCreate;
-		user.canAdmin = canAdmin;
-		
-		GitBlit.self().updateUserModel(user.username, user, true);
-
-		CredentialsProvider cp = new UsernamePasswordCredentialsProvider(user.username, user.password);
-		
-		// fork from original to a temporary bare repo
-		File tmpFolder = File.createTempFile("gitblit", "").getParentFile();
-		File createCheck = new File(tmpFolder, "ticgit.git");
-		if (createCheck.exists()) {
-			FileUtils.delete(createCheck, FileUtils.RECURSIVE);
-		}
-		
-		File personalRepo = new File(GitBlitSuite.REPOSITORIES, MessageFormat.format("~{0}/ticgit.git", user.username));
-		GitBlitSuite.close(personalRepo);
-		if (personalRepo.exists()) {
-			FileUtils.delete(personalRepo, FileUtils.RECURSIVE);
-		}
-
-		File projectRepo = new File(GitBlitSuite.REPOSITORIES, "project/ticgit.git");
-		GitBlitSuite.close(projectRepo);
-		if (projectRepo.exists()) {
-			FileUtils.delete(projectRepo, FileUtils.RECURSIVE);
-		}
-
-		CloneCommand clone = Git.cloneRepository();
-		clone.setURI(MessageFormat.format("{0}/git/ticgit.git", url));
-		clone.setDirectory(createCheck);
-		clone.setBare(true);
-		clone.setCloneAllBranches(true);
-		clone.setCredentialsProvider(cp);
-		Git git = clone.call();
-		
-		GitBlitSuite.close(personalRepo);
-		
-		// add a personal repository remote and a project remote
-		git.getRepository().getConfig().setString("remote", "user", "url", MessageFormat.format("{0}/git/~{1}/ticgit.git", url, user.username));
-		git.getRepository().getConfig().setString("remote", "project", "url", MessageFormat.format("{0}/git/project/ticgit.git", url));
-		git.getRepository().getConfig().save();
-
-		// push to non-existent user repository
-		try {
-			Iterable<PushResult> results = git.push().setRemote("user").setPushAll().setCredentialsProvider(cp).call();
-
-			for (PushResult result : results) {
-				RemoteRefUpdate ref = result.getRemoteUpdate("refs/heads/master");
-				Status status = ref.getStatus();
-				assertTrue("User failed to create repository?! " + status.name(), Status.OK.equals(status));
-			}
-
-			assertTrue("User canAdmin:" + user.canAdmin + " canCreate:" + user.canCreate, user.canAdmin || user.canCreate);
-			
-			// confirm default personal repository permissions
-			RepositoryModel model = GitBlit.self().getRepositoryModel(MessageFormat.format("~{0}/ticgit.git", user.username));
-			assertEquals("Unexpected owner", user.username, ArrayUtils.toString(model.owners));
-			assertEquals("Unexpected authorization control", AuthorizationControl.NAMED, model.authorizationControl);
-			assertEquals("Unexpected access restriction", AccessRestrictionType.VIEW, model.accessRestriction);
-			
-		} catch (GitAPIException e) {
-			assertTrue(e.getMessage(), e.getMessage().contains("git-receive-pack not found"));
-			assertFalse("User canAdmin:" + user.canAdmin + " canCreate:" + user.canCreate, user.canAdmin || user.canCreate);
-		}
-		
-		// push to non-existent project repository
-		try {
-			Iterable<PushResult> results = git.push().setRemote("project").setPushAll().setCredentialsProvider(cp).call();
-			GitBlitSuite.close(git);
-
-			for (PushResult result : results) {
-				RemoteRefUpdate ref = result.getRemoteUpdate("refs/heads/master");
-				Status status = ref.getStatus();
-				assertTrue("User failed to create repository?! " + status.name(), Status.OK.equals(status));
-			}
-			
-			assertTrue("User canAdmin:" + user.canAdmin, user.canAdmin);
-			
-			// confirm default project repository permissions
-			RepositoryModel model = GitBlit.self().getRepositoryModel("project/ticgit.git");
-			assertEquals("Unexpected owner", user.username, ArrayUtils.toString(model.owners));
-			assertEquals("Unexpected authorization control", AuthorizationControl.fromName(GitBlit.getString(Keys.git.defaultAuthorizationControl, "NAMED")), model.authorizationControl);
-			assertEquals("Unexpected access restriction", AccessRestrictionType.fromName(GitBlit.getString(Keys.git.defaultAccessRestriction, "NONE")), model.accessRestriction);
-
-		} catch (GitAPIException e) {
-			assertTrue(e.getMessage(), e.getMessage().contains("git-receive-pack not found"));
-			assertFalse("User canAdmin:" + user.canAdmin, user.canAdmin);
-		}
-
-		GitBlitSuite.close(git);
-		GitBlit.self().deleteUser(user.username);
-	}
-	
-	@Test
-	public void testPushLog() throws IOException {
-		String name = "refchecks/ticgit.git";
-		File refChecks = new File(GitBlitSuite.REPOSITORIES, name);
-		FileRepository repository = new FileRepository(refChecks);
-		List<PushLogEntry> pushes = PushLogUtils.getPushLog(name, repository);
-		GitBlitSuite.close(repository);
-		assertTrue("Repository has an empty push log!", pushes.size() > 0);
-	}
-}
diff --git a/tests/com/gitblit/tests/JGitUtilsTest.java b/tests/com/gitblit/tests/JGitUtilsTest.java
deleted file mode 100644
index ce72a46..0000000
--- a/tests/com/gitblit/tests/JGitUtilsTest.java
+++ /dev/null
@@ -1,472 +0,0 @@
-/*
- * Copyright 2011 gitblit.com.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.gitblit.tests;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.text.SimpleDateFormat;
-import java.util.Arrays;
-import java.util.Date;
-import java.util.List;
-import java.util.Map;
-
-import org.eclipse.jgit.diff.DiffEntry.ChangeType;
-import org.eclipse.jgit.lib.Constants;
-import org.eclipse.jgit.lib.FileMode;
-import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.PersonIdent;
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.lib.RepositoryCache;
-import org.eclipse.jgit.lib.RepositoryCache.FileKey;
-import org.eclipse.jgit.revwalk.RevCommit;
-import org.eclipse.jgit.revwalk.RevTree;
-import org.eclipse.jgit.util.FS;
-import org.eclipse.jgit.util.FileUtils;
-import org.junit.Test;
-
-import com.gitblit.Constants.SearchType;
-import com.gitblit.GitBlit;
-import com.gitblit.Keys;
-import com.gitblit.models.GitNote;
-import com.gitblit.models.PathModel;
-import com.gitblit.models.PathModel.PathChangeModel;
-import com.gitblit.models.RefModel;
-import com.gitblit.utils.CompressionUtils;
-import com.gitblit.utils.JGitUtils;
-import com.gitblit.utils.StringUtils;
-
-public class JGitUtilsTest {
-
-	@Test
-	public void testDisplayName() throws Exception {
-		assertEquals("Napoleon Bonaparte",
-				JGitUtils.getDisplayName(new PersonIdent("Napoleon Bonaparte", "")));
-		assertEquals("<someone@somewhere.com>",
-				JGitUtils.getDisplayName(new PersonIdent("", "someone@somewhere.com")));
-		assertEquals("Napoleon Bonaparte <someone@somewhere.com>",
-				JGitUtils.getDisplayName(new PersonIdent("Napoleon Bonaparte",
-						"someone@somewhere.com")));
-	}
-
-	@Test
-	public void testFindRepositories() {
-		List<String> list = JGitUtils.getRepositoryList(null, false, true, -1, null);
-		assertEquals(0, list.size());
-		list.addAll(JGitUtils.getRepositoryList(new File("DoesNotExist"), true, true, -1, null));
-		assertEquals(0, list.size());
-		list.addAll(JGitUtils.getRepositoryList(GitBlitSuite.REPOSITORIES, false, true, -1, null));
-		assertTrue("No repositories found in " + GitBlitSuite.REPOSITORIES, list.size() > 0);
-	}
-
-	@Test
-	public void testFindExclusions() {
-		List<String> list = JGitUtils.getRepositoryList(GitBlitSuite.REPOSITORIES, false, true, -1, null);
-		assertTrue("Missing jgit repository?!", list.contains("test/jgit.git"));
-
-		list = JGitUtils.getRepositoryList(GitBlitSuite.REPOSITORIES, false, true, -1, Arrays.asList("test/jgit\\.git"));
-		assertFalse("Repository exclusion failed!", list.contains("test/jgit.git"));
-
-		list = JGitUtils.getRepositoryList(GitBlitSuite.REPOSITORIES, false, true, -1, Arrays.asList("test/*"));
-		assertFalse("Repository exclusion failed!", list.contains("test/jgit.git"));
-
-		list = JGitUtils.getRepositoryList(GitBlitSuite.REPOSITORIES, false, true, -1, Arrays.asList(".*jgit.*"));
-		assertFalse("Repository exclusion failed!", list.contains("test/jgit.git"));
-		assertFalse("Repository exclusion failed!", list.contains("working/jgit"));
-		assertFalse("Repository exclusion failed!", list.contains("working/jgit2"));
-
-	}
-
-	@Test
-	public void testOpenRepository() throws Exception {
-		Repository repository = GitBlitSuite.getHelloworldRepository();
-		repository.close();
-		assertNotNull("Could not find repository!", repository);
-	}
-
-	@Test
-	public void testFirstCommit() throws Exception {
-		assertEquals(new Date(0), JGitUtils.getFirstChange(null, null));
-
-		Repository repository = GitBlitSuite.getHelloworldRepository();
-		RevCommit commit = JGitUtils.getFirstCommit(repository, null);
-		Date firstChange = JGitUtils.getFirstChange(repository, null);
-		repository.close();
-		assertNotNull("Could not get first commit!", commit);
-		assertEquals("Incorrect first commit!", "f554664a346629dc2b839f7292d06bad2db4aece",
-				commit.getName());
-		assertTrue(firstChange.equals(new Date(commit.getCommitTime() * 1000L)));
-	}
-
-	@Test
-	public void testLastCommit() throws Exception {
-		assertEquals(new Date(0), JGitUtils.getLastChange(null));
-
-		Repository repository = GitBlitSuite.getHelloworldRepository();
-		assertTrue(JGitUtils.getCommit(repository, null) != null);
-		Date date = JGitUtils.getLastChange(repository);
-		repository.close();
-		assertNotNull("Could not get last repository change date!", date);
-	}
-
-	@Test
-	public void testCreateRepository() throws Exception {
-		String[] repositories = { "NewTestRepository.git", "NewTestRepository" };
-		for (String repositoryName : repositories) {
-			Repository repository = JGitUtils.createRepository(GitBlitSuite.REPOSITORIES,
-					repositoryName);
-			File folder = FileKey.resolve(new File(GitBlitSuite.REPOSITORIES, repositoryName),
-					FS.DETECTED);
-			assertNotNull(repository);
-			assertFalse(JGitUtils.hasCommits(repository));
-			assertNull(JGitUtils.getFirstCommit(repository, null));
-			assertEquals(folder.lastModified(), JGitUtils.getFirstChange(repository, null)
-					.getTime());
-			assertEquals(folder.lastModified(), JGitUtils.getLastChange(repository).getTime());
-			assertNull(JGitUtils.getCommit(repository, null));
-			repository.close();
-			RepositoryCache.close(repository);
-			FileUtils.delete(repository.getDirectory(), FileUtils.RECURSIVE);
-		}
-	}
-
-	@Test
-	public void testRefs() throws Exception {
-		Repository repository = GitBlitSuite.getJGitRepository();
-		Map<ObjectId, List<RefModel>> map = JGitUtils.getAllRefs(repository);
-		repository.close();
-		assertTrue(map.size() > 0);
-		for (Map.Entry<ObjectId, List<RefModel>> entry : map.entrySet()) {
-			List<RefModel> list = entry.getValue();
-			for (RefModel ref : list) {
-				if (ref.displayName.equals("refs/tags/spearce-gpg-pub")) {
-					assertEquals("refs/tags/spearce-gpg-pub", ref.toString());
-					assertEquals("8bbde7aacf771a9afb6992434f1ae413e010c6d8", ref.getObjectId()
-							.getName());
-					assertEquals("spearce@spearce.org", ref.getAuthorIdent().getEmailAddress());
-					assertTrue(ref.getShortMessage().startsWith("GPG key"));
-					assertTrue(ref.getFullMessage().startsWith("GPG key"));
-					assertEquals(Constants.OBJ_BLOB, ref.getReferencedObjectType());
-				} else if (ref.displayName.equals("refs/tags/v0.12.1")) {
-					assertTrue(ref.isAnnotatedTag());
-				}
-			}
-		}
-	}
-
-	@Test
-	public void testBranches() throws Exception {
-		Repository repository = GitBlitSuite.getJGitRepository();
-		assertTrue(JGitUtils.getLocalBranches(repository, true, 0).size() == 0);
-		for (RefModel model : JGitUtils.getLocalBranches(repository, true, -1)) {
-			assertTrue(model.getName().startsWith(Constants.R_HEADS));
-			assertTrue(model.equals(model));
-			assertFalse(model.equals(""));
-			assertTrue(model.hashCode() == model.getReferencedObjectId().hashCode()
-					+ model.getName().hashCode());
-			assertTrue(model.getShortMessage().equals(model.getShortMessage()));
-		}
-		for (RefModel model : JGitUtils.getRemoteBranches(repository, true, -1)) {
-			assertTrue(model.getName().startsWith(Constants.R_REMOTES));
-			assertTrue(model.equals(model));
-			assertFalse(model.equals(""));
-			assertTrue(model.hashCode() == model.getReferencedObjectId().hashCode()
-					+ model.getName().hashCode());
-			assertTrue(model.getShortMessage().equals(model.getShortMessage()));
-		}
-		assertTrue(JGitUtils.getRemoteBranches(repository, true, 8).size() == 8);
-		repository.close();
-	}
-
-	@Test
-	public void testTags() throws Exception {
-		Repository repository = GitBlitSuite.getJGitRepository();
-		assertTrue(JGitUtils.getTags(repository, true, 5).size() == 5);
-		for (RefModel model : JGitUtils.getTags(repository, true, -1)) {
-			if (model.getObjectId().getName().equals("d28091fb2977077471138fe97da1440e0e8ae0da")) {
-				assertTrue("Not an annotated tag!", model.isAnnotatedTag());
-			}
-			assertTrue(model.getName().startsWith(Constants.R_TAGS));
-			assertTrue(model.equals(model));
-			assertFalse(model.equals(""));
-			assertTrue(model.hashCode() == model.getReferencedObjectId().hashCode()
-					+ model.getName().hashCode());
-		}
-		repository.close();
-
-		repository = GitBlitSuite.getGitectiveRepository();
-		for (RefModel model : JGitUtils.getTags(repository, true, -1)) {
-			if (model.getObjectId().getName().equals("035254295a9bba11f72b1f9d6791a6b957abee7b")) {
-				assertFalse(model.isAnnotatedTag());
-				assertTrue(model.getAuthorIdent().getEmailAddress().equals("kevinsawicki@gmail.com"));
-				assertEquals("Add scm and issue tracker elements to pom.xml\n", model.getFullMessage());
-			}
-		}
-		repository.close();
-	}
-
-	@Test
-	public void testCommitNotes() throws Exception {
-		Repository repository = GitBlitSuite.getJGitRepository();
-		RevCommit commit = JGitUtils.getCommit(repository,
-				"690c268c793bfc218982130fbfc25870f292295e");
-		List<GitNote> list = JGitUtils.getNotesOnCommit(repository, commit);
-		repository.close();
-		assertTrue(list.size() > 0);
-		assertEquals("183474d554e6f68478a02d9d7888b67a9338cdff", list.get(0).notesRef
-				.getReferencedObjectId().getName());
-	}
-	
-	@Test
-	public void testRelinkHEAD() throws Exception {
-		Repository repository = GitBlitSuite.getJGitRepository();
-		// confirm HEAD is master
-		String currentRef = JGitUtils.getHEADRef(repository);
-		assertEquals("refs/heads/master", currentRef);
-		List<String> availableHeads = JGitUtils.getAvailableHeadTargets(repository);
-		assertTrue(availableHeads.size() > 0);
-		
-		// set HEAD to stable-1.2
-		JGitUtils.setHEADtoRef(repository, "refs/heads/stable-1.2");
-		currentRef = JGitUtils.getHEADRef(repository);
-		assertEquals("refs/heads/stable-1.2", currentRef);
-
-		// restore HEAD to master
-		JGitUtils.setHEADtoRef(repository, "refs/heads/master");
-		currentRef = JGitUtils.getHEADRef(repository);
-		assertEquals("refs/heads/master", currentRef);
-		
-		repository.close();
-	}
-
-	@Test
-	public void testRelinkBranch() throws Exception {
-		Repository repository = GitBlitSuite.getJGitRepository();
-		
-		// create/set the branch
-		JGitUtils.setBranchRef(repository, "refs/heads/reftest", "3b358ce514ec655d3ff67de1430994d8428cdb04");
-		assertEquals(1, JGitUtils.getAllRefs(repository).get(ObjectId.fromString("3b358ce514ec655d3ff67de1430994d8428cdb04")).size());
-		assertEquals(null, JGitUtils.getAllRefs(repository).get(ObjectId.fromString("755dfdb40948f5c1ec79e06bde3b0a78c352f27f")));
-		
-		// reset the branch
-		JGitUtils.setBranchRef(repository, "refs/heads/reftest", "755dfdb40948f5c1ec79e06bde3b0a78c352f27f");
-		assertEquals(null, JGitUtils.getAllRefs(repository).get(ObjectId.fromString("3b358ce514ec655d3ff67de1430994d8428cdb04")));
-		assertEquals(1, JGitUtils.getAllRefs(repository).get(ObjectId.fromString("755dfdb40948f5c1ec79e06bde3b0a78c352f27f")).size());
-
-		// delete the branch
-		assertTrue(JGitUtils.deleteBranchRef(repository, "refs/heads/reftest"));
-		repository.close();
-	}
-
-	@Test
-	public void testCreateOrphanedBranch() throws Exception {
-		Repository repository = JGitUtils.createRepository(GitBlitSuite.REPOSITORIES, "orphantest");
-		assertTrue(JGitUtils.createOrphanBranch(repository,
-				"x" + Long.toHexString(System.currentTimeMillis()).toUpperCase(), null));
-		 FileUtils.delete(repository.getDirectory(), FileUtils.RECURSIVE);
-	}
-
-	@Test
-	public void testStringContent() throws Exception {
-		Repository repository = GitBlitSuite.getHelloworldRepository();
-		String contentA = JGitUtils.getStringContent(repository, (RevTree) null, "java.java");
-		RevCommit commit = JGitUtils.getCommit(repository, Constants.HEAD);
-		String contentB = JGitUtils.getStringContent(repository, commit.getTree(), "java.java");
-		String contentC = JGitUtils.getStringContent(repository, commit.getTree(), "missing.txt");
-
-		// manually construct a blob, calculate the hash, lookup the hash in git
-		StringBuilder sb = new StringBuilder();
-		sb.append("blob ").append(contentA.length()).append('\0');
-		sb.append(contentA);
-		String sha1 = StringUtils.getSHA1(sb.toString());
-		String contentD = JGitUtils.getStringContent(repository, sha1);
-		repository.close();
-		assertTrue("ContentA is null!", contentA != null && contentA.length() > 0);
-		assertTrue("ContentB is null!", contentB != null && contentB.length() > 0);
-		assertTrue(contentA.equals(contentB));
-		assertNull(contentC);
-		assertTrue(contentA.equals(contentD));
-	}
-
-	@Test
-	public void testFilesInCommit() throws Exception {
-		Repository repository = GitBlitSuite.getHelloworldRepository();
-		RevCommit commit = JGitUtils.getCommit(repository,
-				"1d0c2933a4ae69c362f76797d42d6bd182d05176");
-		List<PathChangeModel> paths = JGitUtils.getFilesInCommit(repository, commit);
-
-		commit = JGitUtils.getCommit(repository, "af0e9b2891fda85afc119f04a69acf7348922830");
-		List<PathChangeModel> deletions = JGitUtils.getFilesInCommit(repository, commit);
-
-		commit = JGitUtils.getFirstCommit(repository, null);
-		List<PathChangeModel> additions = JGitUtils.getFilesInCommit(repository, commit);
-
-		List<PathChangeModel> latestChanges = JGitUtils.getFilesInCommit(repository, null);
-
-		repository.close();
-		assertTrue("No changed paths found!", paths.size() == 1);
-		for (PathChangeModel path : paths) {
-			assertTrue("PathChangeModel hashcode incorrect!",
-					path.hashCode() == (path.commitId.hashCode() + path.path.hashCode()));
-			assertTrue("PathChangeModel equals itself failed!", path.equals(path));
-			assertFalse("PathChangeModel equals string failed!", path.equals(""));
-		}
-		assertEquals(ChangeType.DELETE, deletions.get(0).changeType);
-		assertEquals(ChangeType.ADD, additions.get(0).changeType);
-		assertTrue(latestChanges.size() > 0);
-	}
-
-	@Test
-	public void testFilesInPath() throws Exception {
-		assertEquals(0, JGitUtils.getFilesInPath(null, null, null).size());
-		Repository repository = GitBlitSuite.getHelloworldRepository();
-		List<PathModel> files = JGitUtils.getFilesInPath(repository, null, null);
-		repository.close();
-		assertTrue(files.size() > 10);
-	}
-
-	@Test
-	public void testDocuments() throws Exception {
-		Repository repository = GitBlitSuite.getTicgitRepository();
-		List<String> extensions = GitBlit.getStrings(Keys.web.markdownExtensions);
-		List<PathModel> markdownDocs = JGitUtils.getDocuments(repository, extensions);
-		List<PathModel> markdownDocs2 = JGitUtils.getDocuments(repository,
-				Arrays.asList(new String[] { ".mkd", ".md" }));
-		List<PathModel> allFiles = JGitUtils.getDocuments(repository, null);
-		repository.close();
-		assertTrue(markdownDocs.size() > 0);
-		assertTrue(markdownDocs2.size() > 0);
-		assertTrue(allFiles.size() > markdownDocs.size());
-	}
-
-	@Test
-	public void testFileModes() throws Exception {
-		assertEquals("drwxr-xr-x", JGitUtils.getPermissionsFromMode(FileMode.TREE.getBits()));
-		assertEquals("-rw-r--r--",
-				JGitUtils.getPermissionsFromMode(FileMode.REGULAR_FILE.getBits()));
-		assertEquals("-rwxr-xr-x",
-				JGitUtils.getPermissionsFromMode(FileMode.EXECUTABLE_FILE.getBits()));
-		assertEquals("symlink", JGitUtils.getPermissionsFromMode(FileMode.SYMLINK.getBits()));
-		assertEquals("submodule", JGitUtils.getPermissionsFromMode(FileMode.GITLINK.getBits()));
-		assertEquals("missing", JGitUtils.getPermissionsFromMode(FileMode.MISSING.getBits()));
-	}
-
-	@Test
-	public void testRevlog() throws Exception {
-		assertTrue(JGitUtils.getRevLog(null, 0).size() == 0);
-		List<RevCommit> commits = JGitUtils.getRevLog(null, 10);
-		assertEquals(0, commits.size());
-
-		Repository repository = GitBlitSuite.getHelloworldRepository();
-		// get most recent 10 commits
-		commits = JGitUtils.getRevLog(repository, 10);
-		assertEquals(10, commits.size());
-
-		// test paging and offset by getting the 10th most recent commit
-		RevCommit lastCommit = JGitUtils.getRevLog(repository, null, 9, 1).get(0);
-		assertEquals(lastCommit, commits.get(9));
-
-		// grab the two most recent commits to java.java
-		commits = JGitUtils.getRevLog(repository, null, "java.java", 0, 2);
-		assertEquals(2, commits.size());
-
-		// grab the commits since 2008-07-15
-		commits = JGitUtils.getRevLog(repository, null,
-				new SimpleDateFormat("yyyy-MM-dd").parse("2008-07-15"));
-		assertEquals(12, commits.size());
-		repository.close();
-	}
-
-	@Test
-	public void testRevLogRange() throws Exception {
-		Repository repository = GitBlitSuite.getHelloworldRepository();
-		List<RevCommit> commits = JGitUtils.getRevLog(repository,
-				"fbd14fa6d1a01d4aefa1fca725792683800fc67e",
-				"85a0e4087b8439c0aa6b1f4f9e08c26052ab7e87");
-		repository.close();
-		assertEquals(14, commits.size());
-	}
-
-	@Test
-	public void testSearchTypes() throws Exception {
-		assertEquals(SearchType.COMMIT, SearchType.forName("commit"));
-		assertEquals(SearchType.COMMITTER, SearchType.forName("committer"));
-		assertEquals(SearchType.AUTHOR, SearchType.forName("author"));
-		assertEquals(SearchType.COMMIT, SearchType.forName("unknown"));
-
-		assertEquals("commit", SearchType.COMMIT.toString());
-		assertEquals("committer", SearchType.COMMITTER.toString());
-		assertEquals("author", SearchType.AUTHOR.toString());
-	}
-
-	@Test
-	public void testSearchRevlogs() throws Exception {
-		assertEquals(0, JGitUtils.searchRevlogs(null, null, "java", SearchType.COMMIT, 0, 0).size());
-		List<RevCommit> results = JGitUtils.searchRevlogs(null, null, "java", SearchType.COMMIT, 0,
-				3);
-		assertEquals(0, results.size());
-
-		// test commit message search
-		Repository repository = GitBlitSuite.getHelloworldRepository();
-		results = JGitUtils.searchRevlogs(repository, null, "java", SearchType.COMMIT, 0, 3);
-		assertEquals(3, results.size());
-
-		// test author search
-		results = JGitUtils.searchRevlogs(repository, null, "timothy", SearchType.AUTHOR, 0, -1);
-		assertEquals(1, results.size());
-
-		// test committer search
-		results = JGitUtils.searchRevlogs(repository, null, "mike", SearchType.COMMITTER, 0, 10);
-		assertEquals(10, results.size());
-
-		// test paging and offset
-		RevCommit commit = JGitUtils.searchRevlogs(repository, null, "mike", SearchType.COMMITTER,
-				9, 1).get(0);
-		assertEquals(results.get(9), commit);
-
-		repository.close();
-	}
-
-	@Test
-	public void testZip() throws Exception {
-		assertFalse(CompressionUtils.zip(null, null, null, null));
-		Repository repository = GitBlitSuite.getHelloworldRepository();
-		File zipFileA = new File(GitBlitSuite.REPOSITORIES, "helloworld.zip");
-		FileOutputStream fosA = new FileOutputStream(zipFileA);
-		boolean successA = CompressionUtils.zip(repository, null, Constants.HEAD, fosA);
-		fosA.close();
-
-		File zipFileB = new File(GitBlitSuite.REPOSITORIES, "helloworld-java.zip");
-		FileOutputStream fosB = new FileOutputStream(zipFileB);
-		boolean successB = CompressionUtils.zip(repository, "java.java", Constants.HEAD, fosB);
-		fosB.close();
-
-		repository.close();
-		assertTrue("Failed to generate zip file!", successA);
-		assertTrue(zipFileA.length() > 0);
-		zipFileA.delete();
-
-		assertTrue("Failed to generate zip file!", successB);
-		assertTrue(zipFileB.length() > 0);
-		zipFileB.delete();
-	}
-
-}
\ No newline at end of file
diff --git a/tests/com/gitblit/tests/JsonUtilsTest.java b/tests/com/gitblit/tests/JsonUtilsTest.java
deleted file mode 100644
index cf05844..0000000
--- a/tests/com/gitblit/tests/JsonUtilsTest.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright 2011 gitblit.com.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.gitblit.tests;
-
-import static org.junit.Assert.assertEquals;
-
-import java.text.SimpleDateFormat;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.Map;
-
-import org.junit.Test;
-
-import com.gitblit.utils.JsonUtils;
-import com.google.gson.reflect.TypeToken;
-
-public class JsonUtilsTest {
-
-	@Test
-	public void testSerialization() {
-		Map<String, String> map = new HashMap<String, String>();
-		map.put("a", "alligator");
-		map.put("b", "bear");
-		map.put("c", "caterpillar");
-		map.put("d", "dingo");
-		map.put("e", "eagle");
-		String json = JsonUtils.toJsonString(map);
-		assertEquals(
-				"{\n  \"d\": \"dingo\",\n  \"e\": \"eagle\",\n  \"b\": \"bear\",\n  \"c\": \"caterpillar\",\n  \"a\": \"alligator\"\n}",
-				json);
-		Map<String, String> map2 = JsonUtils.fromJsonString(json,
-				new TypeToken<Map<String, String>>() {
-				}.getType());
-		assertEquals(map, map2);
-
-		SomeJsonObject someJson = new SomeJsonObject();
-		json = JsonUtils.toJsonString(someJson);
-		SomeJsonObject someJson2 = JsonUtils.fromJsonString(json, SomeJsonObject.class);
-		assertEquals(someJson.name, someJson2.name);
-		SimpleDateFormat df = new SimpleDateFormat("yyyyMMdd HHmmss");
-		assertEquals(df.format(someJson.date), df.format(someJson2.date));
-	}
-
-	private class SomeJsonObject {
-		Date date = new Date();
-		String name = "myJson";
-	}
-}
\ No newline at end of file
diff --git a/tests/com/gitblit/tests/LuceneExecutorTest.java b/tests/com/gitblit/tests/LuceneExecutorTest.java
deleted file mode 100644
index 6b45b9f..0000000
--- a/tests/com/gitblit/tests/LuceneExecutorTest.java
+++ /dev/null
@@ -1,181 +0,0 @@
-/*
- * 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.tests;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import org.eclipse.jgit.lib.Repository;
-import org.junit.Test;
-
-import com.gitblit.LuceneExecutor;
-import com.gitblit.models.RefModel;
-import com.gitblit.models.RepositoryModel;
-import com.gitblit.models.SearchResult;
-import com.gitblit.tests.mock.MemorySettings;
-import com.gitblit.utils.FileUtils;
-import com.gitblit.utils.JGitUtils;
-
-/**
- * Tests Lucene indexing and querying.
- * 
- * @author James Moger
- * 
- */
-public class LuceneExecutorTest {
-
-	private LuceneExecutor newLuceneExecutor() {
-		Map<String, Object> map = new HashMap<String, Object>();
-		MemorySettings settings = new MemorySettings(map);		
-		return new LuceneExecutor(settings, GitBlitSuite.REPOSITORIES);
-	}
-	
-	private RepositoryModel newRepositoryModel(Repository repository) {		
-		RepositoryModel model = new RepositoryModel();
-		model.name = FileUtils.getRelativePath(GitBlitSuite.REPOSITORIES, repository.getDirectory());
-		model.hasCommits = JGitUtils.hasCommits(repository);
-		
-		// index all local branches
-		model.indexedBranches = new ArrayList<String>();
-		for (RefModel ref : JGitUtils.getLocalBranches(repository, true, -1)) {
-			model.indexedBranches.add(ref.getName());
-		}
-		return model;
-	}
-	
-	@Test
-	public void testIndex() throws Exception {
-		LuceneExecutor lucene = newLuceneExecutor();
-		
-		// reindex helloworld
-		Repository repository = GitBlitSuite.getHelloworldRepository();
-		RepositoryModel model = newRepositoryModel(repository);
-		lucene.reindex(model, repository);
-		repository.close();
-		
-		SearchResult result = lucene.search("type:blob AND path:bit.bit", 1, 1, model.name).get(0);		
-		assertEquals("Mike Donaghy", result.author);
-		result = lucene.search("type:blob AND path:clipper.prg", 1, 1, model.name).get(0);		
-		assertEquals("tinogomes", result.author);		
-
-		// reindex theoretical physics
-		repository = GitBlitSuite.getTheoreticalPhysicsRepository();
-		model = newRepositoryModel(repository);
-		lucene.reindex(model, repository);
-		repository.close();
-		
-		// reindex JGit
-		repository = GitBlitSuite.getJGitRepository();
-		model = newRepositoryModel(repository);
-		lucene.reindex(model, repository);
-		repository.close();
-		
-		lucene.close();
-	}
-
-	@Test
-	public void testQuery() throws Exception {
-		LuceneExecutor lucene = new LuceneExecutor(null, GitBlitSuite.REPOSITORIES);
-		
-		// 2 occurrences on the master branch
-		Repository repository = GitBlitSuite.getHelloworldRepository();				
-		RepositoryModel model = newRepositoryModel(repository);
-		repository.close();
-		
-		List<SearchResult> results = lucene.search("ada", 1, 10, model.name);
-		assertEquals(2, results.size());
-		for (SearchResult res : results) {
-			assertEquals("refs/heads/master", res.branch);
-		}
-
-		// author test
-		results = lucene.search("author: tinogomes AND type:commit", 1, 10, model.name);
-		assertEquals(2, results.size());
-		
-		// blob test
-		results = lucene.search("type: blob AND \"import std.stdio\"", 1, 10, model.name);
-		assertEquals(1, results.size());
-		assertEquals("d.D", results.get(0).path);
-		
-		// 1 occurrence on the gh-pages branch
-		repository = GitBlitSuite.getTheoreticalPhysicsRepository();
-		model = newRepositoryModel(repository);
-		repository.close();
-		
-		results = lucene.search("\"add the .nojekyll file\"", 1, 10, model.name);
-		assertEquals(1, results.size());
-		assertEquals("Ondrej Certik", results.get(0).author);
-		assertEquals("2648c0c98f2101180715b4d432fc58d0e21a51d7", results.get(0).commitId);
-		assertEquals("refs/heads/gh-pages", results.get(0).branch);
-		
-		results = lucene.search("type:blob AND \"src/intro.rst\"", 1, 10, model.name);
-		assertEquals(4, results.size());
-		
-		// hash id tests
-		results = lucene.search("commit:57c4f26f157ece24b02f4f10f5f68db1d2ce7ff5", 1, 10, model.name);
-		assertEquals(1, results.size());
-
-		results = lucene.search("commit:57c4f26f157*", 1, 10, model.name);
-		assertEquals(1, results.size());		
-		
-		// annotated tag test
-		repository = GitBlitSuite.getJGitRepository();
-		model = newRepositoryModel(repository);
-		repository.close();
-		
-		results = lucene.search("I663208919f297836a9c16bf458e4a43ffaca4c12", 1, 10, model.name);
-		assertEquals(1, results.size());
-		assertEquals("[v1.3.0.201202151440-r]", results.get(0).tags.toString());		
-		
-		lucene.close();
-	}
-	
-	@Test
-	public void testMultiSearch() throws Exception {
-		LuceneExecutor lucene = newLuceneExecutor();
-		List<String> list = new ArrayList<String>();
-		Repository repository = GitBlitSuite.getHelloworldRepository();
-		list.add(newRepositoryModel(repository).name);
-		repository.close();
-
-		repository = GitBlitSuite.getJGitRepository();
-		list.add(newRepositoryModel(repository).name);
-		repository.close();
-
-		List<SearchResult> results = lucene.search("test", 1, 10, list);
-		lucene.close();
-		assertEquals(10, results.size());
-	}
-	
-	@Test
-	public void testDeleteBlobFromIndex() throws Exception {
-		// start with a fresh reindex of entire repository
-		LuceneExecutor lucene = newLuceneExecutor();
-		Repository repository = GitBlitSuite.getHelloworldRepository();
-		RepositoryModel model = newRepositoryModel(repository);
-		lucene.reindex(model, repository);
-		
-		// now delete a blob
-		assertTrue(lucene.deleteBlob(model.name, "refs/heads/master", "java.java"));
-		assertFalse(lucene.deleteBlob(model.name, "refs/heads/master", "java.java"));
-	}
-}
\ No newline at end of file
diff --git a/tests/com/gitblit/tests/MailTest.java b/tests/com/gitblit/tests/MailTest.java
deleted file mode 100644
index 05d55a2..0000000
--- a/tests/com/gitblit/tests/MailTest.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright 2011 gitblit.com.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.gitblit.tests;
-
-import static org.junit.Assert.assertTrue;
-
-import javax.mail.Message;
-
-import org.junit.Test;
-
-import com.gitblit.FileSettings;
-import com.gitblit.MailExecutor;
-
-public class MailTest {
-
-	@Test
-	public void testSendMail() throws Exception {
-		FileSettings settings = new FileSettings("mailtest.properties");
-		MailExecutor mail = new MailExecutor(settings);
-		Message message = mail.createMessageForAdministrators();
-		message.setSubject("Test");
-		message.setText("this is a test");
-		mail.queue(message);
-		mail.run();
-
-		assertTrue("mail queue is not empty!", mail.hasEmptyQueue());
-	}
-}
diff --git a/tests/com/gitblit/tests/PermissionsTest.java b/tests/com/gitblit/tests/PermissionsTest.java
deleted file mode 100644
index 5a95104..0000000
--- a/tests/com/gitblit/tests/PermissionsTest.java
+++ /dev/null
@@ -1,2635 +0,0 @@
-/*
- * 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.tests;
-
-import java.util.Date;
-
-import junit.framework.Assert;
-
-import org.junit.Test;
-
-import com.gitblit.Constants.AccessPermission;
-import com.gitblit.Constants.AccessRestrictionType;
-import com.gitblit.Constants.AuthorizationControl;
-import com.gitblit.models.RepositoryModel;
-import com.gitblit.models.TeamModel;
-import com.gitblit.models.UserModel;
-
-/**
- * Comprehensive, brute-force test of all permutations of discrete permissions.
- * 
- * @author James Moger
- *
- */
-public class PermissionsTest extends Assert {
-
-	/**
-	 * Admin access rights/permissions
-	 */
-	@Test
-	public void testAdmin() throws Exception {
-		UserModel user = new UserModel("admin");
-		user.canAdmin = true;
-		
-		for (AccessRestrictionType ar : AccessRestrictionType.values()) {
-			RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
-			repository.authorizationControl = AuthorizationControl.NAMED;
-			repository.accessRestriction = ar;
-				
-			assertTrue("admin CAN NOT view!", user.canView(repository));
-			assertTrue("admin CAN NOT clone!", user.canClone(repository));
-			assertTrue("admin CAN NOT push!", user.canPush(repository));
-			
-			assertTrue("admin CAN NOT create ref!", user.canCreateRef(repository));
-			assertTrue("admin CAN NOT delete ref!", user.canDeleteRef(repository));
-			assertTrue("admin CAN NOT rewind ref!", user.canRewindRef(repository));
-			
-			assertTrue("admin CAN NOT fork!", user.canFork(repository));
-			
-			assertTrue("admin CAN NOT delete!", user.canDelete(repository));
-			assertTrue("admin CAN NOT edit!", user.canEdit(repository));
-		}
-	}
-	
-	/**
-	 * Anonymous access rights/permissions 
-	 */
-	@Test
-	public void testAnonymous_NONE() throws Exception {
-		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
-		repository.authorizationControl = AuthorizationControl.NAMED;
-		repository.accessRestriction = AccessRestrictionType.NONE;
-		
-		UserModel user = UserModel.ANONYMOUS;
-		
-		// all permissions, except fork
-		assertTrue("anonymous CAN NOT view!", user.canView(repository));
-		assertTrue("anonymous CAN NOT clone!", user.canClone(repository));
-		assertTrue("anonymous CAN NOT push!", user.canPush(repository));
-		
-		assertTrue("anonymous CAN NOT create ref!", user.canCreateRef(repository));
-		assertTrue("anonymous CAN NOT delete ref!", user.canDeleteRef(repository));
-		assertTrue("anonymous CAN NOT rewind ref!", user.canRewindRef(repository));
-
-		repository.allowForks = false;
-		assertFalse("anonymous CAN fork!", user.canFork(repository));
-		repository.allowForks = true;
-		assertFalse("anonymous CAN fork!", user.canFork(repository));
-		
-		assertFalse("anonymous CAN delete!", user.canDelete(repository));
-		assertFalse("anonymous CAN edit!", user.canEdit(repository));
-	}
-	
-	@Test
-	public void testAnonymous_PUSH() throws Exception {
-		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
-		repository.authorizationControl = AuthorizationControl.NAMED;
-		repository.accessRestriction = AccessRestrictionType.PUSH;
-
-		UserModel user = UserModel.ANONYMOUS;
-
-		assertTrue("anonymous CAN NOT view!", user.canView(repository));
-		assertTrue("anonymous CAN NOT clone!", user.canClone(repository));
-		assertFalse("anonymous CAN push!", user.canPush(repository));
-		
-		assertFalse("anonymous CAN create ref!", user.canCreateRef(repository));
-		assertFalse("anonymous CAN delete ref!", user.canDeleteRef(repository));
-		assertFalse("anonymous CAN rewind ref!", user.canRewindRef(repository));
-		
-		repository.allowForks = false;
-		assertFalse("anonymous CAN fork!", user.canFork(repository));
-		repository.allowForks = true;
-		assertFalse("anonymous CAN fork!", user.canFork(repository));
-	}
-	
-	@Test
-	public void testAnonymous_CLONE() throws Exception {
-		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
-		repository.authorizationControl = AuthorizationControl.NAMED;
-		repository.accessRestriction = AccessRestrictionType.CLONE;
-
-		UserModel user = UserModel.ANONYMOUS;
-
-		assertTrue("anonymous CAN NOT view!", user.canView(repository));
-		assertFalse("anonymous CAN clone!", user.canClone(repository));
-		assertFalse("anonymous CAN push!", user.canPush(repository));
-		
-		assertFalse("anonymous CAN create ref!", user.canCreateRef(repository));
-		assertFalse("anonymous CAN delete ref!", user.canDeleteRef(repository));
-		assertFalse("anonymous CAN rewind ref!", user.canRewindRef(repository));
-
-		repository.allowForks = false;
-		assertFalse("anonymous CAN fork!", user.canFork(repository));
-		repository.allowForks = true;
-		assertFalse("anonymous CAN fork!", user.canFork(repository));
-	}
-	
-	@Test
-	public void testAnonymous_VIEW() throws Exception {
-		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
-		repository.authorizationControl = AuthorizationControl.NAMED;
-		repository.accessRestriction = AccessRestrictionType.VIEW;
-		
-		UserModel user = UserModel.ANONYMOUS;
-
-		assertFalse("anonymous CAN view!", user.canView(repository));
-		assertFalse("anonymous CAN clone!", user.canClone(repository));
-		assertFalse("anonymous CAN push!", user.canPush(repository));
-		
-		assertFalse("anonymous CAN create ref!", user.canCreateRef(repository));
-		assertFalse("anonymous CAN delete ref!", user.canDeleteRef(repository));
-		assertFalse("anonymous CAN rewind ref!", user.canRewindRef(repository));
-
-		repository.allowForks = false;
-		assertFalse("anonymous CAN fork!", user.canFork(repository));
-		repository.allowForks = true;
-		assertFalse("anonymous CAN fork!", user.canFork(repository));
-	}
-	
-	/**
-	 * Authenticated access rights/permissions 
-	 */
-	@Test
-	public void testAuthenticated_NONE() throws Exception {
-		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
-		repository.authorizationControl = AuthorizationControl.AUTHENTICATED;
-		repository.accessRestriction = AccessRestrictionType.NONE;
-		
-		UserModel user = new UserModel("test");
-		
-		// all permissions, except fork
-		assertTrue("authenticated CAN NOT view!", user.canView(repository));
-		assertTrue("authenticated CAN NOT clone!", user.canClone(repository));
-		assertTrue("authenticated CAN NOT push!", user.canPush(repository));
-		
-		assertTrue("authenticated CAN NOT create ref!", user.canCreateRef(repository));
-		assertTrue("authenticated CAN NOT delete ref!", user.canDeleteRef(repository));
-		assertTrue("authenticated CAN NOT rewind ref!", user.canRewindRef(repository));
-
-		user.canFork = false;
-		repository.allowForks = false;
-		assertFalse("authenticated CAN fork!", user.canFork(repository));
-		repository.allowForks = true;
-		assertFalse("authenticated CAN fork!", user.canFork(repository));
-		user.canFork = true;
-		assertTrue("authenticated CAN NOT fork!", user.canFork(repository));
-		
-		assertFalse("authenticated CAN delete!", user.canDelete(repository));
-		assertFalse("authenticated CAN edit!", user.canEdit(repository));
-	}
-
-	@Test
-	public void testAuthenticated_PUSH() throws Exception {
-		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
-		repository.authorizationControl = AuthorizationControl.AUTHENTICATED;
-		repository.accessRestriction = AccessRestrictionType.PUSH;
-		
-		UserModel user = new UserModel("test");
-
-		assertTrue("authenticated CAN NOT view!", user.canView(repository));
-		assertTrue("authenticated CAN NOT clone!", user.canClone(repository));
-		assertTrue("authenticated CAN NOT push!", user.canPush(repository));
-		
-		assertTrue("authenticated CAN NOT create ref!", user.canCreateRef(repository));
-		assertTrue("authenticated CAN NOT delete ref!", user.canDeleteRef(repository));
-		assertTrue("authenticated CAN NOT rewind ref!", user.canRewindRef(repository));
-
-		user.canFork = false;
-		repository.allowForks = false;
-		assertFalse("authenticated CAN fork!", user.canFork(repository));
-		repository.allowForks = true;
-		assertFalse("authenticated CAN fork!", user.canFork(repository));
-		user.canFork = true;
-		assertTrue("authenticated CAN NOT fork!", user.canFork(repository));
-	}
-	
-	@Test
-	public void testAuthenticated_CLONE() throws Exception {
-		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
-		repository.authorizationControl = AuthorizationControl.AUTHENTICATED;
-		repository.accessRestriction = AccessRestrictionType.CLONE;
-			
-		UserModel user = new UserModel("test");
-
-		assertTrue("authenticated CAN NOT view!", user.canView(repository));
-		assertTrue("authenticated CAN NOT clone!", user.canClone(repository));
-		assertTrue("authenticated CAN NOT push!", user.canPush(repository));
-		
-		assertTrue("authenticated CAN NOT create ref!", user.canCreateRef(repository));
-		assertTrue("authenticated CAN NOT delete ref!", user.canDeleteRef(repository));
-		assertTrue("authenticated CAN NOT rewind ref!", user.canRewindRef(repository));
-
-		user.canFork = false;
-		repository.allowForks = false;
-		assertFalse("authenticated CAN fork!", user.canFork(repository));
-		repository.allowForks = true;
-		assertFalse("authenticated CAN fork!", user.canFork(repository));
-		user.canFork = true;
-		assertTrue("authenticated CAN NOT fork!", user.canFork(repository));
-	}
-	
-	@Test
-	public void testAuthenticated_VIEW() throws Exception {
-		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
-		repository.authorizationControl = AuthorizationControl.AUTHENTICATED;
-		repository.accessRestriction = AccessRestrictionType.VIEW;
-			
-		UserModel user = new UserModel("test");
-
-		assertTrue("authenticated CAN NOT view!", user.canView(repository));
-		assertTrue("authenticated CAN NOT clone!", user.canClone(repository));
-		assertTrue("authenticated CAN NOT push!", user.canPush(repository));
-		
-		assertTrue("authenticated CAN NOT create ref!", user.canCreateRef(repository));
-		assertTrue("authenticated CAN NOT delete ref!", user.canDeleteRef(repository));
-		assertTrue("authenticated CAN NOT rewind ref!", user.canRewindRef(repository));
-
-		user.canFork = false;
-		repository.allowForks = false;
-		assertFalse("authenticated CAN fork!", user.canFork(repository));
-		repository.allowForks = true;
-		assertFalse("authenticated CAN fork!", user.canFork(repository));
-		user.canFork = true;
-		assertTrue("authenticated CAN NOT fork!", user.canFork(repository));
-	}
-	
-	/**
-	 * NONE_NONE = NO access restriction, NO access permission
-	 */
-	@Test
-	public void testNamed_NONE_NONE() throws Exception {
-		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
-		repository.authorizationControl = AuthorizationControl.NAMED;
-		repository.accessRestriction = AccessRestrictionType.NONE;
-
-		UserModel user = new UserModel("test");
-		
-		assertTrue("named CAN NOT view!", user.canView(repository));
-		assertTrue("named CAN NOT clone!", user.canClone(repository));
-		assertTrue("named CAN NOT push!", user.canPush(repository));
-		
-		assertTrue("named CAN NOT create ref!", user.canCreateRef(repository));
-		assertTrue("named CAN NOT delete ref!", user.canDeleteRef(repository));
-		assertTrue("named CAN NOT rewind ref!", user.canRewindRef(repository));
-		
-		repository.allowForks = false;
-		user.canFork = false;
-		assertFalse("named CAN fork!", user.canFork(repository));
-		user.canFork = true;
-		assertFalse("named CAN fork!", user.canFork(repository));
-		repository.allowForks = true;
-		assertTrue("named CAN NOT fork!", user.canFork(repository));
-		
-		assertFalse("named CAN delete!", user.canDelete(repository));
-		assertFalse("named CAN edit!", user.canEdit(repository));
-	}
-	
-	/**
-	 * PUSH_NONE = PUSH access restriction, NO access permission
-	 */
-	@Test
-	public void testNamed_PUSH_NONE() throws Exception {
-		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
-		repository.authorizationControl = AuthorizationControl.NAMED;
-		repository.accessRestriction = AccessRestrictionType.PUSH;
-		
-		UserModel user = new UserModel("test");
-		
-		assertTrue("named CAN NOT view!", user.canView(repository));
-		assertTrue("named CAN NOT clone!", user.canClone(repository));
-		assertFalse("named CAN push!", user.canPush(repository));
-		
-		assertFalse("named CAN create ref!", user.canCreateRef(repository));
-		assertFalse("named CAN delete ref!", user.canDeleteRef(repository));
-		assertFalse("named CAN rewind ref!", user.canRewindRef(repository));
-
-		repository.allowForks = false;
-		user.canFork = false;
-		assertFalse("named CAN fork!", user.canFork(repository));
-		user.canFork = true;
-		assertFalse("named CAN fork!", user.canFork(repository));
-		repository.allowForks = true;
-		assertTrue("named CAN NOT fork!", user.canFork(repository));
-	}
-	
-	/**
-	 * CLONE_NONE = CLONE access restriction, NO access permission
-	 */
-	@Test
-	public void testNamed_CLONE_NONE() throws Exception {
-		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
-		repository.authorizationControl = AuthorizationControl.NAMED;
-		repository.accessRestriction = AccessRestrictionType.CLONE;
-		
-		UserModel user = new UserModel("test");
-		
-		assertTrue("named CAN NOT view!", user.canView(repository));
-		assertFalse("named CAN clone!", user.canClone(repository));
-		assertFalse("named CAN push!", user.canPush(repository));
-		
-		assertFalse("named CAN create ref!", user.canCreateRef(repository));
-		assertFalse("named CAN delete ref!", user.canDeleteRef(repository));
-		assertFalse("named CAN rewind ref!", user.canRewindRef(repository));
-
-		repository.allowForks = false;
-		user.canFork = false;
-		assertFalse("named CAN fork!", user.canFork(repository));
-		user.canFork = true;
-		assertFalse("named CAN fork!", user.canFork(repository));
-		repository.allowForks = true;
-		assertFalse("named CAN NOT fork!", user.canFork(repository));
-	}
-	
-	/**
-	 * VIEW_NONE = VIEW access restriction, NO access permission
-	 */
-	@Test
-	public void testNamed_VIEW_NONE() throws Exception {
-		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
-		repository.authorizationControl = AuthorizationControl.NAMED;
-		repository.accessRestriction = AccessRestrictionType.VIEW;
-		
-		UserModel user = new UserModel("test");
-		
-		assertFalse("named CAN view!", user.canView(repository));
-		assertFalse("named CAN clone!", user.canClone(repository));
-		assertFalse("named CAN push!", user.canPush(repository));
-		
-		assertFalse("named CAN create ref!", user.canCreateRef(repository));
-		assertFalse("named CAN delete ref!", user.canDeleteRef(repository));
-		assertFalse("named CAN rewind ref!", user.canRewindRef(repository));
-		
-		repository.allowForks = false;
-		user.canFork = false;
-		assertFalse("named CAN fork!", user.canFork(repository));
-		user.canFork = true;
-		assertFalse("named CAN fork!", user.canFork(repository));
-		repository.allowForks = true;
-		assertFalse("named CAN NOT fork!", user.canFork(repository));
-	}
-
-	
-	/**
-	 * NONE_VIEW = NO access restriction, VIEW access permission.
-	 * (not useful scenario)
-	 */
-	@Test
-	public void testNamed_NONE_VIEW() throws Exception {
-		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
-		repository.authorizationControl = AuthorizationControl.NAMED;
-		repository.accessRestriction = AccessRestrictionType.NONE;
-
-		UserModel user = new UserModel("test");
-		user.setRepositoryPermission(repository.name, AccessPermission.VIEW);
-		
-		assertTrue("named CAN NOT view!", user.canView(repository));
-		assertTrue("named CAN NOT clone!", user.canClone(repository));
-		assertTrue("named CAN NOT push!", user.canPush(repository));
-		
-		assertTrue("named CAN NOT create ref!", user.canCreateRef(repository));
-		assertTrue("named CAN NOT delete ref!", user.canDeleteRef(repository));
-		assertTrue("named CAN NOT rewind ref!", user.canRewindRef(repository));
-		
-		repository.allowForks = false;
-		user.canFork = false;
-		assertFalse("named CAN fork!", user.canFork(repository));
-		user.canFork = true;
-		assertFalse("named CAN fork!", user.canFork(repository));
-		repository.allowForks = true;
-		assertTrue("named CAN NOT fork!", user.canFork(repository));
-	}
-	
-	/**
-	 * PUSH_VIEW = PUSH access restriction, VIEW access permission
-	 */
-	@Test
-	public void testNamed_PUSH_VIEW() throws Exception {
-		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
-		repository.authorizationControl = AuthorizationControl.NAMED;
-		repository.accessRestriction = AccessRestrictionType.PUSH;
-		
-		UserModel user = new UserModel("test");
-		user.setRepositoryPermission(repository.name, AccessPermission.VIEW);
-		
-		assertTrue("named CAN NOT view!", user.canView(repository));
-		assertTrue("named CAN NOT clone!", user.canClone(repository));
-		assertFalse("named CAN push!", user.canPush(repository));
-		
-		assertFalse("named CAN create ref!", user.canCreateRef(repository));
-		assertFalse("named CAN delete ref!", user.canDeleteRef(repository));
-		assertFalse("named CAN rewind ref!", user.canRewindRef(repository));
-
-		repository.allowForks = false;
-		user.canFork = false;
-		assertFalse("named CAN fork!", user.canFork(repository));
-		user.canFork = true;
-		assertFalse("named CAN fork!", user.canFork(repository));
-		repository.allowForks = true;
-		assertTrue("named CAN NOT fork!", user.canFork(repository));
-	}
-	
-	/**
-	 * CLONE_VIEW = CLONE access restriction, VIEW access permission
-	 */
-	@Test
-	public void testNamed_CLONE_VIEW() throws Exception {
-		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
-		repository.authorizationControl = AuthorizationControl.NAMED;
-		repository.accessRestriction = AccessRestrictionType.CLONE;
-		
-		UserModel user = new UserModel("test");
-		user.setRepositoryPermission(repository.name, AccessPermission.VIEW);
-
-		assertTrue("named CAN NOT view!", user.canView(repository));
-		assertFalse("named CAN clone!", user.canClone(repository));
-		assertFalse("named CAN push!", user.canPush(repository));
-		
-		assertFalse("named CAN create ref!", user.canCreateRef(repository));
-		assertFalse("named CAN delete ref!", user.canDeleteRef(repository));
-		assertFalse("named CAN rewind ref!", user.canRewindRef(repository));
-
-		repository.allowForks = false;
-		user.canFork = false;
-		assertFalse("named CAN fork!", user.canFork(repository));
-		user.canFork = true;
-		assertFalse("named CAN fork!", user.canFork(repository));
-		repository.allowForks = true;
-		assertFalse("named CAN NOT fork!", user.canFork(repository));
-	}
-	
-	/**
-	 * VIEW_VIEW = VIEW access restriction, VIEW access permission
-	 */
-	@Test
-	public void testNamed_VIEW_VIEW() throws Exception {
-		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
-		repository.authorizationControl = AuthorizationControl.NAMED;
-		repository.accessRestriction = AccessRestrictionType.VIEW;
-		
-		UserModel user = new UserModel("test");
-		user.setRepositoryPermission(repository.name, AccessPermission.VIEW);
-
-		assertTrue("named CAN NOT view!", user.canView(repository));
-		assertFalse("named CAN clone!", user.canClone(repository));
-		assertFalse("named CAN push!", user.canPush(repository));
-		
-		assertFalse("named CAN create ref!", user.canCreateRef(repository));
-		assertFalse("named CAN delete ref!", user.canDeleteRef(repository));
-		assertFalse("named CAN rewind ref!", user.canRewindRef(repository));
-		
-		repository.allowForks = false;
-		user.canFork = false;
-		assertFalse("named CAN fork!", user.canFork(repository));
-		user.canFork = true;
-		assertFalse("named CAN fork!", user.canFork(repository));
-		repository.allowForks = true;
-		assertFalse("named CAN NOT fork!", user.canFork(repository));
-	}
-	
-	/**
-	 * NONE_CLONE = NO access restriction, CLONE access permission.
-	 * (not useful scenario)
-	 */
-	@Test
-	public void testNamed_NONE_CLONE() throws Exception {
-		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
-		repository.authorizationControl = AuthorizationControl.NAMED;
-		repository.accessRestriction = AccessRestrictionType.NONE;
-
-		UserModel user = new UserModel("test");
-		user.setRepositoryPermission(repository.name, AccessPermission.CLONE);
-		
-		assertTrue("named CAN NOT view!", user.canView(repository));
-		assertTrue("named CAN NOT clone!", user.canClone(repository));
-		assertTrue("named CAN NOT push!", user.canPush(repository));
-		
-		assertTrue("named CAN NOT create ref!", user.canCreateRef(repository));
-		assertTrue("named CAN NOT delete ref!", user.canDeleteRef(repository));
-		assertTrue("named CAN NOT rewind ref!", user.canRewindRef(repository));
-		
-		repository.allowForks = false;
-		user.canFork = false;
-		assertFalse("named CAN fork!", user.canFork(repository));
-		user.canFork = true;
-		assertFalse("named CAN fork!", user.canFork(repository));
-		repository.allowForks = true;
-		assertTrue("named CAN NOT fork!", user.canFork(repository));
-	}
-	
-	/**
-	 * PUSH_CLONE = PUSH access restriction, CLONE access permission
-	 */
-	@Test
-	public void testNamed_PUSH_READ() throws Exception {
-		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
-		repository.authorizationControl = AuthorizationControl.NAMED;
-		repository.accessRestriction = AccessRestrictionType.PUSH;
-		
-		UserModel user = new UserModel("test");
-		user.setRepositoryPermission(repository.name, AccessPermission.CLONE);
-		
-		assertTrue("named CAN NOT view!", user.canView(repository));
-		assertTrue("named CAN NOT clone!", user.canClone(repository));
-		assertFalse("named CAN push!", user.canPush(repository));
-		
-		assertFalse("named CAN create ref!", user.canCreateRef(repository));
-		assertFalse("named CAN delete ref!", user.canDeleteRef(repository));
-		assertFalse("named CAN rewind ref!", user.canRewindRef(repository));
-
-		repository.allowForks = false;
-		user.canFork = false;
-		assertFalse("named CAN fork!", user.canFork(repository));
-		user.canFork = true;
-		assertFalse("named CAN fork!", user.canFork(repository));
-		repository.allowForks = true;
-		assertTrue("named CAN NOT fork!", user.canFork(repository));
-	}
-	
-	/**
-	 * CLONE_CLONE = CLONE access restriction, CLONE access permission
-	 */
-	@Test
-	public void testNamed_CLONE_CLONE() throws Exception {
-		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
-		repository.authorizationControl = AuthorizationControl.NAMED;
-		repository.accessRestriction = AccessRestrictionType.CLONE;
-		
-		UserModel user = new UserModel("test");
-		user.setRepositoryPermission(repository.name, AccessPermission.CLONE);
-
-		assertTrue("named CAN NOT view!", user.canView(repository));
-		assertTrue("named CAN NOT clone!", user.canClone(repository));
-		assertFalse("named CAN push!", user.canPush(repository));
-		
-		assertFalse("named CAN create ref!", user.canCreateRef(repository));
-		assertFalse("named CAN delete ref!", user.canDeleteRef(repository));
-		assertFalse("named CAN rewind ref!", user.canRewindRef(repository));
-
-		repository.allowForks = false;
-		user.canFork = false;
-		assertFalse("named CAN fork!", user.canFork(repository));
-		user.canFork = true;
-		assertFalse("named CAN fork!", user.canFork(repository));
-		repository.allowForks = true;
-		assertTrue("named CAN NOT fork!", user.canFork(repository));
-	}
-	
-	/**
-	 * VIEW_CLONE = VIEW access restriction, CLONE access permission
-	 */
-	@Test
-	public void testNamed_VIEW_CLONE() throws Exception {
-		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
-		repository.authorizationControl = AuthorizationControl.NAMED;
-		repository.accessRestriction = AccessRestrictionType.VIEW;
-		
-		UserModel user = new UserModel("test");
-		user.setRepositoryPermission(repository.name, AccessPermission.CLONE);
-
-		assertTrue("named CAN NOT view!", user.canView(repository));
-		assertTrue("named CAN NOT clone!", user.canClone(repository));
-		assertFalse("named CAN push!", user.canPush(repository));
-		
-		assertFalse("named CAN create ref!", user.canCreateRef(repository));
-		assertFalse("named CAN delete ref!", user.canDeleteRef(repository));
-		assertFalse("named CAN rewind ref!", user.canRewindRef(repository));
-		
-		repository.allowForks = false;
-		user.canFork = false;
-		assertFalse("named CAN fork!", user.canFork(repository));
-		user.canFork = true;
-		assertFalse("named CAN fork!", user.canFork(repository));
-		repository.allowForks = true;
-		assertTrue("named CAN NOT fork!", user.canFork(repository));
-	}	
-
-	/**
-	 * NONE_PUSH = NO access restriction, PUSH access permission.
-	 * (not useful scenario)
-	 */
-	@Test
-	public void testNamed_NONE_PUSH() throws Exception {
-		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
-		repository.authorizationControl = AuthorizationControl.NAMED;
-		repository.accessRestriction = AccessRestrictionType.NONE;
-
-		UserModel user = new UserModel("test");
-		user.setRepositoryPermission(repository.name, AccessPermission.PUSH);
-		
-		assertTrue("named CAN NOT view!", user.canView(repository));
-		assertTrue("named CAN NOT clone!", user.canClone(repository));
-		assertTrue("named CAN NOT push!", user.canPush(repository));
-		
-		assertTrue("named CAN NOT create ref!", user.canCreateRef(repository));
-		assertTrue("named CAN NOT delete ref!", user.canDeleteRef(repository));
-		assertTrue("named CAN NOT rewind ref!", user.canRewindRef(repository));
-		
-		repository.allowForks = false;
-		user.canFork = false;
-		assertFalse("named CAN fork!", user.canFork(repository));
-		user.canFork = true;
-		assertFalse("named CAN fork!", user.canFork(repository));
-		repository.allowForks = true;
-		assertTrue("named CAN NOT fork!", user.canFork(repository));
-	}
-	
-	/**
-	 * PUSH_PUSH = PUSH access restriction, PUSH access permission
-	 */
-	@Test
-	public void testNamed_PUSH_PUSH() throws Exception {
-		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
-		repository.authorizationControl = AuthorizationControl.NAMED;
-		repository.accessRestriction = AccessRestrictionType.PUSH;
-		
-		UserModel user = new UserModel("test");
-		user.setRepositoryPermission(repository.name, AccessPermission.PUSH);
-		
-		assertTrue("named CAN NOT view!", user.canView(repository));
-		assertTrue("named CAN NOT clone!", user.canClone(repository));
-		assertTrue("named CAN NOT push!", user.canPush(repository));
-		
-		assertFalse("named CAN create ref!", user.canCreateRef(repository));
-		assertFalse("named CAN delete ref!", user.canDeleteRef(repository));
-		assertFalse("named CAN rewind ref!", user.canRewindRef(repository));
-
-		repository.allowForks = false;
-		user.canFork = false;
-		assertFalse("named CAN fork!", user.canFork(repository));
-		user.canFork = true;
-		assertFalse("named CAN fork!", user.canFork(repository));
-		repository.allowForks = true;
-		assertTrue("named CAN NOT fork!", user.canFork(repository));
-	}
-	
-	/**
-	 * CLONE_PUSH = CLONE access restriction, PUSH access permission
-	 */
-	@Test
-	public void testNamed_CLONE_PUSH() throws Exception {
-		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
-		repository.authorizationControl = AuthorizationControl.NAMED;
-		repository.accessRestriction = AccessRestrictionType.CLONE;
-		
-		UserModel user = new UserModel("test");
-		user.setRepositoryPermission(repository.name, AccessPermission.PUSH);
-
-		assertTrue("named CAN NOT view!", user.canView(repository));
-		assertTrue("named CAN NOT clone!", user.canClone(repository));
-		assertTrue("named CAN NOT push!", user.canPush(repository));
-		
-		assertFalse("named CAN create ref!", user.canCreateRef(repository));
-		assertFalse("named CAN delete red!", user.canDeleteRef(repository));
-		assertFalse("named CAN rewind ref!", user.canRewindRef(repository));
-
-		repository.allowForks = false;
-		user.canFork = false;
-		assertFalse("named CAN fork!", user.canFork(repository));
-		user.canFork = true;
-		assertFalse("named CAN fork!", user.canFork(repository));
-		repository.allowForks = true;
-		assertTrue("named CAN NOT fork!", user.canFork(repository));
-	}
-	
-	/**
-	 * VIEW_PUSH = VIEW access restriction, PUSH access permission
-	 */
-	@Test
-	public void testNamed_VIEW_PUSH() throws Exception {
-		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
-		repository.authorizationControl = AuthorizationControl.NAMED;
-		repository.accessRestriction = AccessRestrictionType.VIEW;
-		
-		UserModel user = new UserModel("test");
-		user.setRepositoryPermission(repository.name, AccessPermission.PUSH);
-
-		assertTrue("named CAN NOT view!", user.canView(repository));
-		assertTrue("named CAN NOT clone!", user.canClone(repository));
-		assertTrue("named CAN not push!", user.canPush(repository));
-		
-		assertFalse("named CAN create ref!", user.canCreateRef(repository));
-		assertFalse("named CAN delete ref!", user.canDeleteRef(repository));
-		assertFalse("named CAN rewind ref!", user.canRewindRef(repository));
-		
-		repository.allowForks = false;
-		user.canFork = false;
-		assertFalse("named CAN fork!", user.canFork(repository));
-		user.canFork = true;
-		assertFalse("named CAN fork!", user.canFork(repository));
-		repository.allowForks = true;
-		assertTrue("named CAN NOT fork!", user.canFork(repository));
-	}
-
-	/**
-	 * NONE_CREATE = NO access restriction, CREATE access permission.
-	 * (not useful scenario)
-	 */
-	@Test
-	public void testNamed_NONE_CREATE() throws Exception {
-		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
-		repository.authorizationControl = AuthorizationControl.NAMED;
-		repository.accessRestriction = AccessRestrictionType.NONE;
-
-		UserModel user = new UserModel("test");
-		user.setRepositoryPermission(repository.name, AccessPermission.CREATE);
-		
-		assertTrue("named CAN NOT view!", user.canView(repository));
-		assertTrue("named CAN NOT clone!", user.canClone(repository));
-		assertTrue("named CAN NOT push!", user.canPush(repository));
-		
-		assertTrue("named CAN NOT create ref!", user.canCreateRef(repository));
-		assertTrue("named CAN NOT delete ref!", user.canDeleteRef(repository));
-		assertTrue("named CAN NOT rewind ref!", user.canRewindRef(repository));
-		
-		repository.allowForks = false;
-		user.canFork = false;
-		assertFalse("named CAN fork!", user.canFork(repository));
-		user.canFork = true;
-		assertFalse("named CAN fork!", user.canFork(repository));
-		repository.allowForks = true;
-		assertTrue("named CAN NOT fork!", user.canFork(repository));
-	}
-	
-	/**
-	 * PUSH_CREATE = PUSH access restriction, CREATE access permission
-	 */
-	@Test
-	public void testNamed_PUSH_CREATE() throws Exception {
-		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
-		repository.authorizationControl = AuthorizationControl.NAMED;
-		repository.accessRestriction = AccessRestrictionType.PUSH;
-		
-		UserModel user = new UserModel("test");
-		user.setRepositoryPermission(repository.name, AccessPermission.CREATE);
-		
-		assertTrue("named CAN NOT view!", user.canView(repository));
-		assertTrue("named CAN NOT clone!", user.canClone(repository));
-		assertTrue("named CAN NOT push!", user.canPush(repository));
-		
-		assertTrue("named CAN NOT create ref!", user.canCreateRef(repository));
-		assertFalse("named CAN delete ref!", user.canDeleteRef(repository));
-		assertFalse("named CAN rewind ref!", user.canRewindRef(repository));
-
-		repository.allowForks = false;
-		user.canFork = false;
-		assertFalse("named CAN fork!", user.canFork(repository));
-		user.canFork = true;
-		assertFalse("named CAN fork!", user.canFork(repository));
-		repository.allowForks = true;
-		assertTrue("named CAN NOT fork!", user.canFork(repository));
-	}
-	
-	/**
-	 * CLONE_CREATE = CLONE access restriction, CREATE access permission
-	 */
-	@Test
-	public void testNamed_CLONE_CREATE() throws Exception {
-		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
-		repository.authorizationControl = AuthorizationControl.NAMED;
-		repository.accessRestriction = AccessRestrictionType.CLONE;
-		
-		UserModel user = new UserModel("test");
-		user.setRepositoryPermission(repository.name, AccessPermission.CREATE);
-
-		assertTrue("named CAN NOT view!", user.canView(repository));
-		assertTrue("named CAN NOT clone!", user.canClone(repository));
-		assertTrue("named CAN NOT push!", user.canPush(repository));
-		
-		assertTrue("named CAN NOT create ref!", user.canCreateRef(repository));
-		assertFalse("named CAN delete red!", user.canDeleteRef(repository));
-		assertFalse("named CAN rewind ref!", user.canRewindRef(repository));
-
-		repository.allowForks = false;
-		user.canFork = false;
-		assertFalse("named CAN fork!", user.canFork(repository));
-		user.canFork = true;
-		assertFalse("named CAN fork!", user.canFork(repository));
-		repository.allowForks = true;
-		assertTrue("named CAN NOT fork!", user.canFork(repository));
-	}
-	
-	/**
-	 * VIEW_CREATE = VIEW access restriction, CREATE access permission
-	 */
-	@Test
-	public void testNamed_VIEW_CREATE() throws Exception {
-		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
-		repository.authorizationControl = AuthorizationControl.NAMED;
-		repository.accessRestriction = AccessRestrictionType.VIEW;
-		
-		UserModel user = new UserModel("test");
-		user.setRepositoryPermission(repository.name, AccessPermission.CREATE);
-
-		assertTrue("named CAN NOT view!", user.canView(repository));
-		assertTrue("named CAN NOT clone!", user.canClone(repository));
-		assertTrue("named CAN not push!", user.canPush(repository));
-		
-		assertTrue("named CAN NOT create ref!", user.canCreateRef(repository));
-		assertFalse("named CAN delete ref!", user.canDeleteRef(repository));
-		assertFalse("named CAN rewind ref!", user.canRewindRef(repository));
-		
-		repository.allowForks = false;
-		user.canFork = false;
-		assertFalse("named CAN fork!", user.canFork(repository));
-		user.canFork = true;
-		assertFalse("named CAN fork!", user.canFork(repository));
-		repository.allowForks = true;
-		assertTrue("named CAN NOT fork!", user.canFork(repository));
-	}
-
-	/**
-	 * NONE_DELETE = NO access restriction, DELETE access permission.
-	 * (not useful scenario)
-	 */
-	@Test
-	public void testNamed_NONE_DELETE() throws Exception {
-		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
-		repository.authorizationControl = AuthorizationControl.NAMED;
-		repository.accessRestriction = AccessRestrictionType.NONE;
-
-		UserModel user = new UserModel("test");
-		user.setRepositoryPermission(repository.name, AccessPermission.DELETE);
-		
-		assertTrue("named CAN NOT view!", user.canView(repository));
-		assertTrue("named CAN NOT clone!", user.canClone(repository));
-		assertTrue("named CAN NOT push!", user.canPush(repository));
-		
-		assertTrue("named CAN NOT create ref!", user.canCreateRef(repository));
-		assertTrue("named CAN NOT delete ref!", user.canDeleteRef(repository));
-		assertTrue("named CAN NOT rewind ref!", user.canRewindRef(repository));
-		
-		repository.allowForks = false;
-		user.canFork = false;
-		assertFalse("named CAN fork!", user.canFork(repository));
-		user.canFork = true;
-		assertFalse("named CAN fork!", user.canFork(repository));
-		repository.allowForks = true;
-		assertTrue("named CAN NOT fork!", user.canFork(repository));
-	}
-	
-	/**
-	 * PUSH_DELETE = PUSH access restriction, DELETE access permission
-	 */
-	@Test
-	public void testNamed_PUSH_DELETE() throws Exception {
-		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
-		repository.authorizationControl = AuthorizationControl.NAMED;
-		repository.accessRestriction = AccessRestrictionType.PUSH;
-		
-		UserModel user = new UserModel("test");
-		user.setRepositoryPermission(repository.name, AccessPermission.DELETE);
-		
-		assertTrue("named CAN NOT view!", user.canView(repository));
-		assertTrue("named CAN NOT clone!", user.canClone(repository));
-		assertTrue("named CAN NOT push!", user.canPush(repository));
-		
-		assertTrue("named CAN NOT create ref!", user.canCreateRef(repository));
-		assertTrue("named CAN NOT delete ref!", user.canDeleteRef(repository));
-		assertFalse("named CAN rewind ref!", user.canRewindRef(repository));
-
-		repository.allowForks = false;
-		user.canFork = false;
-		assertFalse("named CAN fork!", user.canFork(repository));
-		user.canFork = true;
-		assertFalse("named CAN fork!", user.canFork(repository));
-		repository.allowForks = true;
-		assertTrue("named CAN NOT fork!", user.canFork(repository));
-	}
-	
-	/**
-	 * CLONE_DELETE = CLONE access restriction, DELETE access permission
-	 */
-	@Test
-	public void testNamed_CLONE_DELETE() throws Exception {
-		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
-		repository.authorizationControl = AuthorizationControl.NAMED;
-		repository.accessRestriction = AccessRestrictionType.CLONE;
-		
-		UserModel user = new UserModel("test");
-		user.setRepositoryPermission(repository.name, AccessPermission.DELETE);
-
-		assertTrue("named CAN NOT view!", user.canView(repository));
-		assertTrue("named CAN NOT clone!", user.canClone(repository));
-		assertTrue("named CAN NOT push!", user.canPush(repository));
-		
-		assertTrue("named CAN NOT create ref!", user.canCreateRef(repository));
-		assertTrue("named CAN NOT delete red!", user.canDeleteRef(repository));
-		assertFalse("named CAN rewind ref!", user.canRewindRef(repository));
-
-		repository.allowForks = false;
-		user.canFork = false;
-		assertFalse("named CAN fork!", user.canFork(repository));
-		user.canFork = true;
-		assertFalse("named CAN fork!", user.canFork(repository));
-		repository.allowForks = true;
-		assertTrue("named CAN NOT fork!", user.canFork(repository));
-	}
-	
-	/**
-	 * VIEW_DELETE = VIEW access restriction, DELETE access permission
-	 */
-	@Test
-	public void testNamed_VIEW_DELETE() throws Exception {
-		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
-		repository.authorizationControl = AuthorizationControl.NAMED;
-		repository.accessRestriction = AccessRestrictionType.VIEW;
-		
-		UserModel user = new UserModel("test");
-		user.setRepositoryPermission(repository.name, AccessPermission.DELETE);
-
-		assertTrue("named CAN NOT view!", user.canView(repository));
-		assertTrue("named CAN NOT clone!", user.canClone(repository));
-		assertTrue("named CAN not push!", user.canPush(repository));
-		
-		assertTrue("named CAN NOT create ref!", user.canCreateRef(repository));
-		assertTrue("named CAN NOT delete ref!", user.canDeleteRef(repository));
-		assertFalse("named CAN rewind ref!", user.canRewindRef(repository));
-		
-		repository.allowForks = false;
-		user.canFork = false;
-		assertFalse("named CAN fork!", user.canFork(repository));
-		user.canFork = true;
-		assertFalse("named CAN fork!", user.canFork(repository));
-		repository.allowForks = true;
-		assertTrue("named CAN NOT fork!", user.canFork(repository));
-	}
-	
-	/**
-	 * NONE_REWIND = NO access restriction, REWIND access permission.
-	 * (not useful scenario)
-	 */
-	@Test
-	public void testNamed_NONE_REWIND() throws Exception {
-		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
-		repository.authorizationControl = AuthorizationControl.NAMED;
-		repository.accessRestriction = AccessRestrictionType.NONE;
-
-		UserModel user = new UserModel("test");
-		user.setRepositoryPermission(repository.name, AccessPermission.REWIND);
-		
-		assertTrue("named CAN NOT view!", user.canView(repository));
-		assertTrue("named CAN NOT clone!", user.canClone(repository));
-		assertTrue("named CAN NOT push!", user.canPush(repository));
-		
-		assertTrue("named CAN NOT create ref!", user.canCreateRef(repository));
-		assertTrue("named CAN NOT delete ref!", user.canDeleteRef(repository));
-		assertTrue("named CAN NOT rewind ref!", user.canRewindRef(repository));
-		
-		repository.allowForks = false;
-		user.canFork = false;
-		assertFalse("named CAN fork!", user.canFork(repository));
-		user.canFork = true;
-		assertFalse("named CAN fork!", user.canFork(repository));
-		repository.allowForks = true;
-		assertTrue("named CAN NOT fork!", user.canFork(repository));
-	}
-	
-	/**
-	 * PUSH_REWIND = PUSH access restriction, REWIND access permission
-	 */
-	@Test
-	public void testNamed_PUSH_REWIND() throws Exception {
-		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
-		repository.authorizationControl = AuthorizationControl.NAMED;
-		repository.accessRestriction = AccessRestrictionType.PUSH;
-		
-		UserModel user = new UserModel("test");
-		user.setRepositoryPermission(repository.name, AccessPermission.REWIND);
-		
-		assertTrue("named CAN NOT view!", user.canView(repository));
-		assertTrue("named CAN NOT clone!", user.canClone(repository));
-		assertTrue("named CAN NOT push!", user.canPush(repository));
-		
-		assertTrue("named CAN NOT create ref!", user.canCreateRef(repository));
-		assertTrue("named CAN NOT delete ref!", user.canDeleteRef(repository));
-		assertTrue("named CAN NOT rewind ref!", user.canRewindRef(repository));
-
-		repository.allowForks = false;
-		user.canFork = false;
-		assertFalse("named CAN fork!", user.canFork(repository));
-		user.canFork = true;
-		assertFalse("named CAN fork!", user.canFork(repository));
-		repository.allowForks = true;
-		assertTrue("named CAN NOT fork!", user.canFork(repository));
-	}
-	
-	/**
-	 * CLONE_REWIND = CLONE access restriction, REWIND access permission
-	 */
-	@Test
-	public void testNamed_CLONE_REWIND() throws Exception {
-		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
-		repository.authorizationControl = AuthorizationControl.NAMED;
-		repository.accessRestriction = AccessRestrictionType.CLONE;
-		
-		UserModel user = new UserModel("test");
-		user.setRepositoryPermission(repository.name, AccessPermission.REWIND);
-
-		assertTrue("named CAN NOT view!", user.canView(repository));
-		assertTrue("named CAN NOT clone!", user.canClone(repository));
-		assertTrue("named CAN NOT push!", user.canPush(repository));
-		
-		assertTrue("named CAN NOT create ref!", user.canCreateRef(repository));
-		assertTrue("named CAN NOT delete ref!", user.canDeleteRef(repository));
-		assertTrue("named CAN NOT rewind ref!", user.canRewindRef(repository));
-
-		repository.allowForks = false;
-		user.canFork = false;
-		assertFalse("named CAN fork!", user.canFork(repository));
-		user.canFork = true;
-		assertFalse("named CAN fork!", user.canFork(repository));
-		repository.allowForks = true;
-		assertTrue("named CAN NOT fork!", user.canFork(repository));
-	}
-	
-	/**
-	 * VIEW_REWIND = VIEW access restriction, REWIND access permission
-	 */
-	@Test
-	public void testNamed_VIEW_REWIND() throws Exception {
-		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
-		repository.authorizationControl = AuthorizationControl.NAMED;
-		repository.accessRestriction = AccessRestrictionType.VIEW;
-		
-		UserModel user = new UserModel("test");
-		user.setRepositoryPermission(repository.name, AccessPermission.REWIND);
-
-		assertTrue("named CAN NOT view!", user.canView(repository));
-		assertTrue("named CAN NOT clone!", user.canClone(repository));
-		assertTrue("named CAN NOT push!", user.canPush(repository));
-		
-		assertTrue("named CAN NOT create ref!", user.canCreateRef(repository));
-		assertTrue("named CAN NOT delete ref!", user.canDeleteRef(repository));
-		assertTrue("named CAN NOT rewind ref!", user.canRewindRef(repository));
-		
-		repository.allowForks = false;
-		user.canFork = false;
-		assertFalse("named CAN fork!", user.canFork(repository));
-		user.canFork = true;
-		assertFalse("named CAN fork!", user.canFork(repository));
-		repository.allowForks = true;
-		assertTrue("named CAN NOT fork!", user.canFork(repository));
-	}
-	
-	/**
-	 * NONE_NONE = NO access restriction, NO access permission
-	 */
-	@Test
-	public void testTeam_NONE_NONE() throws Exception {
-		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
-		repository.authorizationControl = AuthorizationControl.NAMED;
-		repository.accessRestriction = AccessRestrictionType.NONE;
-
-		TeamModel team = new TeamModel("test");
-		
-		assertTrue("team CAN NOT view!", team.canView(repository));
-		assertTrue("team CAN NOT clone!", team.canClone(repository));
-		assertTrue("team CAN NOT push!", team.canPush(repository));
-		
-		assertTrue("team CAN NOT create ref!", team.canCreateRef(repository));
-		assertTrue("team CAN NOT delete ref!", team.canDeleteRef(repository));
-		assertTrue("team CAN NOT rewind ref!", team.canRewindRef(repository));
-	}
-	
-	/**
-	 * PUSH_NONE = PUSH access restriction, NO access permission
-	 */
-	@Test
-	public void testTeam_PUSH_NONE() throws Exception {
-		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
-		repository.authorizationControl = AuthorizationControl.NAMED;
-		repository.accessRestriction = AccessRestrictionType.PUSH;
-
-		TeamModel team = new TeamModel("test");
-		
-		assertTrue("team CAN NOT view!", team.canView(repository));
-		assertTrue("team CAN NOT clone!", team.canClone(repository));
-		assertFalse("team CAN push!", team.canPush(repository));
-		
-		assertFalse("team CAN create ref!", team.canCreateRef(repository));
-		assertFalse("team CAN delete ref!", team.canDeleteRef(repository));
-		assertFalse("team CAN rewind ref!", team.canRewindRef(repository));
-	}
-
-	/**
-	 * CLONE_NONE = CLONE access restriction, NO access permission
-	 */
-	@Test
-	public void testTeam_CLONE_NONE() throws Exception {
-		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
-		repository.authorizationControl = AuthorizationControl.NAMED;
-		repository.accessRestriction = AccessRestrictionType.CLONE;
-
-		TeamModel team = new TeamModel("test");
-		
-		assertTrue("team CAN NOT view!", team.canView(repository));
-		assertFalse("team CAN clone!", team.canClone(repository));
-		assertFalse("team CAN push!", team.canPush(repository));
-		
-		assertFalse("team CAN create ref!", team.canCreateRef(repository));
-		assertFalse("team CAN delete ref!", team.canDeleteRef(repository));
-		assertFalse("team CAN rewind ref!", team.canRewindRef(repository));
-	}
-
-	/**
-	 * VIEW_NONE = VIEW access restriction, NO access permission
-	 */
-	@Test
-	public void testTeam_VIEW_NONE() throws Exception {
-		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
-		repository.authorizationControl = AuthorizationControl.NAMED;
-		repository.accessRestriction = AccessRestrictionType.VIEW;
-
-		TeamModel team = new TeamModel("test");
-		
-		assertFalse("team CAN view!", team.canView(repository));
-		assertFalse("team CAN clone!", team.canClone(repository));
-		assertFalse("team CAN push!", team.canPush(repository));
-		
-		assertFalse("team CAN create ref!", team.canCreateRef(repository));
-		assertFalse("team CAN delete ref!", team.canDeleteRef(repository));
-		assertFalse("team CAN rewind ref!", team.canRewindRef(repository));
-	}
-	
-	/**
-	 * NONE_PUSH = NO access restriction, PUSH access permission
-	 * (not useful scenario)
-	 */
-	@Test
-	public void testTeam_NONE_PUSH() throws Exception {
-		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
-		repository.authorizationControl = AuthorizationControl.NAMED;
-		repository.accessRestriction = AccessRestrictionType.NONE;
-
-		TeamModel team = new TeamModel("test");
-		team.setRepositoryPermission(repository.name, AccessPermission.PUSH);
-		
-		assertTrue("team CAN NOT view!", team.canView(repository));
-		assertTrue("team CAN NOT clone!", team.canClone(repository));
-		assertTrue("team CAN NOT push!", team.canPush(repository));
-		
-		assertTrue("team CAN NOT create ref!", team.canCreateRef(repository));
-		assertTrue("team CAN NOT delete ref!", team.canDeleteRef(repository));
-		assertTrue("team CAN NOT rewind ref!", team.canRewindRef(repository));
-	}
-	
-	/**
-	 * PUSH_PUSH = PUSH access restriction, PUSH access permission
-	 */
-	@Test
-	public void testTeam_PUSH_PUSH() throws Exception {
-		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
-		repository.authorizationControl = AuthorizationControl.NAMED;
-		repository.accessRestriction = AccessRestrictionType.PUSH;
-
-		TeamModel team = new TeamModel("test");
-		team.setRepositoryPermission(repository.name, AccessPermission.PUSH);
-		
-		assertTrue("team CAN NOT view!", team.canView(repository));
-		assertTrue("team CAN NOT clone!", team.canClone(repository));
-		assertTrue("team CAN NOT push!", team.canPush(repository));
-		
-		assertFalse("team CAN create ref!", team.canCreateRef(repository));
-		assertFalse("team CAN delete ref!", team.canDeleteRef(repository));
-		assertFalse("team CAN rewind ref!", team.canRewindRef(repository));
-	}
-	
-	/**
-	 * CLONE_PUSH = CLONE access restriction, PUSH access permission
-	 */
-	@Test
-	public void testTeam_CLONE_PUSH() throws Exception {
-		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
-		repository.authorizationControl = AuthorizationControl.NAMED;
-		repository.accessRestriction = AccessRestrictionType.CLONE;
-
-		TeamModel team = new TeamModel("test");
-		team.setRepositoryPermission(repository.name, AccessPermission.PUSH);
-		
-		assertTrue("team CAN NOT view!", team.canView(repository));
-		assertTrue("team CAN NOT clone!", team.canClone(repository));
-		assertTrue("team CAN NOT push!", team.canPush(repository));
-		
-		assertFalse("team CAN create ref!", team.canCreateRef(repository));
-		assertFalse("team CAN delete ref!", team.canDeleteRef(repository));
-		assertFalse("team CAN rewind ref!", team.canRewindRef(repository));
-	}
-	
-	/**
-	 * VIEW_PUSH = VIEW access restriction, PUSH access permission
-	 */
-	@Test
-	public void testTeam_VIEW_PUSH() throws Exception {
-		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
-		repository.authorizationControl = AuthorizationControl.NAMED;
-		repository.accessRestriction = AccessRestrictionType.VIEW;
-
-		TeamModel team = new TeamModel("test");
-		team.setRepositoryPermission(repository.name, AccessPermission.PUSH);
-		
-		assertTrue("team CAN NOT view!", team.canView(repository));
-		assertTrue("team CAN NOT clone!", team.canClone(repository));
-		assertTrue("team CAN NOT push!", team.canPush(repository));
-		
-		assertFalse("team CAN create ref!", team.canCreateRef(repository));
-		assertFalse("team CAN delete ref!", team.canDeleteRef(repository));
-		assertFalse("team CAN rewind ref!", team.canRewindRef(repository));
-	}
-	
-	/**
-	 * NONE_CREATE = NO access restriction, CREATE access permission
-	 * (not useful scenario)
-	 */
-	@Test
-	public void testTeam_NONE_CREATE() throws Exception {
-		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
-		repository.authorizationControl = AuthorizationControl.NAMED;
-		repository.accessRestriction = AccessRestrictionType.NONE;
-
-		TeamModel team = new TeamModel("test");
-		team.setRepositoryPermission(repository.name, AccessPermission.CREATE);
-		
-		assertTrue("team CAN NOT view!", team.canView(repository));
-		assertTrue("team CAN NOT clone!", team.canClone(repository));
-		assertTrue("team CAN NOT push!", team.canPush(repository));
-		
-		assertTrue("team CAN NOT create ref!", team.canCreateRef(repository));
-		assertTrue("team CAN NOT delete ref!", team.canDeleteRef(repository));
-		assertTrue("team CAN NOT rewind ref!", team.canRewindRef(repository));
-	}
-	
-	/**
-	 * PUSH_CREATE = PUSH access restriction, CREATE access permission
-	 */
-	@Test
-	public void testTeam_PUSH_CREATE() throws Exception {
-		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
-		repository.authorizationControl = AuthorizationControl.NAMED;
-		repository.accessRestriction = AccessRestrictionType.PUSH;
-
-		TeamModel team = new TeamModel("test");
-		team.setRepositoryPermission(repository.name, AccessPermission.CREATE);
-		
-		assertTrue("team CAN NOT view!", team.canView(repository));
-		assertTrue("team CAN NOT clone!", team.canClone(repository));
-		assertTrue("team CAN NOT push!", team.canPush(repository));
-		
-		assertTrue("team CAN NOT create ref!", team.canCreateRef(repository));
-		assertFalse("team CAN delete ref!", team.canDeleteRef(repository));
-		assertFalse("team CAN rewind ref!", team.canRewindRef(repository));
-	}
-	
-	/**
-	 * CLONE_CREATE = CLONE access restriction, CREATE access permission
-	 */
-	@Test
-	public void testTeam_CLONE_CREATE() throws Exception {
-		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
-		repository.authorizationControl = AuthorizationControl.NAMED;
-		repository.accessRestriction = AccessRestrictionType.CLONE;
-
-		TeamModel team = new TeamModel("test");
-		team.setRepositoryPermission(repository.name, AccessPermission.CREATE);
-		
-		assertTrue("team CAN NOT view!", team.canView(repository));
-		assertTrue("team CAN NOT clone!", team.canClone(repository));
-		assertTrue("team CAN NOT push!", team.canPush(repository));
-		
-		assertTrue("team CAN NOT create ref!", team.canCreateRef(repository));
-		assertFalse("team CAN delete ref!", team.canDeleteRef(repository));
-		assertFalse("team CAN rewind ref!", team.canRewindRef(repository));
-	}
-	
-	/**
-	 * VIEW_CREATE = VIEW access restriction, CREATE access permission
-	 */
-	@Test
-	public void testTeam_VIEW_CREATE() throws Exception {
-		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
-		repository.authorizationControl = AuthorizationControl.NAMED;
-		repository.accessRestriction = AccessRestrictionType.VIEW;
-
-		TeamModel team = new TeamModel("test");
-		team.setRepositoryPermission(repository.name, AccessPermission.CREATE);
-		
-		assertTrue("team CAN NOT view!", team.canView(repository));
-		assertTrue("team CAN NOT clone!", team.canClone(repository));
-		assertTrue("team CAN NOT push!", team.canPush(repository));
-		
-		assertTrue("team CAN NOT create ref!", team.canCreateRef(repository));
-		assertFalse("team CAN delete ref!", team.canDeleteRef(repository));
-		assertFalse("team CAN rewind ref!", team.canRewindRef(repository));
-	}
-
-	/**
-	 * NONE_DELETE = NO access restriction, DELETE access permission
-	 * (not useful scenario)
-	 */
-	@Test
-	public void testTeam_NONE_DELETE() throws Exception {
-		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
-		repository.authorizationControl = AuthorizationControl.NAMED;
-		repository.accessRestriction = AccessRestrictionType.NONE;
-
-		TeamModel team = new TeamModel("test");
-		team.setRepositoryPermission(repository.name, AccessPermission.DELETE);
-		
-		assertTrue("team CAN NOT view!", team.canView(repository));
-		assertTrue("team CAN NOT clone!", team.canClone(repository));
-		assertTrue("team CAN NOT push!", team.canPush(repository));
-		
-		assertTrue("team CAN NOT create ref!", team.canCreateRef(repository));
-		assertTrue("team CAN NOT delete ref!", team.canDeleteRef(repository));
-		assertTrue("team CAN NOT rewind ref!", team.canRewindRef(repository));
-	}
-	
-	/**
-	 * PUSH_DELETE = PUSH access restriction, DELETE access permission
-	 */
-	@Test
-	public void testTeam_PUSH_DELETE() throws Exception {
-		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
-		repository.authorizationControl = AuthorizationControl.NAMED;
-		repository.accessRestriction = AccessRestrictionType.PUSH;
-
-		TeamModel team = new TeamModel("test");
-		team.setRepositoryPermission(repository.name, AccessPermission.DELETE);
-		
-		assertTrue("team CAN NOT view!", team.canView(repository));
-		assertTrue("team CAN NOT clone!", team.canClone(repository));
-		assertTrue("team CAN NOT push!", team.canPush(repository));
-		
-		assertTrue("team CAN NOT create ref!", team.canCreateRef(repository));
-		assertTrue("team CAN NOT delete ref!", team.canDeleteRef(repository));
-		assertFalse("team CAN rewind ref!", team.canRewindRef(repository));
-	}
-	
-	/**
-	 * CLONE_DELETE = CLONE access restriction, DELETE access permission
-	 */
-	@Test
-	public void testTeam_CLONE_DELETE() throws Exception {
-		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
-		repository.authorizationControl = AuthorizationControl.NAMED;
-		repository.accessRestriction = AccessRestrictionType.CLONE;
-
-		TeamModel team = new TeamModel("test");
-		team.setRepositoryPermission(repository.name, AccessPermission.DELETE);
-		
-		assertTrue("team CAN NOT view!", team.canView(repository));
-		assertTrue("team CAN NOT clone!", team.canClone(repository));
-		assertTrue("team CAN NOT push!", team.canPush(repository));
-		
-		assertTrue("team CAN NOT create ref!", team.canCreateRef(repository));
-		assertTrue("team CAN NOT delete ref!", team.canDeleteRef(repository));
-		assertFalse("team CAN rewind ref!", team.canRewindRef(repository));
-	}
-	
-	/**
-	 * VIEW_DELETE = VIEW access restriction, DELETE access permission
-	 */
-	@Test
-	public void testTeam_VIEW_DELETE() throws Exception {
-		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
-		repository.authorizationControl = AuthorizationControl.NAMED;
-		repository.accessRestriction = AccessRestrictionType.VIEW;
-
-		TeamModel team = new TeamModel("test");
-		team.setRepositoryPermission(repository.name, AccessPermission.DELETE);
-		
-		assertTrue("team CAN NOT view!", team.canView(repository));
-		assertTrue("team CAN NOT clone!", team.canClone(repository));
-		assertTrue("team CAN NOT push!", team.canPush(repository));
-		
-		assertTrue("team CAN NOT create ref!", team.canCreateRef(repository));
-		assertTrue("team CAN NOT delete ref!", team.canDeleteRef(repository));
-		assertFalse("team CAN rewind ref!", team.canRewindRef(repository));
-	}
-	
-	/**
-	 * NONE_REWIND = NO access restriction, REWIND access permission
-	 * (not useful scenario)
-	 */
-	@Test
-	public void testTeam_NONE_REWIND() throws Exception {
-		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
-		repository.authorizationControl = AuthorizationControl.NAMED;
-		repository.accessRestriction = AccessRestrictionType.NONE;
-
-		TeamModel team = new TeamModel("test");
-		team.setRepositoryPermission(repository.name, AccessPermission.REWIND);
-		
-		assertTrue("team CAN NOT view!", team.canView(repository));
-		assertTrue("team CAN NOT clone!", team.canClone(repository));
-		assertTrue("team CAN NOT push!", team.canPush(repository));
-		
-		assertTrue("team CAN NOT create ref!", team.canCreateRef(repository));
-		assertTrue("team CAN NOT delete ref!", team.canDeleteRef(repository));
-		assertTrue("team CAN NOT rewind ref!", team.canRewindRef(repository));
-	}
-	
-	/**
-	 * PUSH_REWIND = PUSH access restriction, REWIND access permission
-	 */
-	@Test
-	public void testTeam_PUSH_REWIND() throws Exception {
-		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
-		repository.authorizationControl = AuthorizationControl.NAMED;
-		repository.accessRestriction = AccessRestrictionType.PUSH;
-
-		TeamModel team = new TeamModel("test");
-		team.setRepositoryPermission(repository.name, AccessPermission.REWIND);
-		
-		assertTrue("team CAN NOT view!", team.canView(repository));
-		assertTrue("team CAN NOT clone!", team.canClone(repository));
-		assertTrue("team CAN NOT push!", team.canPush(repository));
-		
-		assertTrue("team CAN NOT create ref!", team.canCreateRef(repository));
-		assertTrue("team CAN NOT delete ref!", team.canDeleteRef(repository));
-		assertTrue("team CAN NOT rewind ref!", team.canRewindRef(repository));
-	}
-	
-	/**
-	 * CLONE_REWIND = CLONE access restriction, REWIND access permission
-	 */
-	@Test
-	public void testTeam_CLONE_REWIND() throws Exception {
-		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
-		repository.authorizationControl = AuthorizationControl.NAMED;
-		repository.accessRestriction = AccessRestrictionType.CLONE;
-
-		TeamModel team = new TeamModel("test");
-		team.setRepositoryPermission(repository.name, AccessPermission.REWIND);
-		
-		assertTrue("team CAN NOT view!", team.canView(repository));
-		assertTrue("team CAN NOT clone!", team.canClone(repository));
-		assertTrue("team CAN NOT push!", team.canPush(repository));
-		
-		assertTrue("team CAN NOT create ref!", team.canCreateRef(repository));
-		assertTrue("team CAN NOT delete ref!", team.canDeleteRef(repository));
-		assertTrue("team CAN NOT rewind ref!", team.canRewindRef(repository));
-	}
-	
-	/**
-	 * VIEW_REWIND = VIEW access restriction, REWIND access permission
-	 */
-	@Test
-	public void testTeam_VIEW_REWIND() throws Exception {
-		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
-		repository.authorizationControl = AuthorizationControl.NAMED;
-		repository.accessRestriction = AccessRestrictionType.VIEW;
-
-		TeamModel team = new TeamModel("test");
-		team.setRepositoryPermission(repository.name, AccessPermission.REWIND);
-		
-		assertTrue("team CAN NOT view!", team.canView(repository));
-		assertTrue("team CAN NOT clone!", team.canClone(repository));
-		assertTrue("team CAN NOT push!", team.canPush(repository));
-		
-		assertTrue("team CAN NOT create ref!", team.canCreateRef(repository));
-		assertTrue("team CAN NOT delete ref!", team.canDeleteRef(repository));
-		assertTrue("team CAN NOT rewind ref!", team.canRewindRef(repository));
-	}
-	
-	/**
-	 * NONE_CLONE = NO access restriction, CLONE access permission
-	 * (not useful scenario)
-	 */
-	@Test
-	public void testTeam_NONE_CLONE() throws Exception {
-		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
-		repository.authorizationControl = AuthorizationControl.NAMED;
-		repository.accessRestriction = AccessRestrictionType.NONE;
-
-		TeamModel team = new TeamModel("test");
-		team.setRepositoryPermission(repository.name, AccessPermission.CLONE);
-		
-		assertTrue("team CAN NOT view!", team.canView(repository));
-		assertTrue("team CAN NOT clone!", team.canClone(repository));
-		assertTrue("team CAN NOT push!", team.canPush(repository));
-		
-		assertTrue("team CAN NOT create ref!", team.canCreateRef(repository));
-		assertTrue("team CAN NOT delete ref!", team.canDeleteRef(repository));
-		assertTrue("team CAN NOT rewind ref!", team.canRewindRef(repository));
-	}
-
-	/**
-	 * PUSH_CLONE = PUSH access restriction, CLONE access permission
-	 */
-	@Test
-	public void testTeam_PUSH_CLONE() throws Exception {
-		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
-		repository.authorizationControl = AuthorizationControl.NAMED;
-		repository.accessRestriction = AccessRestrictionType.PUSH;
-
-		TeamModel team = new TeamModel("test");
-		team.setRepositoryPermission(repository.name, AccessPermission.CLONE);
-		
-		assertTrue("team CAN NOT view!", team.canView(repository));
-		assertTrue("team CAN NOT clone!", team.canClone(repository));
-		assertFalse("team CAN push!", team.canPush(repository));
-		
-		assertFalse("team CAN create ref!", team.canCreateRef(repository));
-		assertFalse("team CAN delete ref!", team.canDeleteRef(repository));
-		assertFalse("team CAN rewind ref!", team.canRewindRef(repository));
-	}
-
-	/**
-	 * CLONE_CLONE = CLONE access restriction, CLONE access permission
-	 */
-	@Test
-	public void testTeam_CLONE_CLONE() throws Exception {
-		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
-		repository.authorizationControl = AuthorizationControl.NAMED;
-		repository.accessRestriction = AccessRestrictionType.CLONE;
-
-		TeamModel team = new TeamModel("test");
-		team.setRepositoryPermission(repository.name, AccessPermission.CLONE);
-		
-		assertTrue("team CAN NOT view!", team.canView(repository));
-		assertTrue("team CAN NOT clone!", team.canClone(repository));
-		assertFalse("team CAN push!", team.canPush(repository));
-		
-		assertFalse("team CAN create ref!", team.canCreateRef(repository));
-		assertFalse("team CAN delete ref!", team.canDeleteRef(repository));
-		assertFalse("team CAN rewind ref!", team.canRewindRef(repository));
-	}
-	
-	/**
-	 * VIEW_CLONE = VIEW access restriction, CLONE access permission
-	 */
-	@Test
-	public void testTeam_VIEW_CLONE() throws Exception {
-		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
-		repository.authorizationControl = AuthorizationControl.NAMED;
-		repository.accessRestriction = AccessRestrictionType.VIEW;
-
-		TeamModel team = new TeamModel("test");
-		team.setRepositoryPermission(repository.name, AccessPermission.CLONE);
-		
-		assertTrue("team CAN NOT view!", team.canView(repository));
-		assertTrue("team CAN NOT clone!", team.canClone(repository));
-		assertFalse("team CAN push!", team.canPush(repository));
-		
-		assertFalse("team CAN create ref!", team.canCreateRef(repository));
-		assertFalse("team CAN delete ref!", team.canDeleteRef(repository));
-		assertFalse("team CAN rewind ref!", team.canRewindRef(repository));
-	}
-
-	/**
-	 * NONE_VIEW = NO access restriction, VIEW access permission
-	 * (not useful scenario)
-	 */
-	@Test
-	public void testTeam_NONE_VIEW() throws Exception {
-		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
-		repository.authorizationControl = AuthorizationControl.NAMED;
-		repository.accessRestriction = AccessRestrictionType.NONE;
-
-		TeamModel team = new TeamModel("test");
-		team.setRepositoryPermission(repository.name, AccessPermission.VIEW);
-		
-		assertTrue("team CAN NOT view!", team.canView(repository));
-		assertTrue("team CAN NOT clone!", team.canClone(repository));
-		assertTrue("team CAN NOT push!", team.canPush(repository));
-		
-		assertTrue("team CAN NOT create ref!", team.canCreateRef(repository));
-		assertTrue("team CAN NOT delete ref!", team.canDeleteRef(repository));
-		assertTrue("team CAN NOT rewind ref!", team.canRewindRef(repository));
-	}
-
-	/**
-	 * PUSH_VIEW = PUSH access restriction, VIEW access permission
-	 */
-	@Test
-	public void testTeam_PUSH_VIEW() throws Exception {
-		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
-		repository.authorizationControl = AuthorizationControl.NAMED;
-		repository.accessRestriction = AccessRestrictionType.PUSH;
-
-		TeamModel team = new TeamModel("test");
-		team.setRepositoryPermission(repository.name, AccessPermission.VIEW);
-		
-		assertTrue("team CAN NOT view!", team.canView(repository));
-		assertTrue("team CAN NOT clone!", team.canClone(repository));
-		assertFalse("team CAN push!", team.canPush(repository));
-		
-		assertFalse("team CAN create ref!", team.canCreateRef(repository));
-		assertFalse("team CAN delete ref!", team.canDeleteRef(repository));
-		assertFalse("team CAN rewind ref!", team.canRewindRef(repository));
-	}
-	
-	/**
-	 * CLONE_VIEW = CLONE access restriction, VIEW access permission
-	 */
-	@Test
-	public void testTeam_CLONE_VIEW() throws Exception {
-		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
-		repository.authorizationControl = AuthorizationControl.NAMED;
-		repository.accessRestriction = AccessRestrictionType.CLONE;
-
-		TeamModel team = new TeamModel("test");
-		team.setRepositoryPermission(repository.name, AccessPermission.VIEW);
-		
-		assertTrue("team CAN NOT view!", team.canView(repository));
-		assertFalse("team CAN clone!", team.canClone(repository));
-		assertFalse("team CAN push!", team.canPush(repository));
-		
-		assertFalse("team CAN create ref!", team.canCreateRef(repository));
-		assertFalse("team CAN delete ref!", team.canDeleteRef(repository));
-		assertFalse("team CAN rewind ref!", team.canRewindRef(repository));
-	}
-	
-	/**
-	 * VIEW_VIEW = VIEW access restriction, VIEW access permission
-	 */
-	@Test
-	public void testTeam_VIEW_VIEW() throws Exception {
-		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
-		repository.authorizationControl = AuthorizationControl.NAMED;
-		repository.accessRestriction = AccessRestrictionType.VIEW;
-
-		TeamModel team = new TeamModel("test");
-		team.setRepositoryPermission(repository.name, AccessPermission.VIEW);
-		
-		assertTrue("team CAN NOT view!", team.canView(repository));
-		assertFalse("team CAN clone!", team.canClone(repository));
-		assertFalse("team CAN push!", team.canPush(repository));
-		
-		assertFalse("team CAN create ref!", team.canCreateRef(repository));
-		assertFalse("team CAN delete ref!", team.canDeleteRef(repository));
-		assertFalse("team CAN rewind ref!", team.canRewindRef(repository));
-	}
-	
-	/**
-	 * NONE_NONE = NO access restriction, NO access permission
-	 */
-	@Test
-	public void testTeamMember_NONE_NONE() throws Exception {
-		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
-		repository.authorizationControl = AuthorizationControl.NAMED;
-		repository.accessRestriction = AccessRestrictionType.NONE;
-
-		TeamModel team = new TeamModel("test");
-		UserModel user = new UserModel("test");
-		user.teams.add(team);
-		
-		assertTrue("team member CAN NOT view!", user.canView(repository));
-		assertTrue("team member CAN NOT clone!", user.canClone(repository));
-		assertTrue("team member CAN NOT push!", user.canPush(repository));
-		
-		assertTrue("team member CAN NOT create ref!", user.canCreateRef(repository));
-		assertTrue("team member CAN NOT delete ref!", user.canDeleteRef(repository));
-		assertTrue("team member CAN NOT rewind ref!", user.canRewindRef(repository));
-	}
-	
-	/**
-	 * PUSH_NONE = PUSH access restriction, NO access permission
-	 */
-	@Test
-	public void testTeamMember_PUSH_NONE() throws Exception {
-		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
-		repository.authorizationControl = AuthorizationControl.NAMED;
-		repository.accessRestriction = AccessRestrictionType.PUSH;
-
-		TeamModel team = new TeamModel("test");
-		UserModel user = new UserModel("test");
-		user.teams.add(team);
-		
-		assertTrue("team member CAN NOT view!", user.canView(repository));
-		assertTrue("team member CAN NOT clone!", user.canClone(repository));
-		assertFalse("team member CAN push!", user.canPush(repository));
-		
-		assertFalse("team member CAN create ref!", user.canCreateRef(repository));
-		assertFalse("team member CAN delete ref!", user.canDeleteRef(repository));
-		assertFalse("team member CAN rewind ref!", user.canRewindRef(repository));
-	}
-
-	/**
-	 * CLONE_NONE = CLONE access restriction, NO access permission
-	 */
-	@Test
-	public void testTeamMember_CLONE_NONE() throws Exception {
-		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
-		repository.authorizationControl = AuthorizationControl.NAMED;
-		repository.accessRestriction = AccessRestrictionType.CLONE;
-
-		TeamModel team = new TeamModel("test");
-		UserModel user = new UserModel("test");
-		user.teams.add(team);
-		
-		assertTrue("team member CAN NOT view!", user.canView(repository));
-		assertFalse("team member CAN clone!", user.canClone(repository));
-		assertFalse("team member CAN push!", user.canPush(repository));
-		
-		assertFalse("team member CAN create ref!", user.canCreateRef(repository));
-		assertFalse("team member CAN delete ref!", user.canDeleteRef(repository));
-		assertFalse("team member CAN rewind ref!", user.canRewindRef(repository));
-	}
-
-	/**
-	 * VIEW_NONE = VIEW access restriction, NO access permission
-	 */
-	@Test
-	public void testTeamMember_VIEW_NONE() throws Exception {
-		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
-		repository.authorizationControl = AuthorizationControl.NAMED;
-		repository.accessRestriction = AccessRestrictionType.VIEW;
-
-		TeamModel team = new TeamModel("test");
-		UserModel user = new UserModel("test");
-		user.teams.add(team);
-		
-		assertFalse("team member CAN view!", user.canView(repository));
-		assertFalse("team member CAN clone!", user.canClone(repository));
-		assertFalse("team member CAN push!", user.canPush(repository));
-		
-		assertFalse("team member CAN create ref!", user.canCreateRef(repository));
-		assertFalse("team member CAN delete ref!", user.canDeleteRef(repository));
-		assertFalse("team member CAN rewind ref!", user.canRewindRef(repository));
-	}
-	
-	/**
-	 * NONE_PUSH = NO access restriction, PUSH access permission
-	 * (not useful scenario)
-	 */
-	@Test
-	public void testTeamMember_NONE_PUSH() throws Exception {
-		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
-		repository.authorizationControl = AuthorizationControl.NAMED;
-		repository.accessRestriction = AccessRestrictionType.NONE;
-
-		TeamModel team = new TeamModel("test");
-		team.setRepositoryPermission(repository.name, AccessPermission.PUSH);
-		UserModel user = new UserModel("test");
-		user.teams.add(team);
-		
-		assertTrue("team member CAN NOT view!", user.canView(repository));
-		assertTrue("team member CAN NOT clone!", user.canClone(repository));
-		assertTrue("team member CAN NOT push!", user.canPush(repository));
-		
-		assertTrue("team member CAN NOT create ref!", user.canCreateRef(repository));
-		assertTrue("team member CAN NOT delete ref!", user.canDeleteRef(repository));
-		assertTrue("team member CAN NOT rewind ref!", user.canRewindRef(repository));
-	}
-	
-	/**
-	 * PUSH_PUSH = PUSH access restriction, PUSH access permission
-	 */
-	@Test
-	public void testTeamMember_PUSH_PUSH() throws Exception {
-		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
-		repository.authorizationControl = AuthorizationControl.NAMED;
-		repository.accessRestriction = AccessRestrictionType.PUSH;
-
-		TeamModel team = new TeamModel("test");
-		team.setRepositoryPermission(repository.name, AccessPermission.PUSH);
-		UserModel user = new UserModel("test");
-		user.teams.add(team);
-
-		assertTrue("team member CAN NOT view!", user.canView(repository));
-		assertTrue("team member CAN NOT clone!", user.canClone(repository));
-		assertTrue("team member CAN NOT push!", user.canPush(repository));
-		
-		assertFalse("team member CAN create ref!", user.canCreateRef(repository));
-		assertFalse("team member CAN delete ref!", user.canDeleteRef(repository));
-		assertFalse("team member CAN rewind ref!", user.canRewindRef(repository));
-	}
-	
-	/**
-	 * CLONE_PUSH = CLONE access restriction, PUSH access permission
-	 */
-	@Test
-	public void testTeamMember_CLONE_PUSH() throws Exception {
-		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
-		repository.authorizationControl = AuthorizationControl.NAMED;
-		repository.accessRestriction = AccessRestrictionType.CLONE;
-
-		TeamModel team = new TeamModel("test");
-		team.setRepositoryPermission(repository.name, AccessPermission.PUSH);
-		UserModel user = new UserModel("test");
-		user.teams.add(team);
-
-		assertTrue("team member CAN NOT view!", user.canView(repository));
-		assertTrue("team member CAN NOT clone!", user.canClone(repository));
-		assertTrue("team member CAN NOT push!", user.canPush(repository));
-		
-		assertFalse("team member CAN create ref!", user.canCreateRef(repository));
-		assertFalse("team member CAN delete ref!", user.canDeleteRef(repository));
-		assertFalse("team member CAN rewind ref!", user.canRewindRef(repository));
-	}
-	
-	/**
-	 * VIEW_PUSH = VIEW access restriction, PUSH access permission
-	 */
-	@Test
-	public void testTeamMember_VIEW_PUSH() throws Exception {
-		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
-		repository.authorizationControl = AuthorizationControl.NAMED;
-		repository.accessRestriction = AccessRestrictionType.VIEW;
-
-		TeamModel team = new TeamModel("test");
-		team.setRepositoryPermission(repository.name, AccessPermission.PUSH);
-		UserModel user = new UserModel("test");
-		user.teams.add(team);
-
-		assertTrue("team member CAN NOT view!", user.canView(repository));
-		assertTrue("team member CAN NOT clone!", user.canClone(repository));
-		assertTrue("team member CAN NOT push!", user.canPush(repository));
-		
-		assertFalse("team member CAN create ref!", user.canCreateRef(repository));
-		assertFalse("team member CAN delete ref!", user.canDeleteRef(repository));
-		assertFalse("team member CAN rewind ref!", user.canRewindRef(repository));
-	}
-	
-	/**
-	 * NONE_CREATE = NO access restriction, CREATE access permission
-	 * (not useful scenario)
-	 */
-	@Test
-	public void testTeamMember_NONE_CREATE() throws Exception {
-		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
-		repository.authorizationControl = AuthorizationControl.NAMED;
-		repository.accessRestriction = AccessRestrictionType.NONE;
-
-		TeamModel team = new TeamModel("test");
-		team.setRepositoryPermission(repository.name, AccessPermission.CREATE);
-		UserModel user = new UserModel("test");
-		user.teams.add(team);
-		
-		assertTrue("team member CAN NOT view!", user.canView(repository));
-		assertTrue("team member CAN NOT clone!", user.canClone(repository));
-		assertTrue("team member CAN NOT push!", user.canPush(repository));
-		
-		assertTrue("team member CAN NOT create ref!", user.canCreateRef(repository));
-		assertTrue("team member CAN NOT delete ref!", user.canDeleteRef(repository));
-		assertTrue("team member CAN NOT rewind ref!", user.canRewindRef(repository));
-	}
-	
-	/**
-	 * PUSH_CREATE = PUSH access restriction, CREATE access permission
-	 */
-	@Test
-	public void testTeamMember_PUSH_CREATE() throws Exception {
-		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
-		repository.authorizationControl = AuthorizationControl.NAMED;
-		repository.accessRestriction = AccessRestrictionType.PUSH;
-
-		TeamModel team = new TeamModel("test");
-		team.setRepositoryPermission(repository.name, AccessPermission.CREATE);
-		UserModel user = new UserModel("test");
-		user.teams.add(team);
-
-		assertTrue("team member CAN NOT view!", user.canView(repository));
-		assertTrue("team member CAN NOT clone!", user.canClone(repository));
-		assertTrue("team member CAN NOT push!", user.canPush(repository));
-		
-		assertTrue("team member CAN NOT create ref!", user.canCreateRef(repository));
-		assertFalse("team member CAN delete ref!", user.canDeleteRef(repository));
-		assertFalse("team member CAN rewind ref!", user.canRewindRef(repository));
-	}
-	
-	/**
-	 * CLONE_CREATE = CLONE access restriction, CREATE access permission
-	 */
-	@Test
-	public void testTeamMember_CLONE_CREATE() throws Exception {
-		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
-		repository.authorizationControl = AuthorizationControl.NAMED;
-		repository.accessRestriction = AccessRestrictionType.CLONE;
-
-		TeamModel team = new TeamModel("test");
-		team.setRepositoryPermission(repository.name, AccessPermission.CREATE);
-		UserModel user = new UserModel("test");
-		user.teams.add(team);
-
-		assertTrue("team member CAN NOT view!", user.canView(repository));
-		assertTrue("team member CAN NOT clone!", user.canClone(repository));
-		assertTrue("team member CAN NOT push!", user.canPush(repository));
-		
-		assertTrue("team member CAN NOT create ref!", user.canCreateRef(repository));
-		assertFalse("team member CAN delete ref!", user.canDeleteRef(repository));
-		assertFalse("team member CAN rewind ref!", user.canRewindRef(repository));
-	}
-	
-	/**
-	 * VIEW_CREATE = VIEW access restriction, CREATE access permission
-	 */
-	@Test
-	public void testTeamMember_VIEW_CREATE() throws Exception {
-		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
-		repository.authorizationControl = AuthorizationControl.NAMED;
-		repository.accessRestriction = AccessRestrictionType.VIEW;
-
-		TeamModel team = new TeamModel("test");
-		team.setRepositoryPermission(repository.name, AccessPermission.CREATE);
-		UserModel user = new UserModel("test");
-		user.teams.add(team);
-
-		assertTrue("team member CAN NOT view!", user.canView(repository));
-		assertTrue("team member CAN NOT clone!", user.canClone(repository));
-		assertTrue("team member CAN NOT push!", user.canPush(repository));
-		
-		assertTrue("team member CAN NOT create ref!", user.canCreateRef(repository));
-		assertFalse("team member CAN delete ref!", user.canDeleteRef(repository));
-		assertFalse("team member CAN rewind ref!", user.canRewindRef(repository));
-	}
-
-	/**
-	 * NONE_DELETE = NO access restriction, DELETE access permission
-	 * (not useful scenario)
-	 */
-	@Test
-	public void testTeamMember_NONE_DELETE() throws Exception {
-		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
-		repository.authorizationControl = AuthorizationControl.NAMED;
-		repository.accessRestriction = AccessRestrictionType.NONE;
-
-		TeamModel team = new TeamModel("test");
-		team.setRepositoryPermission(repository.name, AccessPermission.DELETE);
-		UserModel user = new UserModel("test");
-		user.teams.add(team);
-		
-		assertTrue("team member CAN NOT view!", user.canView(repository));
-		assertTrue("team member CAN NOT clone!", user.canClone(repository));
-		assertTrue("team member CAN NOT push!", user.canPush(repository));
-		
-		assertTrue("team member CAN NOT create ref!", user.canCreateRef(repository));
-		assertTrue("team member CAN NOT delete ref!", user.canDeleteRef(repository));
-		assertTrue("team member CAN NOT rewind ref!", user.canRewindRef(repository));
-	}
-	
-	/**
-	 * PUSH_DELETE = PUSH access restriction, DELETE access permission
-	 */
-	@Test
-	public void testTeamMember_PUSH_DELETE() throws Exception {
-		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
-		repository.authorizationControl = AuthorizationControl.NAMED;
-		repository.accessRestriction = AccessRestrictionType.PUSH;
-
-		TeamModel team = new TeamModel("test");
-		team.setRepositoryPermission(repository.name, AccessPermission.DELETE);
-		UserModel user = new UserModel("test");
-		user.teams.add(team);
-
-		assertTrue("team member CAN NOT view!", user.canView(repository));
-		assertTrue("team member CAN NOT clone!", user.canClone(repository));
-		assertTrue("team member CAN NOT push!", user.canPush(repository));
-		
-		assertTrue("team member CAN NOT create ref!", user.canCreateRef(repository));
-		assertTrue("team member CAN NOT delete ref!", user.canDeleteRef(repository));
-		assertFalse("team member CAN rewind ref!", user.canRewindRef(repository));
-	}
-	
-	/**
-	 * CLONE_DELETE = CLONE access restriction, DELETE access permission
-	 */
-	@Test
-	public void testTeamMember_CLONE_DELETE() throws Exception {
-		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
-		repository.authorizationControl = AuthorizationControl.NAMED;
-		repository.accessRestriction = AccessRestrictionType.CLONE;
-
-		TeamModel team = new TeamModel("test");
-		team.setRepositoryPermission(repository.name, AccessPermission.DELETE);
-		UserModel user = new UserModel("test");
-		user.teams.add(team);
-
-		assertTrue("team member CAN NOT view!", user.canView(repository));
-		assertTrue("team member CAN NOT clone!", user.canClone(repository));
-		assertTrue("team member CAN NOT push!", user.canPush(repository));
-		
-		assertTrue("team member CAN NOT create ref!", user.canCreateRef(repository));
-		assertTrue("team member CAN NOT delete ref!", user.canDeleteRef(repository));
-		assertFalse("team member CAN rewind ref!", user.canRewindRef(repository));
-	}
-	
-	/**
-	 * VIEW_DELETE = VIEW access restriction, DELETE access permission
-	 */
-	@Test
-	public void testTeamMember_VIEW_DELETE() throws Exception {
-		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
-		repository.authorizationControl = AuthorizationControl.NAMED;
-		repository.accessRestriction = AccessRestrictionType.VIEW;
-
-		TeamModel team = new TeamModel("test");
-		team.setRepositoryPermission(repository.name, AccessPermission.DELETE);
-		UserModel user = new UserModel("test");
-		user.teams.add(team);
-
-		assertTrue("team member CAN NOT view!", user.canView(repository));
-		assertTrue("team member CAN NOT clone!", user.canClone(repository));
-		assertTrue("team member CAN NOT push!", user.canPush(repository));
-		
-		assertTrue("team member CAN NOT create ref!", user.canCreateRef(repository));
-		assertTrue("team member CAN NOT delete ref!", user.canDeleteRef(repository));
-		assertFalse("team member CAN rewind ref!", user.canRewindRef(repository));
-	}
-
-	/**
-	 * NONE_REWIND = NO access restriction, REWIND access permission
-	 * (not useful scenario)
-	 */
-	@Test
-	public void testTeamMember_NONE_REWIND() throws Exception {
-		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
-		repository.authorizationControl = AuthorizationControl.NAMED;
-		repository.accessRestriction = AccessRestrictionType.NONE;
-
-		TeamModel team = new TeamModel("test");
-		team.setRepositoryPermission(repository.name, AccessPermission.REWIND);
-		UserModel user = new UserModel("test");
-		user.teams.add(team);
-		
-		assertTrue("team member CAN NOT view!", user.canView(repository));
-		assertTrue("team member CAN NOT clone!", user.canClone(repository));
-		assertTrue("team member CAN NOT push!", user.canPush(repository));
-		
-		assertTrue("team member CAN NOT create ref!", user.canCreateRef(repository));
-		assertTrue("team member CAN NOT delete ref!", user.canDeleteRef(repository));
-		assertTrue("team member CAN NOT rewind ref!", user.canRewindRef(repository));
-	}
-	
-	/**
-	 * PUSH_REWIND = PUSH access restriction, REWIND access permission
-	 */
-	@Test
-	public void testTeamMember_PUSH_REWIND() throws Exception {
-		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
-		repository.authorizationControl = AuthorizationControl.NAMED;
-		repository.accessRestriction = AccessRestrictionType.PUSH;
-
-		TeamModel team = new TeamModel("test");
-		team.setRepositoryPermission(repository.name, AccessPermission.REWIND);
-		UserModel user = new UserModel("test");
-		user.teams.add(team);
-
-		assertTrue("team member CAN NOT view!", user.canView(repository));
-		assertTrue("team member CAN NOT clone!", user.canClone(repository));
-		assertTrue("team member CAN NOT push!", user.canPush(repository));
-		
-		assertTrue("team member CAN NOT create ref!", user.canCreateRef(repository));
-		assertTrue("team member CAN NOT delete ref!", user.canDeleteRef(repository));
-		assertTrue("team member CAN NOT rewind ref!", user.canRewindRef(repository));
-	}
-	
-	/**
-	 * CLONE_REWIND = CLONE access restriction, REWIND access permission
-	 */
-	@Test
-	public void testTeamMember_CLONE_REWIND() throws Exception {
-		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
-		repository.authorizationControl = AuthorizationControl.NAMED;
-		repository.accessRestriction = AccessRestrictionType.CLONE;
-
-		TeamModel team = new TeamModel("test");
-		team.setRepositoryPermission(repository.name, AccessPermission.REWIND);
-		UserModel user = new UserModel("test");
-		user.teams.add(team);
-
-		assertTrue("team member CAN NOT view!", user.canView(repository));
-		assertTrue("team member CAN NOT clone!", user.canClone(repository));
-		assertTrue("team member CAN NOT push!", user.canPush(repository));
-		
-		assertTrue("team member CAN NOT create ref!", user.canCreateRef(repository));
-		assertTrue("team member CAN NOT delete ref!", user.canDeleteRef(repository));
-		assertTrue("team member CAN NOT rewind ref!", user.canRewindRef(repository));
-	}
-	
-	/**
-	 * VIEW_REWIND = VIEW access restriction, REWIND access permission
-	 */
-	@Test
-	public void testTeamMember_VIEW_REWIND() throws Exception {
-		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
-		repository.authorizationControl = AuthorizationControl.NAMED;
-		repository.accessRestriction = AccessRestrictionType.VIEW;
-
-		TeamModel team = new TeamModel("test");
-		team.setRepositoryPermission(repository.name, AccessPermission.REWIND);
-		UserModel user = new UserModel("test");
-		user.teams.add(team);
-
-		assertTrue("team member CAN NOT view!", user.canView(repository));
-		assertTrue("team member CAN NOT clone!", user.canClone(repository));
-		assertTrue("team member CAN NOT push!", user.canPush(repository));
-		
-		assertTrue("team member CAN NOT create ref!", user.canCreateRef(repository));
-		assertTrue("team member CAN NOT delete ref!", user.canDeleteRef(repository));
-		assertTrue("team member CAN NOT rewind ref!", user.canRewindRef(repository));
-	}
-	
-	/**
-	 * NONE_CLONE = NO access restriction, CLONE access permission
-	 * (not useful scenario)
-	 */
-	@Test
-	public void testTeamMember_NONE_CLONE() throws Exception {
-		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
-		repository.authorizationControl = AuthorizationControl.NAMED;
-		repository.accessRestriction = AccessRestrictionType.NONE;
-
-		TeamModel team = new TeamModel("test");
-		team.setRepositoryPermission(repository.name, AccessPermission.CLONE);
-		UserModel user = new UserModel("test");
-		user.teams.add(team);
-
-		assertTrue("team member CAN NOT view!", user.canView(repository));
-		assertTrue("team member CAN NOT clone!", user.canClone(repository));
-		assertTrue("team member CAN NOT push!", user.canPush(repository));
-		
-		assertTrue("team member CAN NOT create ref!", user.canCreateRef(repository));
-		assertTrue("team member CAN NOT delete ref!", user.canDeleteRef(repository));
-		assertTrue("team member CAN NOT rewind ref!", user.canRewindRef(repository));
-	}
-
-	/**
-	 * PUSH_CLONE = PUSH access restriction, CLONE access permission
-	 */
-	@Test
-	public void testTeamMember_PUSH_CLONE() throws Exception {
-		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
-		repository.authorizationControl = AuthorizationControl.NAMED;
-		repository.accessRestriction = AccessRestrictionType.PUSH;
-
-		TeamModel team = new TeamModel("test");
-		team.setRepositoryPermission(repository.name, AccessPermission.CLONE);
-		UserModel user = new UserModel("test");
-		user.teams.add(team);
-
-		assertTrue("team member CAN NOT view!", user.canView(repository));
-		assertTrue("team member CAN NOT clone!", user.canClone(repository));
-		assertFalse("team member CAN push!", user.canPush(repository));
-		
-		assertFalse("team member CAN create ref!", user.canCreateRef(repository));
-		assertFalse("team member CAN delete ref!", user.canDeleteRef(repository));
-		assertFalse("team member CAN rewind ref!", user.canRewindRef(repository));
-	}
-
-	/**
-	 * CLONE_CLONE = CLONE access restriction, CLONE access permission
-	 */
-	@Test
-	public void testTeamMember_CLONE_CLONE() throws Exception {
-		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
-		repository.authorizationControl = AuthorizationControl.NAMED;
-		repository.accessRestriction = AccessRestrictionType.CLONE;
-
-		TeamModel team = new TeamModel("test");
-		team.setRepositoryPermission(repository.name, AccessPermission.CLONE);
-		UserModel user = new UserModel("test");
-		user.teams.add(team);
-
-		assertTrue("team member CAN NOT view!", user.canView(repository));
-		assertTrue("team member CAN NOT clone!", user.canClone(repository));
-		assertFalse("team member CAN push!", user.canPush(repository));
-		
-		assertFalse("team member CAN create ref!", user.canCreateRef(repository));
-		assertFalse("team member CAN delete ref!", user.canDeleteRef(repository));
-		assertFalse("team member CAN rewind ref!", user.canRewindRef(repository));
-	}
-	
-	/**
-	 * VIEW_CLONE = VIEW access restriction, CLONE access permission
-	 */
-	@Test
-	public void testTeamMember_VIEW_CLONE() throws Exception {
-		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
-		repository.authorizationControl = AuthorizationControl.NAMED;
-		repository.accessRestriction = AccessRestrictionType.VIEW;
-
-		TeamModel team = new TeamModel("test");
-		team.setRepositoryPermission(repository.name, AccessPermission.CLONE);
-		UserModel user = new UserModel("test");
-		user.teams.add(team);
-
-		assertTrue("team member CAN NOT view!", user.canView(repository));
-		assertTrue("team member CAN NOT clone!", user.canClone(repository));
-		assertFalse("team member CAN push!", user.canPush(repository));
-		
-		assertFalse("team member CAN create ref!", user.canCreateRef(repository));
-		assertFalse("team member CAN delete ref!", user.canDeleteRef(repository));
-		assertFalse("team member CAN rewind ref!", user.canRewindRef(repository));
-	}
-
-	/**
-	 * NONE_VIEW = NO access restriction, VIEW access permission
-	 * (not useful scenario)
-	 */
-	@Test
-	public void testTeamMember_NONE_VIEW() throws Exception {
-		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
-		repository.authorizationControl = AuthorizationControl.NAMED;
-		repository.accessRestriction = AccessRestrictionType.NONE;
-
-		TeamModel team = new TeamModel("test");
-		team.setRepositoryPermission(repository.name, AccessPermission.VIEW);
-		UserModel user = new UserModel("test");
-		user.teams.add(team);
-
-		assertTrue("team member CAN NOT view!", user.canView(repository));
-		assertTrue("team member CAN NOT clone!", user.canClone(repository));
-		assertTrue("team member CAN NOT push!", user.canPush(repository));
-		
-		assertTrue("team member CAN NOT create ref!", user.canCreateRef(repository));
-		assertTrue("team member CAN NOT delete ref!", user.canDeleteRef(repository));
-		assertTrue("team member CAN NOT rewind ref!", user.canRewindRef(repository));
-	}
-
-	/**
-	 * PUSH_VIEW = PUSH access restriction, VIEW access permission
-	 */
-	@Test
-	public void testTeamMember_PUSH_VIEW() throws Exception {
-		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
-		repository.authorizationControl = AuthorizationControl.NAMED;
-		repository.accessRestriction = AccessRestrictionType.PUSH;
-
-		TeamModel team = new TeamModel("test");
-		team.setRepositoryPermission(repository.name, AccessPermission.VIEW);
-		UserModel user = new UserModel("test");
-		user.teams.add(team);
-
-		assertTrue("team member CAN NOT view!", user.canView(repository));
-		assertTrue("team member CAN NOT clone!", user.canClone(repository));
-		assertFalse("team member CAN push!", user.canPush(repository));
-		
-		assertFalse("team member CAN create ref!", user.canCreateRef(repository));
-		assertFalse("team member CAN delete ref!", user.canDeleteRef(repository));
-		assertFalse("team member CAN rewind ref!", user.canRewindRef(repository));
-	}
-	
-	/**
-	 * CLONE_VIEW = CLONE access restriction, VIEW access permission
-	 */
-	@Test
-	public void testTeamMember_CLONE_VIEW() throws Exception {
-		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
-		repository.authorizationControl = AuthorizationControl.NAMED;
-		repository.accessRestriction = AccessRestrictionType.CLONE;
-
-		TeamModel team = new TeamModel("test");
-		team.setRepositoryPermission(repository.name, AccessPermission.VIEW);
-		UserModel user = new UserModel("test");
-		user.teams.add(team);
-
-		assertTrue("team member CAN NOT view!", user.canView(repository));
-		assertFalse("team member CAN clone!", user.canClone(repository));
-		assertFalse("team member CAN push!", user.canPush(repository));
-		
-		assertFalse("team member CAN create ref!", user.canCreateRef(repository));
-		assertFalse("team member CAN delete ref!", user.canDeleteRef(repository));
-		assertFalse("team member CAN rewind ref!", user.canRewindRef(repository));
-	}
-	
-	/**
-	 * VIEW_VIEW = VIEW access restriction, VIEW access permission
-	 */
-	@Test
-	public void testTeamMember_VIEW_VIEW() throws Exception {
-		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
-		repository.authorizationControl = AuthorizationControl.NAMED;
-		repository.accessRestriction = AccessRestrictionType.VIEW;
-
-		TeamModel team = new TeamModel("test");
-		team.setRepositoryPermission(repository.name, AccessPermission.VIEW);
-		UserModel user = new UserModel("test");
-		user.teams.add(team);
-
-		assertTrue("team member CAN NOT view!", user.canView(repository));
-		assertFalse("team member CAN clone!", user.canClone(repository));
-		assertFalse("team member CAN push!", user.canPush(repository));
-		
-		assertFalse("team member CAN create ref!", user.canCreateRef(repository));
-		assertFalse("team member CAN delete ref!", user.canDeleteRef(repository));
-		assertFalse("team member CAN rewind ref!", user.canRewindRef(repository));
-	}
-	
-	@Test
-	public void testOwner() throws Exception {
-		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
-		repository.authorizationControl = AuthorizationControl.NAMED;
-		repository.accessRestriction = AccessRestrictionType.VIEW;
-
-		UserModel user = new UserModel("test");
-		repository.addOwner(user.username);
-
-		assertFalse("user SHOULD NOT HAVE a repository permission!", user.hasRepositoryPermission(repository.name));
-		assertTrue("owner CAN NOT view!", user.canView(repository));
-		assertTrue("owner CAN NOT clone!", user.canClone(repository));
-		assertTrue("owner CAN NOT push!", user.canPush(repository));
-		
-		assertTrue("owner CAN NOT create ref!", user.canCreateRef(repository));
-		assertTrue("owner CAN NOT delete ref!", user.canDeleteRef(repository));
-		assertTrue("owner CAN NOT rewind ref!", user.canRewindRef(repository));
-
-		assertTrue("owner CAN NOT fork!", user.canFork(repository));
-		
-		assertFalse("owner CAN NOT delete!", user.canDelete(repository));
-		assertTrue("owner CAN NOT edit!", user.canEdit(repository));
-	}
-	
-	@Test
-	public void testMultipleOwners() throws Exception {
-		RepositoryModel repository = new RepositoryModel("myrepo.git", null, null, new Date());
-		repository.authorizationControl = AuthorizationControl.NAMED;
-		repository.accessRestriction = AccessRestrictionType.VIEW;
-
-		UserModel user = new UserModel("test");
-		repository.addOwner(user.username);
-		UserModel user2 = new UserModel("test2");
-		repository.addOwner(user2.username);
-
-		// first owner
-		assertFalse("user SHOULD NOT HAVE a repository permission!", user.hasRepositoryPermission(repository.name));
-		assertTrue("owner CAN NOT view!", user.canView(repository));
-		assertTrue("owner CAN NOT clone!", user.canClone(repository));
-		assertTrue("owner CAN NOT push!", user.canPush(repository));
-		
-		assertTrue("owner CAN NOT create ref!", user.canCreateRef(repository));
-		assertTrue("owner CAN NOT delete ref!", user.canDeleteRef(repository));
-		assertTrue("owner CAN NOT rewind ref!", user.canRewindRef(repository));
-
-		assertTrue("owner CAN NOT fork!", user.canFork(repository));
-		
-		assertFalse("owner CAN NOT delete!", user.canDelete(repository));
-		assertTrue("owner CAN NOT edit!", user.canEdit(repository));
-		
-		// second owner
-		assertFalse("user SHOULD NOT HAVE a repository permission!", user2.hasRepositoryPermission(repository.name));
-		assertTrue("owner CAN NOT view!", user2.canView(repository));
-		assertTrue("owner CAN NOT clone!", user2.canClone(repository));
-		assertTrue("owner CAN NOT push!", user2.canPush(repository));
-		
-		assertTrue("owner CAN NOT create ref!", user2.canCreateRef(repository));
-		assertTrue("owner CAN NOT delete ref!", user2.canDeleteRef(repository));
-		assertTrue("owner CAN NOT rewind ref!", user2.canRewindRef(repository));
-
-		assertTrue("owner CAN NOT fork!", user2.canFork(repository));
-		
-		assertFalse("owner CAN NOT delete!", user2.canDelete(repository));
-		assertTrue("owner CAN NOT edit!", user2.canEdit(repository));
-		
-		assertTrue(repository.isOwner(user.username));
-		assertTrue(repository.isOwner(user2.username));	
-	}
-	
-	@Test
-	public void testOwnerPersonalRepository() throws Exception {
-		RepositoryModel repository = new RepositoryModel("~test/myrepo.git", null, null, new Date());
-		repository.authorizationControl = AuthorizationControl.NAMED;
-		repository.accessRestriction = AccessRestrictionType.VIEW;
-
-		UserModel user = new UserModel("test");
-		repository.addOwner(user.username);
-
-		assertFalse("user SHOULD NOT HAVE a repository permission!", user.hasRepositoryPermission(repository.name));
-		assertTrue("user CAN NOT view!", user.canView(repository));
-		assertTrue("user CAN NOT clone!", user.canClone(repository));
-		assertTrue("user CAN NOT push!", user.canPush(repository));
-		
-		assertTrue("user CAN NOT create ref!", user.canCreateRef(repository));
-		assertTrue("user CAN NOT delete ref!", user.canDeleteRef(repository));
-		assertTrue("user CAN NOT rewind ref!", user.canRewindRef(repository));
-
-		assertFalse("user CAN fork!", user.canFork(repository));
-		
-		assertTrue("user CAN NOT delete!", user.canDelete(repository));
-		assertTrue("user CAN NOT edit!", user.canEdit(repository));
-	}
-
-	@Test
-	public void testVisitorPersonalRepository() throws Exception {
-		RepositoryModel repository = new RepositoryModel("~test/myrepo.git", null, null, new Date());
-		repository.authorizationControl = AuthorizationControl.NAMED;
-		repository.accessRestriction = AccessRestrictionType.VIEW;
-
-		UserModel user = new UserModel("visitor");
-		repository.addOwner("test");
-
-		assertFalse("user HAS a repository permission!", user.hasRepositoryPermission(repository.name));
-		assertFalse("user CAN view!", user.canView(repository));
-		assertFalse("user CAN clone!", user.canClone(repository));
-		assertFalse("user CAN push!", user.canPush(repository));
-		
-		assertFalse("user CAN create ref!", user.canCreateRef(repository));
-		assertFalse("user CAN delete ref!", user.canDeleteRef(repository));
-		assertFalse("user CAN rewind ref!", user.canRewindRef(repository));
-
-		assertFalse("user CAN fork!", user.canFork(repository));
-		
-		assertFalse("user CAN delete!", user.canDelete(repository));
-		assertFalse("user CAN edit!", user.canEdit(repository));
-	}
-	
-	@Test
-	public void testRegexMatching() throws Exception {
-		RepositoryModel repository = new RepositoryModel("ubercool/_my-r/e~po.git", null, null, new Date());
-		repository.authorizationControl = AuthorizationControl.NAMED;
-		repository.accessRestriction = AccessRestrictionType.VIEW;
-
-		UserModel user = new UserModel("test");
-		user.setRepositoryPermission("ubercool/[A-Z0-9-~_\\./]+", AccessPermission.CLONE);
-
-		assertTrue("user DOES NOT HAVE a repository permission!", user.hasRepositoryPermission(repository.name));
-		assertTrue("user CAN NOT view!", user.canView(repository));
-		assertTrue("user CAN NOT clone!", user.canClone(repository));
-		assertFalse("user CAN push!", user.canPush(repository));
-		
-		assertFalse("user CAN create ref!", user.canCreateRef(repository));
-		assertFalse("user CAN delete ref!", user.canDeleteRef(repository));
-		assertFalse("user CAN rewind ref!", user.canRewindRef(repository));
-
-		assertFalse("user CAN fork!", user.canFork(repository));
-		
-		assertFalse("user CAN delete!", user.canDelete(repository));
-		assertFalse("user CAN edit!", user.canEdit(repository));
-	}
-
-	@Test
-	public void testRegexIncludeCommonExcludePersonal() throws Exception {
-		
-		UserModel user = new UserModel("test");
-		user.setRepositoryPermission("[^~].*", AccessPermission.CLONE);
-
-		// common
-		RepositoryModel common = new RepositoryModel("ubercool/_my-r/e~po.git", null, null, new Date());
-		common.authorizationControl = AuthorizationControl.NAMED;
-		common.accessRestriction = AccessRestrictionType.VIEW;
-		
-		assertTrue("user DOES NOT HAVE a repository permission!", user.hasRepositoryPermission(common.name));
-		assertTrue("user CAN NOT view!", user.canView(common));
-		assertTrue("user CAN NOT clone!", user.canClone(common));
-		assertFalse("user CAN push!", user.canPush(common));
-		
-		assertFalse("user CAN create ref!", user.canCreateRef(common));
-		assertFalse("user CAN delete ref!", user.canDeleteRef(common));
-		assertFalse("user CAN rewind ref!", user.canRewindRef(common));
-
-		assertFalse("user CAN fork!", user.canFork(common));
-		
-		assertFalse("user CAN delete!", user.canDelete(common));
-		assertFalse("user CAN edit!", user.canEdit(common));
-
-		// personal
-		RepositoryModel personal = new RepositoryModel("~ubercool/_my-r/e~po.git", null, null, new Date());
-		personal.authorizationControl = AuthorizationControl.NAMED;
-		personal.accessRestriction = AccessRestrictionType.VIEW;
-		
-		assertFalse("user HAS a repository permission!", user.hasRepositoryPermission(personal.name));
-		assertFalse("user CAN NOT view!", user.canView(personal));
-		assertFalse("user CAN NOT clone!", user.canClone(personal));
-		assertFalse("user CAN push!", user.canPush(personal));
-		
-		assertFalse("user CAN create ref!", user.canCreateRef(personal));
-		assertFalse("user CAN delete ref!", user.canDeleteRef(personal));
-		assertFalse("user CAN rewind ref!", user.canRewindRef(personal));
-
-		assertFalse("user CAN fork!", user.canFork(personal));
-		
-		assertFalse("user CAN delete!", user.canDelete(personal));
-		assertFalse("user CAN edit!", user.canEdit(personal));
-	}
-	
-	@Test
-	public void testRegexMatching2() throws Exception {
-		RepositoryModel personal = new RepositoryModel("~ubercool/_my-r/e~po.git", null, null, new Date());
-		personal.authorizationControl = AuthorizationControl.NAMED;
-		personal.accessRestriction = AccessRestrictionType.VIEW;
-
-		UserModel user = new UserModel("test");
-		// permit all repositories excluding all personal rpeositories
-		user.setRepositoryPermission("[^~].*", AccessPermission.CLONE);
-		// permitall  ~ubercool repositories
-		user.setRepositoryPermission("~ubercool/.*", AccessPermission.CLONE);
-		
-		// personal
-		assertTrue("user DOES NOT HAVE a repository permission!", user.hasRepositoryPermission(personal.name));
-		assertTrue("user CAN NOT view!", user.canView(personal));
-		assertTrue("user CAN NOT clone!", user.canClone(personal));
-		assertFalse("user CAN push!", user.canPush(personal));
-		
-		assertFalse("user CAN create ref!", user.canCreateRef(personal));
-		assertFalse("user CAN delete ref!", user.canDeleteRef(personal));
-		assertFalse("user CAN rewind ref!", user.canRewindRef(personal));
-
-		assertFalse("user CAN fork!", user.canFork(personal));
-		
-		assertFalse("user CAN delete!", user.canDelete(personal));
-		assertFalse("user CAN edit!", user.canEdit(personal));
-	}
-	
-	@Test
-	public void testRegexOrder() throws Exception {
-		RepositoryModel personal = new RepositoryModel("~ubercool/_my-r/e~po.git", null, null, new Date());
-		personal.authorizationControl = AuthorizationControl.NAMED;
-		personal.accessRestriction = AccessRestrictionType.VIEW;
-
-		UserModel user = new UserModel("test");
-		user.setRepositoryPermission(".*", AccessPermission.PUSH);
-		user.setRepositoryPermission("~ubercool/.*", AccessPermission.CLONE);
-		
-		// has PUSH access because first match is PUSH permission 
-		assertTrue("user HAS a repository permission!", user.hasRepositoryPermission(personal.name));
-		assertTrue("user CAN NOT view!", user.canView(personal));
-		assertTrue("user CAN NOT clone!", user.canClone(personal));
-		assertTrue("user CAN NOT push!", user.canPush(personal));
-		
-		assertFalse("user CAN create ref!", user.canCreateRef(personal));
-		assertFalse("user CAN delete ref!", user.canDeleteRef(personal));
-		assertFalse("user CAN rewind ref!", user.canRewindRef(personal));
-
-		assertFalse("user CAN fork!", user.canFork(personal));
-		
-		assertFalse("user CAN delete!", user.canDelete(personal));
-		assertFalse("user CAN edit!", user.canEdit(personal));
-				
-		user.permissions.clear();
-		user.setRepositoryPermission("~ubercool/.*", AccessPermission.CLONE);
-		user.setRepositoryPermission(".*", AccessPermission.PUSH);
-		
-		// has CLONE access because first match is CLONE permission
-		assertTrue("user HAS a repository permission!", user.hasRepositoryPermission(personal.name));
-		assertTrue("user CAN NOT view!", user.canView(personal));
-		assertTrue("user CAN NOT clone!", user.canClone(personal));
-		assertFalse("user CAN push!", user.canPush(personal));
-				
-		assertFalse("user CAN create ref!", user.canCreateRef(personal));
-		assertFalse("user CAN delete ref!", user.canDeleteRef(personal));
-		assertFalse("user CAN rewind ref!", user.canRewindRef(personal));
-
-		assertFalse("user CAN fork!", user.canFork(personal));
-				
-		assertFalse("user CAN delete!", user.canDelete(personal));
-		assertFalse("user CAN edit!", user.canEdit(personal));
-	}
-	
-	@Test
-	public void testExclusion() throws Exception {
-		RepositoryModel personal = new RepositoryModel("~ubercool/_my-r/e~po.git", null, null, new Date());
-		personal.authorizationControl = AuthorizationControl.NAMED;
-		personal.accessRestriction = AccessRestrictionType.VIEW;
-
-		UserModel user = new UserModel("test");
-		user.setRepositoryPermission("~ubercool/.*", AccessPermission.EXCLUDE);
-		user.setRepositoryPermission(".*", AccessPermission.PUSH);
-		
-		// has EXCLUDE access because first match is EXCLUDE permission
-		assertTrue("user DOES NOT HAVE a repository permission!", user.hasRepositoryPermission(personal.name));
-		assertFalse("user CAN NOT view!", user.canView(personal));
-		assertFalse("user CAN NOT clone!", user.canClone(personal));
-		assertFalse("user CAN push!", user.canPush(personal));
-				
-		assertFalse("user CAN create ref!", user.canCreateRef(personal));
-		assertFalse("user CAN delete ref!", user.canDeleteRef(personal));
-		assertFalse("user CAN rewind ref!", user.canRewindRef(personal));
-
-		assertFalse("user CAN fork!", user.canFork(personal));
-				
-		assertFalse("user CAN delete!", user.canDelete(personal));
-		assertFalse("user CAN edit!", user.canEdit(personal));
-	}
-
-	@Test
-	public void testAdminTeamInheritance() throws Exception {
-		UserModel user = new UserModel("test");
-		TeamModel team = new TeamModel("team");
-		team.canAdmin = true;
-		user.teams.add(team);
-		assertTrue("User did not inherit admin privileges", user.canAdmin());
-	}
-	
-	@Test
-	public void testForkTeamInheritance() throws Exception {
-		UserModel user = new UserModel("test");
-		TeamModel team = new TeamModel("team");
-		team.canFork = true;
-		user.teams.add(team);
-		assertTrue("User did not inherit fork privileges", user.canFork());
-	}
-
-	@Test
-	public void testCreateTeamInheritance() throws Exception {
-		UserModel user = new UserModel("test");
-		TeamModel team = new TeamModel("team");
-		team.canCreate= true;
-		user.teams.add(team);
-		assertTrue("User did not inherit create privileges", user.canCreate());
-	}
-
-}
diff --git a/tests/com/gitblit/tests/PushLogTest.java b/tests/com/gitblit/tests/PushLogTest.java
deleted file mode 100644
index aa4cf41..0000000
--- a/tests/com/gitblit/tests/PushLogTest.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright 2013 gitblit.com.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.gitblit.tests;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.List;
-
-import org.eclipse.jgit.storage.file.FileRepository;
-import org.junit.Test;
-
-import com.gitblit.models.PushLogEntry;
-import com.gitblit.utils.PushLogUtils;
-
-public class PushLogTest {
-
-	@Test
-	public void testPushLog() throws IOException {
-		String name = "~james/helloworld.git";
-		FileRepository repository = new FileRepository(new File(GitBlitSuite.REPOSITORIES, name));
-		List<PushLogEntry> pushes = PushLogUtils.getPushLog(name, repository);
-		GitBlitSuite.close(repository);
-	}
-}
\ No newline at end of file
diff --git a/tests/com/gitblit/tests/RpcTests.java b/tests/com/gitblit/tests/RpcTests.java
deleted file mode 100644
index 3241a8a..0000000
--- a/tests/com/gitblit/tests/RpcTests.java
+++ /dev/null
@@ -1,382 +0,0 @@
-/*
- * Copyright 2011 gitblit.com.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.gitblit.tests;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-import org.junit.Test;
-
-import com.gitblit.Constants.AccessPermission;
-import com.gitblit.Constants.AccessRestrictionType;
-import com.gitblit.Constants.AuthorizationControl;
-import com.gitblit.Constants.PermissionType;
-import com.gitblit.Constants.RegistrantType;
-import com.gitblit.GitBlitException.UnauthorizedException;
-import com.gitblit.Keys;
-import com.gitblit.RpcServlet;
-import com.gitblit.models.RegistrantAccessPermission;
-import com.gitblit.models.FederationModel;
-import com.gitblit.models.FederationProposal;
-import com.gitblit.models.FederationSet;
-import com.gitblit.models.RepositoryModel;
-import com.gitblit.models.ServerSettings;
-import com.gitblit.models.ServerStatus;
-import com.gitblit.models.TeamModel;
-import com.gitblit.models.UserModel;
-import com.gitblit.utils.RpcUtils;
-
-/**
- * Tests all the rpc client utility methods, the rpc filter and rpc servlet.
- * 
- * @author James Moger
- * 
- */
-public class RpcTests {
-
-	String url = GitBlitSuite.url;
-	String account = GitBlitSuite.account;
-	String password = GitBlitSuite.password;
-
-	private static final AtomicBoolean started = new AtomicBoolean(false);
-
-	@BeforeClass
-	public static void startGitblit() throws Exception {
-		started.set(GitBlitSuite.startGitblit());
-	}
-
-	@AfterClass
-	public static void stopGitblit() throws Exception {
-		if (started.get()) {
-			GitBlitSuite.stopGitblit();
-		}
-	}
-
-	@Test
-	public void testGetProtocolVersion() throws IOException {
-		int protocol = RpcUtils.getProtocolVersion(url, null, null);
-		assertEquals(RpcServlet.PROTOCOL_VERSION, protocol);
-	}
-
-	@Test
-	public void testListRepositories() throws IOException {
-		Map<String, RepositoryModel> map = RpcUtils.getRepositories(url, null, null);
-		assertNotNull("Repository list is null!", map);
-		assertTrue("Repository list is empty!", map.size() > 0);
-	}
-
-	@Test
-	public void testListUsers() throws IOException {
-		List<UserModel> list = null;
-		try {
-			list = RpcUtils.getUsers(url, null, null);
-		} catch (UnauthorizedException e) {
-		}
-		assertNull("Server allows anyone to admin!", list);
-
-		list = RpcUtils.getUsers(url, "admin", "admin".toCharArray());
-		assertTrue("User list is empty!", list.size() > 0);
-	}
-
-	@Test
-	public void testListTeams() throws IOException {
-		List<TeamModel> list = null;
-		try {
-			list = RpcUtils.getTeams(url, null, null);
-		} catch (UnauthorizedException e) {
-		}
-		assertNull("Server allows anyone to admin!", list);
-
-		list = RpcUtils.getTeams(url, "admin", "admin".toCharArray());
-		assertTrue("Team list is empty!", list.size() > 0);
-		assertEquals("admins", list.get(0).name);
-	}
-
-	@Test
-	public void testUserAdministration() throws IOException {
-		UserModel user = new UserModel("garbage");
-		user.canAdmin = true;
-		user.password = "whocares";
-
-		// create
-		assertTrue("Failed to create user!",
-				RpcUtils.createUser(user, url, account, password.toCharArray()));
-
-		UserModel retrievedUser = findUser(user.username);
-		assertNotNull("Failed to find " + user.username, retrievedUser);
-		assertTrue("Retrieved user can not administer Gitblit", retrievedUser.canAdmin);
-
-		// rename and toggle admin permission
-		String originalName = user.username;
-		user.username = "garbage2";
-		user.canAdmin = false;
-		assertTrue("Failed to update user!",
-				RpcUtils.updateUser(originalName, user, url, account, password.toCharArray()));
-
-		retrievedUser = findUser(user.username);
-		assertNotNull("Failed to find " + user.username, retrievedUser);
-		assertTrue("Retrieved user did not update", !retrievedUser.canAdmin);
-
-		// delete
-		assertTrue("Failed to delete " + user.username,
-				RpcUtils.deleteUser(retrievedUser, url, account, password.toCharArray()));
-
-		retrievedUser = findUser(user.username);
-		assertNull("Failed to delete " + user.username, retrievedUser);
-	}
-
-	private UserModel findUser(String name) throws IOException {
-		List<UserModel> users = RpcUtils.getUsers(url, account, password.toCharArray());
-		UserModel retrievedUser = null;
-		for (UserModel model : users) {
-			if (model.username.equalsIgnoreCase(name)) {
-				retrievedUser = model;
-				break;
-			}
-		}
-		return retrievedUser;
-	}
-
-	@Test
-	public void testRepositoryAdministration() throws IOException {
-		RepositoryModel model = new RepositoryModel();
-		model.name = "garbagerepo.git";
-		model.description = "created by RpcUtils";
-		model.addOwner("garbage");
-		model.accessRestriction = AccessRestrictionType.VIEW;
-		model.authorizationControl = AuthorizationControl.AUTHENTICATED;
-
-		// create
-		assertTrue("Failed to create repository!",
-				RpcUtils.createRepository(model, url, account, password.toCharArray()));
-
-		RepositoryModel retrievedRepository = findRepository(model.name);
-		assertNotNull("Failed to find " + model.name, retrievedRepository);
-		assertEquals(AccessRestrictionType.VIEW, retrievedRepository.accessRestriction);
-		assertEquals(AuthorizationControl.AUTHENTICATED, retrievedRepository.authorizationControl);
-
-		// rename and change access restriciton
-		String originalName = model.name;
-		model.name = "garbagerepo2.git";
-		model.accessRestriction = AccessRestrictionType.PUSH;
-		model.authorizationControl = AuthorizationControl.NAMED;
-		assertTrue("Failed to update repository!", RpcUtils.updateRepository(originalName, model,
-				url, account, password.toCharArray()));
-
-		retrievedRepository = findRepository(model.name);
-		assertNotNull("Failed to find " + model.name, retrievedRepository);
-		assertTrue("Access retriction type is wrong",
-				AccessRestrictionType.PUSH.equals(retrievedRepository.accessRestriction));
-
-		// memberships
-		UserModel testMember = new UserModel("justadded");
-		assertTrue(RpcUtils.createUser(testMember, url, account, password.toCharArray()));
-
-		List<RegistrantAccessPermission> permissions = RpcUtils.getRepositoryMemberPermissions(retrievedRepository, url, account,
-				password.toCharArray());
-		assertEquals("Membership permissions is not empty!", 0, permissions.size());
-		permissions.add(new RegistrantAccessPermission(testMember.username, AccessPermission.PUSH, PermissionType.EXPLICIT, RegistrantType.USER, null, true));
-		assertTrue(
-				"Failed to set member permissions!",
-				RpcUtils.setRepositoryMemberPermissions(retrievedRepository, permissions, url, account,
-						password.toCharArray()));
-		permissions = RpcUtils.getRepositoryMemberPermissions(retrievedRepository, url, account,
-				password.toCharArray());
-		boolean foundMember = false;
-		for (RegistrantAccessPermission permission : permissions) {
-			if (permission.registrant.equalsIgnoreCase(testMember.username)) {
-				foundMember = true;
-				assertEquals(AccessPermission.PUSH, permission.permission);
-				break;
-			}
-		}
-		assertTrue("Failed to find member!", foundMember);
-
-		// delete
-		assertTrue("Failed to delete " + model.name, RpcUtils.deleteRepository(retrievedRepository,
-				url, account, password.toCharArray()));
-
-		retrievedRepository = findRepository(model.name);
-		assertNull("Failed to delete " + model.name, retrievedRepository);
-
-		for (UserModel u : RpcUtils.getUsers(url, account, password.toCharArray())) {
-			if (u.username.equals(testMember.username)) {
-				assertTrue(RpcUtils.deleteUser(u, url, account, password.toCharArray()));
-				break;
-			}
-		}
-	}
-
-	private RepositoryModel findRepository(String name) throws IOException {
-		Map<String, RepositoryModel> repositories = RpcUtils.getRepositories(url, account,
-				password.toCharArray());
-		RepositoryModel retrievedRepository = null;
-		for (RepositoryModel model : repositories.values()) {
-			if (model.name.equalsIgnoreCase(name)) {
-				retrievedRepository = model;
-				break;
-			}
-		}
-		return retrievedRepository;
-	}
-
-	@Test
-	public void testTeamAdministration() throws IOException {
-		List<TeamModel> teams = RpcUtils.getTeams(url, account, password.toCharArray());
-		assertEquals(1, teams.size());
-		
-		// Create the A-Team
-		TeamModel aTeam = new TeamModel("A-Team");
-		aTeam.users.add("admin");
-		aTeam.addRepositoryPermission("helloworld.git");
-		assertTrue(RpcUtils.createTeam(aTeam, url, account, password.toCharArray()));
-
-		aTeam = null;
-		teams = RpcUtils.getTeams(url, account, password.toCharArray());
-		assertEquals(2, teams.size());
-		for (TeamModel team : teams) {
-			if (team.name.equals("A-Team")) {
-				aTeam = team;
-				break;
-			}
-		}
-		assertNotNull(aTeam);
-		assertTrue(aTeam.hasUser("admin"));
-		assertTrue(aTeam.hasRepositoryPermission("helloworld.git"));
-
-		RepositoryModel helloworld = null;
-		Map<String, RepositoryModel> repositories = RpcUtils.getRepositories(url, account,
-				password.toCharArray());
-		for (RepositoryModel repository : repositories.values()) {
-			if (repository.name.equals("helloworld.git")) {
-				helloworld = repository;
-				break;
-			}
-		}
-		assertNotNull(helloworld);
-		
-		// Confirm that we have added the team
-		List<String> helloworldTeams = RpcUtils.getRepositoryTeams(helloworld, url, account,
-				password.toCharArray());
-		assertEquals(1, helloworldTeams.size());
-		assertTrue(helloworldTeams.contains(aTeam.name));
-
-		// set no teams
-		List<RegistrantAccessPermission> permissions = new ArrayList<RegistrantAccessPermission>();
-		for (String team : helloworldTeams) {
-			permissions.add(new RegistrantAccessPermission(team, AccessPermission.NONE, PermissionType.EXPLICIT, RegistrantType.TEAM, null, true));
-		}
-		assertTrue(RpcUtils.setRepositoryTeamPermissions(helloworld, permissions, url, account,
-				password.toCharArray()));
-		helloworldTeams = RpcUtils.getRepositoryTeams(helloworld, url, account,
-				password.toCharArray());
-		assertEquals(0, helloworldTeams.size());
-		
-		// delete the A-Team
-		assertTrue(RpcUtils.deleteTeam(aTeam, url, account, password.toCharArray()));
-
-		teams = RpcUtils.getTeams(url, account, password.toCharArray());
-		assertEquals(1, teams.size());
-	}
-
-	@Test
-	public void testFederationRegistrations() throws Exception {
-		List<FederationModel> registrations = RpcUtils.getFederationRegistrations(url, account,
-				password.toCharArray());
-		assertTrue("No federation registrations were retrieved!", registrations.size() >= 0);
-	}
-
-	@Test
-	public void testFederationResultRegistrations() throws Exception {
-		List<FederationModel> registrations = RpcUtils.getFederationResultRegistrations(url,
-				account, password.toCharArray());
-		assertTrue("No federation result registrations were retrieved!", registrations.size() >= 0);
-	}
-
-	@Test
-	public void testFederationProposals() throws Exception {
-		List<FederationProposal> proposals = RpcUtils.getFederationProposals(url, account,
-				password.toCharArray());
-		assertTrue("No federation proposals were retrieved!", proposals.size() >= 0);
-	}
-
-	@Test
-	public void testFederationSets() throws Exception {
-		List<FederationSet> sets = RpcUtils.getFederationSets(url, account, password.toCharArray());
-		assertTrue("No federation sets were retrieved!", sets.size() >= 0);
-	}
-
-	@Test
-	public void testSettings() throws Exception {
-		ServerSettings settings = RpcUtils.getSettings(url, account, password.toCharArray());
-		assertNotNull("No settings were retrieved!", settings);
-	}
-
-	@Test
-	public void testServerStatus() throws Exception {
-		ServerStatus status = RpcUtils.getStatus(url, account, password.toCharArray());
-		assertNotNull("No status was retrieved!", status);
-	}
-
-	@Test
-	public void testUpdateSettings() throws Exception {
-		Map<String, String> updated = new HashMap<String, String>();
-
-		// grab current setting
-		ServerSettings settings = RpcUtils.getSettings(url, account, password.toCharArray());
-		boolean showSizes = settings.get(Keys.web.showRepositorySizes).getBoolean(true);
-		showSizes = !showSizes;
-
-		// update setting
-		updated.put(Keys.web.showRepositorySizes, String.valueOf(showSizes));
-		boolean success = RpcUtils.updateSettings(updated, url, account, password.toCharArray());
-		assertTrue("Failed to update server settings", success);
-
-		// confirm setting change
-		settings = RpcUtils.getSettings(url, account, password.toCharArray());
-		boolean newValue = settings.get(Keys.web.showRepositorySizes).getBoolean(false);
-		assertEquals(newValue, showSizes);
-
-		// restore setting
-		newValue = !newValue;
-		updated.put(Keys.web.showRepositorySizes, String.valueOf(newValue));
-		success = RpcUtils.updateSettings(updated, url, account, password.toCharArray());
-		assertTrue("Failed to update server settings", success);
-		settings = RpcUtils.getSettings(url, account, password.toCharArray());
-		showSizes = settings.get(Keys.web.showRepositorySizes).getBoolean(true);
-		assertEquals(newValue, showSizes);
-	}
-
-	@Test
-	public void testBranches() throws Exception {
-		Map<String, Collection<String>> branches = RpcUtils.getBranches(url, account,
-				password.toCharArray());
-		assertNotNull(branches);
-		assertTrue(branches.size() > 0);
-	}
-}
diff --git a/tests/com/gitblit/tests/TimeUtilsTest.java b/tests/com/gitblit/tests/TimeUtilsTest.java
deleted file mode 100644
index f9d5d83..0000000
--- a/tests/com/gitblit/tests/TimeUtilsTest.java
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * Copyright 2011 gitblit.com.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.gitblit.tests;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-import java.util.Date;
-
-import org.junit.Test;
-
-import com.gitblit.utils.TimeUtils;
-
-public class TimeUtilsTest {
-
-	private Date offset(long subtract) {
-		return new Date(System.currentTimeMillis() - subtract);
-	}
-
-	@Test
-	public void testBasicTimeFunctions() throws Exception {
-		assertEquals(2, TimeUtils.minutesAgo(offset(2 * TimeUtils.MIN), false));
-		assertEquals(3, TimeUtils.minutesAgo(offset((2 * TimeUtils.MIN) + (35 * 1000L)), true));
-
-		assertEquals(2, TimeUtils.hoursAgo(offset(2 * TimeUtils.ONEHOUR), false));
-		assertEquals(3, TimeUtils.hoursAgo(offset(5 * TimeUtils.HALFHOUR), true));
-
-		assertEquals(4, TimeUtils.daysAgo(offset(4 * TimeUtils.ONEDAY)));
-	}
-
-	@Test
-	public void testToday() throws Exception {
-		assertTrue(TimeUtils.isToday(new Date()));
-	}
-
-	@Test
-	public void testYesterday() throws Exception {
-		assertTrue(TimeUtils.isYesterday(offset(TimeUtils.ONEDAY)));
-	}
-
-	@Test
-	public void testDurations() throws Exception {
-		TimeUtils timeUtils = new TimeUtils();
-		assertEquals("1 day", timeUtils.duration(1));
-		assertEquals("5 days", timeUtils.duration(5));
-		assertEquals("3 months", timeUtils.duration(75));
-		assertEquals("12 months", timeUtils.duration(364));
-		assertEquals("1 year", timeUtils.duration(365 + 0));
-		assertEquals("1 year", timeUtils.duration(365 + 10));
-		assertEquals("1 year, 1 month", timeUtils.duration(365 + 15));
-		assertEquals("1 year, 1 month", timeUtils.duration(365 + 30));
-		assertEquals("1 year, 1 month", timeUtils.duration(365 + 44));
-		assertEquals("1 year, 2 months", timeUtils.duration(365 + 45));
-		assertEquals("1 year, 2 months", timeUtils.duration(365 + 60));
-
-		assertEquals("2 years", timeUtils.duration(2 * 365 + 0));
-		assertEquals("2 years", timeUtils.duration(2 * 365 + 10));
-		assertEquals("2 years, 1 month", timeUtils.duration(2 * 365 + 15));
-		assertEquals("2 years, 1 month", timeUtils.duration(2 * 365 + 30));
-		assertEquals("2 years, 1 month", timeUtils.duration(2 * 365 + 44));
-		assertEquals("2 years, 2 months", timeUtils.duration(2 * 365 + 45));
-		assertEquals("2 years, 2 months", timeUtils.duration(2 * 365 + 60));
-	}
-
-	@Test
-	public void testTimeAgo() throws Exception {
-		// standard time ago tests
-		TimeUtils timeUtils = new TimeUtils();
-		assertEquals("just now", timeUtils.timeAgo(offset(1 * TimeUtils.MIN)));
-		assertEquals("60 mins ago", timeUtils.timeAgo(offset(60 * TimeUtils.MIN)));
-		assertEquals("2 hours ago", timeUtils.timeAgo(offset(120 * TimeUtils.MIN)));
-		assertEquals("15 hours ago", timeUtils.timeAgo(offset(15 * TimeUtils.ONEHOUR)));
-		assertEquals("yesterday", timeUtils.timeAgo(offset(24 * TimeUtils.ONEHOUR)));
-		assertEquals("2 days ago", timeUtils.timeAgo(offset(2 * TimeUtils.ONEDAY)));
-		assertEquals("5 weeks ago", timeUtils.timeAgo(offset(35 * TimeUtils.ONEDAY)));
-		assertEquals("3 months ago", timeUtils.timeAgo(offset(84 * TimeUtils.ONEDAY)));
-		assertEquals("3 months ago", timeUtils.timeAgo(offset(95 * TimeUtils.ONEDAY)));
-		assertEquals("4 months ago", timeUtils.timeAgo(offset(104 * TimeUtils.ONEDAY)));
-		assertEquals("1 year ago", timeUtils.timeAgo(offset(365 * TimeUtils.ONEDAY)));
-		assertEquals("13 months ago", timeUtils.timeAgo(offset(395 * TimeUtils.ONEDAY)));
-		assertEquals("2 years ago", timeUtils.timeAgo(offset((2 * 365 + 30) * TimeUtils.ONEDAY)));
-
-		// css class tests
-		assertEquals("age0", timeUtils.timeAgoCss(offset(1 * TimeUtils.MIN)));
-		assertEquals("age0", timeUtils.timeAgoCss(offset(60 * TimeUtils.MIN)));
-		assertEquals("age1", timeUtils.timeAgoCss(offset(120 * TimeUtils.MIN)));
-		assertEquals("age1", timeUtils.timeAgoCss(offset(24 * TimeUtils.ONEHOUR)));
-		assertEquals("age2", timeUtils.timeAgoCss(offset(2 * TimeUtils.ONEDAY)));
-	}
-
-	@Test
-	public void testFrequency() {
-		assertEquals(5, TimeUtils.convertFrequencyToMinutes("2 mins"));
-		assertEquals(10, TimeUtils.convertFrequencyToMinutes("10 mins"));
-		assertEquals(600, TimeUtils.convertFrequencyToMinutes("10 hours"));
-		assertEquals(14400, TimeUtils.convertFrequencyToMinutes(" 10 days "));
-	}
-}
diff --git a/tests/de/akquinet/devops/GitblitRunnable.java b/tests/de/akquinet/devops/GitblitRunnable.java
deleted file mode 100644
index fc08f5a..0000000
--- a/tests/de/akquinet/devops/GitblitRunnable.java
+++ /dev/null
@@ -1,134 +0,0 @@
-/*
- * Copyright 2013 akquinet tech@spree GmbH
- *
- * 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 de.akquinet.devops;
-
-import java.net.InetAddress;
-import java.net.ServerSocket;
-
-import com.gitblit.GitBlitServer;
-import com.gitblit.tests.GitBlitSuite;
-
-/**
- * This is a runnable implementation, that is used to run a gitblit server in a
- * separate thread (e.g. alongside test cases)
- * 
- * @author saheba
- * 
- */
-public class GitblitRunnable implements Runnable {
-
-	private int httpPort, httpsPort, shutdownPort;
-	private String userPropertiesPath, gitblitPropertiesPath;
-	private boolean startFailed = false;
-
-	/**
-	 * constructor with reduced set of start params
-	 * 
-	 * @param httpPort
-	 * @param httpsPort
-	 * @param shutdownPort
-	 * @param gitblitPropertiesPath
-	 * @param userPropertiesPath
-	 */
-	public GitblitRunnable(int httpPort, int httpsPort, int shutdownPort,
-			String gitblitPropertiesPath, String userPropertiesPath) {
-		this.httpPort = httpPort;
-		this.httpsPort = httpsPort;
-		this.shutdownPort = shutdownPort;
-		this.userPropertiesPath = userPropertiesPath;
-		this.gitblitPropertiesPath = gitblitPropertiesPath;
-	}
-
-	/*
-	 * (non-Javadoc)
-	 * 
-	 * @see java.lang.Runnable#run()
-	 */
-	public void run() {
-		boolean portsFree = false;
-		long lastRun = -1;
-		while (!portsFree) {
-			long current = System.currentTimeMillis();
-			if (lastRun == -1 || lastRun + 100 < current) {
-				portsFree = areAllPortsFree(new int[] { httpPort, httpsPort,
-						shutdownPort }, "127.0.0.1");
-			}
-			lastRun = current;
-
-		}
-		try {
-			GitBlitServer.main("--httpPort", "" + httpPort, "--httpsPort", ""
-					+ httpsPort, "--shutdownPort", "" + shutdownPort,
-					"--repositoriesFolder",
-					"\"" + GitBlitSuite.REPOSITORIES.getAbsolutePath() + "\"",
-					"--userService", userPropertiesPath, "--settings",
-					gitblitPropertiesPath);
-			setStartFailed(false);
-		} catch (Exception iex) {
-			System.out.println("Gitblit server start failed");
-			setStartFailed(true);
-		}
-	}
-
-	/**
-	 * Method used to ensure that all ports are free, if the runnable is used
-	 * JUnit test classes. Be aware that JUnit's setUpClass and tearDownClass
-	 * methods, which are executed before and after a test class (consisting of
-	 * several test cases), may be executed parallely if they are part of a test
-	 * suite consisting of several test classes. Therefore the run method of
-	 * this class calls areAllPortsFree to check port availability before
-	 * starting another gitblit instance.
-	 * 
-	 * @param ports
-	 * @param inetAddress
-	 * @return
-	 */
-	public static boolean areAllPortsFree(int[] ports, String inetAddress) {
-		System.out
-				.println("\n"
-						+ System.currentTimeMillis()
-						+ " ----------------------------------- testing if all ports are free ...");
-		String blockedPorts = "";
-		for (int i = 0; i < ports.length; i++) {
-			ServerSocket s;
-			try {
-				s = new ServerSocket(ports[i], 1,
-						InetAddress.getByName(inetAddress));
-				s.close();
-			} catch (Exception e) {
-				if (!blockedPorts.equals("")) {
-					blockedPorts += ", ";
-				}
-			}
-		}
-		if (blockedPorts.equals("")) {
-			System.out
-					.println(" ----------------------------------- ... verified");
-			return true;
-		}
-		System.out.println(" ----------------------------------- ... "
-				+ blockedPorts + " are still blocked");
-		return false;
-	}
-
-	private void setStartFailed(boolean startFailed) {
-		this.startFailed = startFailed;
-	}
-
-	public boolean isStartFailed() {
-		return startFailed;
-	}
-}
diff --git a/tests/de/akquinet/devops/LaunchWithUITestConfig.java b/tests/de/akquinet/devops/LaunchWithUITestConfig.java
deleted file mode 100644
index 594d7fc..0000000
--- a/tests/de/akquinet/devops/LaunchWithUITestConfig.java
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * Copyright 2013 akquinet tech@spree GmbH
- *
- * 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 de.akquinet.devops;
-
-import java.io.IOException;
-import java.io.OutputStream;
-import java.net.InetAddress;
-import java.net.Socket;
-import java.net.UnknownHostException;
-
-import junit.framework.Assert;
-
-import org.junit.Test;
-
-import com.gitblit.Constants;
-import com.gitblit.GitBlitServer;
-import com.gitblit.tests.GitBlitSuite;
-
-/**
- * This test checks if it is possible to run two server instances in the same
- * JVM sequentially
- * 
- * @author saheba
- * 
- */
-public class LaunchWithUITestConfig {
-
-	@Test
-	public void testSequentialLaunchOfSeveralInstances()
-			throws InterruptedException {
-		// different ports than in testParallelLaunchOfSeveralInstances to
-		// ensure that both test cases do not affect each others test results
-		int httpPort = 9191, httpsPort = 9292, shutdownPort = 9393;
-		String gitblitPropertiesPath = "test-ui-gitblit.properties", usersPropertiesPath = "test-ui-users.conf";
-
-		GitblitRunnable gitblitRunnable = new GitblitRunnable(httpPort,
-				httpsPort, shutdownPort, gitblitPropertiesPath,
-				usersPropertiesPath);
-		Thread serverThread = new Thread(gitblitRunnable);
-		serverThread.start();
-		Thread.sleep(2000);
-		Assert.assertFalse(gitblitRunnable.isStartFailed());
-		LaunchWithUITestConfig.shutdownGitBlitServer(shutdownPort);
-
-		Thread.sleep(5000);
-
-		GitblitRunnable gitblitRunnable2 = new GitblitRunnable(httpPort,
-				httpsPort, shutdownPort, gitblitPropertiesPath,
-				usersPropertiesPath);
-		Thread serverThread2 = new Thread(gitblitRunnable2);
-		serverThread2.start();
-		Thread.sleep(2000);
-		Assert.assertFalse(gitblitRunnable2.isStartFailed());
-		LaunchWithUITestConfig.shutdownGitBlitServer(shutdownPort);
-	}
-
-	@Test
-	public void testParallelLaunchOfSeveralInstances()
-			throws InterruptedException {
-		// different ports than in testSequentialLaunchOfSeveralInstances to
-		// ensure that both test cases do not affect each others test results
-		int httpPort = 9797, httpsPort = 9898, shutdownPort = 9999;
-		int httpPort2 = 9494, httpsPort2 = 9595, shutdownPort2 = 9696;
-		String gitblitPropertiesPath = "test-ui-gitblit.properties", usersPropertiesPath = "test-ui-users.conf";
-
-		GitblitRunnable gitblitRunnable = new GitblitRunnable(httpPort,
-				httpsPort, shutdownPort, gitblitPropertiesPath,
-				usersPropertiesPath);
-		Thread serverThread = new Thread(gitblitRunnable);
-		serverThread.start();
-		Thread.sleep(2000);
-		Assert.assertFalse(gitblitRunnable.isStartFailed());
-
-		GitblitRunnable gitblitRunnable2 = new GitblitRunnable(httpPort2,
-				httpsPort2, shutdownPort2, gitblitPropertiesPath,
-				usersPropertiesPath);
-		Thread serverThread2 = new Thread(gitblitRunnable2);
-		serverThread2.start();
-		Thread.sleep(2000);
-		Assert.assertFalse(gitblitRunnable2.isStartFailed());
-
-		LaunchWithUITestConfig.shutdownGitBlitServer(shutdownPort);
-		LaunchWithUITestConfig.shutdownGitBlitServer(shutdownPort2);
-	}
-
-	/**
-	 * main runs the tests without assert checks. You have to check the console
-	 * output manually.
-	 * 
-	 * @param args
-	 * @throws InterruptedException
-	 */
-	public static void main(String[] args) throws InterruptedException {
-		new LaunchWithUITestConfig().testSequentialLaunchOfSeveralInstances();
-		new LaunchWithUITestConfig().testParallelLaunchOfSeveralInstances();
-	}
-
-	private static void shutdownGitBlitServer(int shutdownPort) {
-		try {
-			Socket s = new Socket(InetAddress.getByName("127.0.0.1"),
-					shutdownPort);
-			OutputStream out = s.getOutputStream();
-			System.out.println("Sending Shutdown Request to " + Constants.NAME);
-			out.write("\r\n".getBytes());
-			out.flush();
-			s.close();
-		} catch (UnknownHostException e) {
-			e.printStackTrace();
-		} catch (IOException e) {
-			e.printStackTrace();
-		}
-	}
-}
diff --git a/tests/de/akquinet/devops/ManualUITestLaunch.java b/tests/de/akquinet/devops/ManualUITestLaunch.java
deleted file mode 100644
index 2eff491..0000000
--- a/tests/de/akquinet/devops/ManualUITestLaunch.java
+++ /dev/null
@@ -1,14 +0,0 @@
-package de.akquinet.devops;
-
-public class ManualUITestLaunch {
-public static void main(String[] args) {
-	int httpPort = 8080, httpsPort = 8443, shutdownPort = 8081;
-	String gitblitPropertiesPath = "test-ui-gitblit.properties", usersPropertiesPath = "test-ui-users.conf";
-
-	GitblitRunnable gitblitRunnable = new GitblitRunnable(httpPort,
-			httpsPort, shutdownPort, gitblitPropertiesPath,
-			usersPropertiesPath);
-	Thread serverThread = new Thread(gitblitRunnable);
-	serverThread.start();
-}
-}
diff --git a/tests/de/akquinet/devops/test/ui/TestUISuite.java b/tests/de/akquinet/devops/test/ui/TestUISuite.java
deleted file mode 100644
index 97bd903..0000000
--- a/tests/de/akquinet/devops/test/ui/TestUISuite.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright 2013 akquinet tech@spree GmbH
- *
- * 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 de.akquinet.devops.test.ui;
-
-import org.junit.runner.RunWith;
-import org.junit.runners.Suite;
-
-import de.akquinet.devops.test.ui.cases.UI_MultiAdminSupportTest;
-
-/**
- * the test suite including all selenium-based ui-tests.
- * 
- * @author saheba
- *
- */
-@RunWith(Suite.class)
-@Suite.SuiteClasses({ UI_MultiAdminSupportTest.class })
-public class TestUISuite {
-
-}
diff --git a/tests/de/akquinet/devops/test/ui/cases/UI_MultiAdminSupportTest.java b/tests/de/akquinet/devops/test/ui/cases/UI_MultiAdminSupportTest.java
deleted file mode 100644
index 9cdad16..0000000
--- a/tests/de/akquinet/devops/test/ui/cases/UI_MultiAdminSupportTest.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright 2013 akquinet tech@spree GmbH
- *
- * 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 de.akquinet.devops.test.ui.cases;
-
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
-
-import de.akquinet.devops.test.ui.generic.AbstractUITest;
-import de.akquinet.devops.test.ui.view.RepoEditView;
-import de.akquinet.devops.test.ui.view.RepoListView;
-
-/**
- * tests the multi admin per repo feature.
- * 
- * @author saheba
- * 
- */
-public class UI_MultiAdminSupportTest extends AbstractUITest {
-
-	String baseUrl = "https://localhost:8443";
-	RepoListView view;
-	RepoEditView editView;
-	private static final String TEST_MULTI_ADMIN_SUPPORT_REPO_NAME = "testmultiadminsupport";
-	private static final String TEST_MULTI_ADMIN_SUPPORT_REPO_PATH = "~repocreator/"
-			+ TEST_MULTI_ADMIN_SUPPORT_REPO_NAME + ".git";
-	private static final String TEST_MULTI_ADMIN_SUPPORT_REPO_PATH_WITHOUT_SUFFIX = "~repocreator/"
-			+ TEST_MULTI_ADMIN_SUPPORT_REPO_NAME;
-
-	@Before
-	public void before() {
-		System.out.println("IN BEFORE");
-		this.view = new RepoListView(AbstractUITest.getDriver(), baseUrl);
-		this.editView = new RepoEditView(AbstractUITest.getDriver());
-		AbstractUITest.getDriver().navigate().to(baseUrl);
-	}
-
-	@Test
-	public void test_MultiAdminSelectionInStandardRepo() {
-		// login
-		view.login("repocreator", "repocreator");
-
-		// create new repo
-		view.navigateToNewRepo(1);
-		editView.changeName(TEST_MULTI_ADMIN_SUPPORT_REPO_PATH);
-		Assert.assertTrue(editView.navigateToPermissionsTab());
-
-		Assert.assertTrue(editView
-				.changeAccessRestriction(RepoEditView.RESTRICTION_AUTHENTICATED_VCP));
-		Assert.assertTrue(editView
-				.changeAuthorizationControl(RepoEditView.AUTHCONTROL_RWALL));
-
-		// with a second admin
-		editView.addRepoAdministrator("admin");
-		Assert.assertTrue(editView.save());
-		// user is automatically forwarded to repo list view
-		Assert.assertTrue(view.isEmptyRepo(TEST_MULTI_ADMIN_SUPPORT_REPO_PATH));
-		Assert.assertTrue(view
-				.isEditableRepo(TEST_MULTI_ADMIN_SUPPORT_REPO_PATH));
-		Assert.assertTrue(view
-				.isDeletableRepo(TEST_MULTI_ADMIN_SUPPORT_REPO_PATH_WITHOUT_SUFFIX));
-		// logout repocreator
-		view.logout();
-
-		// check with admin account if second admin has the same rights
-		view.login("admin", "admin");
-		Assert.assertTrue(view.isEmptyRepo(TEST_MULTI_ADMIN_SUPPORT_REPO_PATH));
-		Assert.assertTrue(view
-				.isEditableRepo(TEST_MULTI_ADMIN_SUPPORT_REPO_PATH));
-		Assert.assertTrue(view
-				.isDeletableRepo(TEST_MULTI_ADMIN_SUPPORT_REPO_PATH_WITHOUT_SUFFIX));
-		// delete repo to reach state as before test execution
-		view.navigateToDeleteRepo(TEST_MULTI_ADMIN_SUPPORT_REPO_PATH_WITHOUT_SUFFIX);
-		view.acceptAlertDialog();
-		view.logout();
-
-		Assert.assertTrue(view.isLoginPartVisible());
-	}
-
-}
diff --git a/tests/de/akquinet/devops/test/ui/view/RepoEditView.java b/tests/de/akquinet/devops/test/ui/view/RepoEditView.java
deleted file mode 100644
index ef0a317..0000000
--- a/tests/de/akquinet/devops/test/ui/view/RepoEditView.java
+++ /dev/null
@@ -1,158 +0,0 @@
-/*
- * Copyright 2013 akquinet tech@spree GmbH
- *
- * 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 de.akquinet.devops.test.ui.view;
-
-import java.util.List;
-
-import org.openqa.selenium.By;
-import org.openqa.selenium.WebDriver;
-import org.openqa.selenium.WebElement;
-import org.openqa.selenium.support.ui.WebDriverWait;
-
-/**
- * class representing the tabs you can access when you edit a repo.
- * 
- * @author saheba
- * 
- */
-public class RepoEditView extends GitblitDashboardView {
-
-	public static final String PERMISSION_VIEW_USERS_NAME_PREFIX = "users:";
-	public static final String PERMISSION_VIEW_TEAMS_NAME_PREFIX = "teams:";
-
-	public static final String PERMISSION_VIEW_MUTABLE = "permissionToggleForm:showMutable";
-	public static final String PERMISSION_VIEW_SPECIFIED = "permissionToggleForm:showSpecified";
-	public static final String PERMISSION_VIEW_EFFECTIVE = "permissionToggleForm:showEffective";
-
-	public static final int RESTRICTION_ANONYMOUS_VCP = 0;
-	public static final int RESTRICTION_AUTHENTICATED_P = 1;
-	public static final int RESTRICTION_AUTHENTICATED_CP = 2;
-	public static final int RESTRICTION_AUTHENTICATED_VCP = 3;
-
-	public static final int AUTHCONTROL_RWALL = 0;
-	public static final int AUTHOCONTROL_FINE = 1;
-
-	public RepoEditView(WebDriver driver) {
-		super(driver, null);
-	}
-
-	public void changeName(String newName) {
-		String pathName = "//input[@id = \"name\" ]";
-		WebElement field = getDriver().findElement(By.xpath(pathName));
-		field.clear();
-		field.sendKeys(newName);
-	}
-
-	public boolean navigateToPermissionsTab() {
-		String linkText = "access permissions";
-		List<WebElement> found = getDriver().findElements(
-				By.partialLinkText(linkText));
-		System.out.println("PERM TABS found =" + found.size());
-		if (found != null && found.size() == 1) {
-			found.get(0).click();
-			return true;
-		}
-		return false;
-	}
-
-	private void changeRepoAdministrators(String action,
-			String affectedSelection, String username) {
-		String xpath = "//select[@name=\"" + affectedSelection
-				+ "\"]/option[@value = \"" + username + "\" ]";
-		WebElement option = getDriver().findElement(By.xpath(xpath));
-		option.click();
-		String buttonPath = "//button[@class=\"button " + action + "\"]";
-		WebElement button = getDriver().findElement(By.xpath(buttonPath));
-		button.click();
-	}
-
-	public void removeRepoAdministrator(String username) {
-		changeRepoAdministrators("remove", "repoAdministrators:selection",
-				username);
-	}
-
-	public void addRepoAdministrator(String username) {
-		changeRepoAdministrators("add", "repoAdministrators:choices", username);
-	}
-
-	public WebElement getAccessRestrictionSelection() {
-		String xpath = "//select[@name =\"accessRestriction\"]";
-		List<WebElement> found = getDriver().findElements(By.xpath(xpath));
-		if (found != null && found.size() == 1) {
-			return found.get(0);
-		}
-		return null;
-	}
-
-	public boolean changeAccessRestriction(int option) {
-		WebElement accessRestrictionSelection = getAccessRestrictionSelection();
-		if (accessRestrictionSelection == null) {
-			return false;
-		}
-		accessRestrictionSelection.click();
-		sleep(100);
-		String xpath = "//select[@name =\"accessRestriction\"]/option[@value=\""
-				+ option + "\"]";
-		List<WebElement> found = getDriver().findElements(By.xpath(xpath));
-		if (found == null || found.size() == 0 || found.size() > 1) {
-			return false;
-		}
-		found.get(0).click();
-		return true;
-	}
-
-	public boolean changeAuthorizationControl(int option) {
-		System.out.println("try to change auth control");
-		String xpath = "//input[@name =\"authorizationControl\" and @value=\""
-				+ option + "\"]";
-		List<WebElement> found = getDriver().findElements(By.xpath(xpath));
-		System.out.println("found auth CONTROL options " + found.size());
-		if (found == null || found.size() == 0 || found.size() > 1) {
-			return false;
-		}
-		found.get(0).click();
-		return true;
-	}
-
-	private boolean isPermissionViewDisabled(String prefix, String view) {
-		String xpath = "//[@name =\"" + prefix + view + "\"]";
-		List<WebElement> found = getDriver().findElements(By.xpath(xpath));
-		if (found == null || found.size() == 0 || found.size() > 1) {
-			return false;
-		}
-		String attrValue = found.get(0).getAttribute("disabled");
-		return (attrValue != null) && (attrValue.equals("disabled"));
-	}
-
-	public boolean isPermissionViewSectionDisabled(String prefix) {
-		return isPermissionViewDisabled(prefix, PERMISSION_VIEW_MUTABLE)
-				&& isPermissionViewDisabled(prefix, PERMISSION_VIEW_SPECIFIED)
-				&& isPermissionViewDisabled(prefix, PERMISSION_VIEW_EFFECTIVE);
-	}
-
-	public boolean save() {
-		String xpath = "//div[@class=\"form-actions\"]/input[@name =\""
-				+ "save" + "\"]";
-		List<WebElement> found = getDriver().findElements(By.xpath(xpath));
-		if (found == null || found.size() == 0 || found.size() > 1) {
-			return false;
-		}
-		found.get(0).click();
-		WebDriverWait webDriverWait = new WebDriverWait(getDriver(), 1);
-		webDriverWait.until(new Exp.RepoListViewLoaded());
-		return true;
-	}
-}
diff --git a/tmplt.pom.xml b/tmplt.pom.xml
deleted file mode 100644
index 4abe4be..0000000
--- a/tmplt.pom.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-		xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
-		<modelVersion>4.0.0</modelVersion>
-		<groupId>com.gitblit</groupId>
-		<artifactId>gitblit</artifactId>
-		<version>@gb.version@</version>
-</project>
diff --git a/tools/GenJar.jar b/tools/GenJar.jar
deleted file mode 100644
index 6f225cb..0000000
--- a/tools/GenJar.jar
+++ /dev/null
Binary files differ
diff --git a/tools/ant-googlecode-0.0.3.jar b/tools/ant-googlecode-0.0.3.jar
deleted file mode 100644
index 452fa84..0000000
--- a/tools/ant-googlecode-0.0.3.jar
+++ /dev/null
Binary files differ

--
Gitblit v1.9.1