James Moger
2014-06-03 42972d830611fa4b1aa2c2c49c824a15e1987597
Merged #76 "Simplify repository creation with a new page"
16 files added
22 files modified
1915 ■■■■ changed files
.gitmodules 3 ●●●●● patch | view | raw | blame | history
README.markdown 13 ●●●● patch | view | raw | blame | history
build.xml 6 ●●●●● patch | view | raw | blame | history
releases.moxie 3 ●●●●● patch | view | raw | blame | history
src/main/distrib/data/gitblit.properties 5 ●●●●● patch | view | raw | blame | history
src/main/distrib/data/gitignore @ 097db8 1 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/Constants.java 8 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/servlet/GitblitContext.java 16 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/GitBlitWebApp.java 10 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/GitBlitWebApp.properties 45 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/EditRepositoryPage.html 219 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/EditRepositoryPage.java 404 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/EmptyRepositoryPage.html 2 ●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/EmptyRepositoryPage_es.html 2 ●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/EmptyRepositoryPage_ko.html 2 ●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/EmptyRepositoryPage_nl.html 2 ●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/EmptyRepositoryPage_pl.html 2 ●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/EmptyRepositoryPage_pt_BR.html 2 ●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/EmptyRepositoryPage_zh_CN.html 2 ●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/NewRepositoryPage.html 41 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/NewRepositoryPage.java 373 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/RootPage.java 2 ●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/UserPage.java 4 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/panels/AccessPolicyPanel.html 28 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/panels/AccessPolicyPanel.java 192 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/panels/CheckboxOption.html 17 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/panels/CheckboxOption.java 54 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/panels/ChoiceOption.html 19 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/panels/ChoiceOption.java 52 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/panels/ConditionalChoiceOption.html 19 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/panels/ConditionalChoiceOption.java 78 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/panels/FilterableRepositoryList.java 3 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/panels/RegistrantPermissionsPanel.html 2 ●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/panels/RepositoriesPanel.java 5 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/panels/RepositoryNamePanel.html 30 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/panels/RepositoryNamePanel.java 176 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/panels/TextOption.html 20 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/panels/TextOption.java 53 ●●●●● patch | view | raw | blame | history
.gitmodules
New file
@@ -0,0 +1,3 @@
[submodule "src/main/distrib/data/gitignore"]
    path = src/main/distrib/data/gitignore
    url = https://github.com/github/gitignore.git
README.markdown
@@ -4,13 +4,18 @@
Gitblit is an open source, pure Java Git solution for managing, viewing, and serving [Git](http://git-scm.com) repositories.
More information about Gitblit can be found [here](http://gitblit.com).
[ ![Download](https://api.bintray.com/packages/gitblit/releases/stable/images/download.png) ](https://bintray.com/gitblit/releases/stable/_latestVersion)
<a href='https://bintray.com/gitblit/releases/gitblit/_latestVersion'><img src='https://api.bintray.com/packages/gitblit/releases/gitblit/images/download.png'></a>
License
-------
Gitblit is distributed under the terms of the [Apache Software Foundation license, version 2.0](http://www.apache.org/licenses/LICENSE-2.0).
The text of the license is included in the file LICENSE in the root of the project.
Contributing
------------
GitHub pull requests are preferred.  Any contributions must be distributed under the terms of the [Apache Software Foundation license, version 2.0](http://www.apache.org/licenses/LICENSE-2.0).
Java Runtime Requirement
------------------------------------
@@ -25,6 +30,10 @@
Building Gitblit
----------------
Gitblit uses submodules.
Make sure to clone using `--recursive` OR to execute `git submodule update --init --recursive`.
[Eclipse](http://eclipse.org) is recommended for development as the project settings are preconfigured.
1. Import the gitblit project into your Eclipse workspace.
@@ -39,4 +48,4 @@
Building Tips & Tricks
----------------------
1. If you are running Ant from an ANSI-capable console, consider setting the `MX_COLOR` environment 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.
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.
build.xml
@@ -919,6 +919,12 @@
                    <include name="subgit.groovy" />
                </fileset>
            </copy>
            <mkdir dir="@{toDir}/gitignore" />
            <copy todir="@{toDir}/gitignore">
                <fileset dir="${project.distrib.dir}/data/gitignore">
                    <include name="*.gitignore" />
                </fileset>
            </copy>
      </sequential>
    </macrodef>
    
releases.moxie
@@ -27,6 +27,7 @@
    - Move repository deletion functions to the edit repository page AND allow deletion to be disabled (pr-180, ticket-67)
    - Update the Korean translation (pr-184, ticket-69)
    - Overhaul the EmptyRepositoryPage (ticket-73)
    - Overhauled the edit repository page (ticket-76)
    - Process bugtraq links in the ticket description and comments (ticket-78)
    additions:
    - Add My Tickets page (issue-215, ticket-15)
@@ -36,6 +37,7 @@
    - Add FORK_REPOSITORY RPC request type (issue-371, pr-161, ticket-65)
    - Add object type (ot) parameter for RSS queries to retrieve tag details (pr-165, ticket-66)
    - Add setting to allow STARTTLS without requiring SMTPS (pr-183)
    - Simplified repository creation, offer simple README generation, and insertion of a pre-defined .gitignore file (ticket-76)
    - Added an extension point for monitoring onStartup and onShutdown (ticket-79)
    - Tag server-side merges when incremental push tags are enabled (issue-432, ticket-85)
    - Add setting to control default thread pool size for miscellaneous background tasks (ticket-92)
@@ -55,6 +57,7 @@
    - { name: 'web.allowDeletingNonEmptyRepositories', defaultValue: 'true' }
    - { name: 'mail.starttls', defaultValue: 'false' }
    - { name: 'execution.defaultThreadPoolSize', defaultValue: '1' }
    - { name: 'git.gitignoreFolder', defaultValue: '${baseFolder}/gitignore' }
}
#
src/main/distrib/data/gitblit.properties
@@ -271,6 +271,11 @@
# SINCE 1.4.0
git.createRepositoriesShared = false
# Directory for gitignore templates used during repository creation.
#
# SINCE 1.6.0
git.gitignoreFolder = ${baseFolder}/gitignore
# Enable JGit-based garbage collection. (!!EXPERIMENTAL!!)
#
# USE AT YOUR OWN RISK!
src/main/distrib/data/gitignore
New file
@@ -0,0 +1 @@
Subproject commit 097db81c08b138dea7cb031eb18eeb16afe44bdf
src/main/java/com/gitblit/Constants.java
@@ -122,6 +122,14 @@
    public static final String R_TICKETS_PATCHSETS = "refs/tickets/";
    public static final String R_MASTER = "refs/heads/master";
    public static final String MASTER = "master";
    public static final String R_DEVELOP = "refs/heads/develop";
    public static final String DEVELOP = "develop";
    public static String getVersion() {
        String v = Constants.class.getPackage().getImplementationVersion();
        if (v == null) {
src/main/java/com/gitblit/servlet/GitblitContext.java
@@ -372,6 +372,22 @@
            }
        }
        // Copy the included gitignore files to the configured gitignore folder
        String gitignorePath = webxmlSettings.getString(Keys.git.gitignoreFolder, "gitignore");
        File localGitignores = com.gitblit.utils.FileUtils.resolveParameter(Constants.baseFolder$, base, gitignorePath);
        if (!localGitignores.exists()) {
            File warGitignores = new File(contextFolder, "/WEB-INF/data/gitignore");
            if (!warGitignores.equals(localGitignores)) {
                try {
                    com.gitblit.utils.FileUtils.copy(localGitignores, warGitignores.listFiles());
                } catch (IOException e) {
                    logger.error(MessageFormat.format(
                            "Failed to copy included .gitignore files from {0} to {1}",
                            warGitignores, localGitignores));
                }
            }
        }
        // merge the WebXmlSettings into the runtime settings (for backwards-compatibilty)
        runtimeSettings.merge(webxmlSettings);
src/main/java/com/gitblit/wicket/GitBlitWebApp.java
@@ -57,6 +57,7 @@
import com.gitblit.wicket.pages.DocPage;
import com.gitblit.wicket.pages.DocsPage;
import com.gitblit.wicket.pages.EditMilestonePage;
import com.gitblit.wicket.pages.EditRepositoryPage;
import com.gitblit.wicket.pages.EditTicketPage;
import com.gitblit.wicket.pages.ExportTicketPage;
import com.gitblit.wicket.pages.FederationRegistrationPage;
@@ -71,6 +72,7 @@
import com.gitblit.wicket.pages.MyDashboardPage;
import com.gitblit.wicket.pages.MyTicketsPage;
import com.gitblit.wicket.pages.NewMilestonePage;
import com.gitblit.wicket.pages.NewRepositoryPage;
import com.gitblit.wicket.pages.NewTicketPage;
import com.gitblit.wicket.pages.OverviewPage;
import com.gitblit.wicket.pages.PatchPage;
@@ -91,6 +93,8 @@
public class GitBlitWebApp extends WebApplication implements GitblitWicketApp {
    private final Class<? extends WebPage> homePageClass = MyDashboardPage.class;
    private final Class<? extends WebPage> newRepositoryPageClass = NewRepositoryPage.class;
    private final Map<String, CacheControl> cacheablePages = new HashMap<String, CacheControl>();
@@ -207,6 +211,8 @@
        mount("/proposal", ReviewProposalPage.class, "t");
        mount("/registration", FederationRegistrationPage.class, "u", "n");
        mount("/new", NewRepositoryPage.class);
        mount("/edit", EditRepositoryPage.class, "r");
        mount("/activity", ActivityPage.class, "r", "h");
        mount("/lucene", LuceneSearchPage.class);
        mount("/project", ProjectPage.class, "p");
@@ -262,6 +268,10 @@
        return homePageClass;
    }
    public Class<? extends WebPage> getNewRepositoryPage() {
        return newRepositoryPageClass;
    }
    /* (non-Javadoc)
     * @see com.gitblit.wicket.Webapp#isCacheablePage(java.lang.String)
     */
src/main/java/com/gitblit/wicket/GitBlitWebApp.properties
@@ -133,7 +133,7 @@
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.headRefDescription = The default branch that will be cloned and displayed on the Summary page.
gb.federationStrategy = federation strategy
gb.federationRegistration = federation registration
gb.federationResults = federation pull results
@@ -223,8 +223,8 @@
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.indexedBranches = Indexed Branches
gb.indexedBranchesDescription = Select the branches to be indexed by Lucene
gb.noIndexedRepositoriesWarning = none of your repositories are configured for Lucene indexing
gb.undefinedQueryWarning = query is undefined!
gb.noSelectedRepositoriesWarning = please select one or more repositories!
@@ -685,4 +685,41 @@
gb.administration = administration
gb.plugins = plugins
gb.extensions = extensions
gb.pleaseSelectProject = Please select the project!
gb.accessPolicy = Access Policy
gb.accessPolicyDescription = Choose an access policy to control repository visibility and git permissions.
gb.anonymousPolicy = Anonymous View, Clone, & Push
gb.anonymousPolicyDescription = Anyone can see, clone, and push to this repository.
gb.authenticatedPushPolicy = Restrict Push (Authenticated)
gb.authenticatedPushPolicyDescription = Anyone can see and clone this repository. All authenticated users have RW+ push permission.
gb.namedPushPolicy = Restrict Push (Named)
gb.namedPushPolicyDescription = Anyone can see and clone this repository. You choose who can push.
gb.clonePolicy = Restrict Clone & Push
gb.clonePolicyDescription = Anyone can see this repository. You choose who can clone and push.
gb.viewPolicy  = Restrict View, Clone, & Push
gb.viewPolicyDescription = You choose who can see, clone, and push to this repository.
gb.initialCommit = Initial Commit
gb.initialCommitDescription = This will allow you to <code>git clone</code> this repository immediately. Skip this step if you have already run <code>git init</code> locally.
gb.initWithReadme = Include a README
gb.initWithReadmeDescription = This will generate a simple README document for your repository.
gb.initWithGitignore = Include a .gitignore file
gb.initWithGitignoreDescription = This will insert a config file that instructs your Git clients to ignore files or directories that match defined patterns.
gb.pleaseSelectGitIgnore = Please select a .gitignore file
gb.receive = receive
gb.permissions = permissions
gb.ownersDescription = Owners can manage all repository settings but they are not allowed to rename a repository unless it is their personal repository.
gb.userPermissionsDescription = You can specify individual user permissions. These settings will override team or regex permissions.
gb.teamPermissionsDescription = You can specify individual team permissions. These settings will override regex permissions.
gb.ticketSettings = Ticket Settings
gb.receiveSettings = Receive Settings
gb.receiveSettingsDescription = The receive settings control pushes to the repository.
gb.preReceiveDescription = Pre-receive hooks are executed after commits are received but <em>BEFORE</em> the refs are updated.<p>This is the appropriate hook for rejecting a push.</p>
gb.postReceiveDescription = Post-receive hooks are executed after commits are received but <em>AFTER</em> the refs are updated.<p>This is the appropriate hook for notifications, build triggers, etc.</p>
gb.federationStrategyDescription = Control if and how to federate this repository with another Gitblit.
gb.federationSetsDescription = This repository will be included in the selected federation sets.
gb.miscellaneous = miscellaneous
gb.originDescription = The url from which this repository was cloned.
gb.gc = GC
gb.garbageCollection = Garbage Collection
gb.garbageCollectionDescription = The garbage collector will pack loose objects pushed from clients and will remove unreferenced objects from the repository.
gb.commitMessageRendererDescription = Commit messages can be displayed as plaintext or as rendered markup.
src/main/java/com/gitblit/wicket/pages/EditRepositoryPage.html
@@ -9,14 +9,17 @@
    <form style="padding-top:5px;" wicket:id="editForm">
<div class="row">
<div class="span12">
<div class="tabbable">
<div class="tabbable tabs-left">
    <!-- 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="#permissions" data-toggle="tab"><wicket:message key="gb.permissions"></wicket:message></a></li>
        <li><a href="#receive" data-toggle="tab"><wicket:message key="gb.receive"></wicket:message></a></li>
        <li><a href="#tickets" data-toggle="tab"><wicket:message key="gb.tickets"></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>
        <li><a href="#gc" data-toggle="tab"><wicket:message key="gb.gc"></wicket:message></a></li>
        <li><a href="#misc" data-toggle="tab"><wicket:message key="gb.miscellaneous"></wicket:message></a></li>
    </ul>
    <!-- tab content -->
@@ -24,99 +27,167 @@
        <!-- 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.acceptNewTickets"></wicket:message></th><td class="edit"><label class="checkbox"><input type="checkbox" wicket:id="acceptNewTickets" tabindex="7" /> &nbsp;<span class="help-inline"><wicket:message key="gb.acceptNewTicketsDescription"></wicket:message></span></label></td></tr>
                <tr><th><wicket:message key="gb.acceptNewPatchsets"></wicket:message></th><td class="edit"><label class="checkbox"><input type="checkbox" wicket:id="acceptNewPatchsets" tabindex="8" /> &nbsp;<span class="help-inline"><wicket:message key="gb.acceptNewPatchsetsDescription"></wicket:message></span></label></td></tr>
                <tr><th><wicket:message key="gb.requireApproval"></wicket:message></th><td class="edit"><label class="checkbox"><input type="checkbox" wicket:id="requireApproval" tabindex="9" /> &nbsp;<span class="help-inline"><wicket:message key="gb.requireApprovalDescription"></wicket:message></span></label></td></tr>
                <tr><th><wicket:message key="gb.mergeTo"></wicket:message></th><td class="edit"><select class="span2" wicket:id="mergeTo" tabindex="10" /> &nbsp;<span class="help-inline"><wicket:message key="gb.mergeToDescription"></wicket:message></span></td></tr>
                <tr><th colspan="2"><hr/></th></tr>
                <tr><th><wicket:message key="gb.enableIncrementalPushTags"></wicket:message></th><td class="edit"><label class="checkbox"><input type="checkbox" wicket:id="useIncrementalPushTags" tabindex="11" /> &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="12" /> &nbsp;<span class="help-inline"><wicket:message key="gb.showRemoteBranchesDescription"></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="13" /> &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="14" /> &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="15" /> &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="16" /></td></tr>
                <tr><th><wicket:message key="gb.commitMessageRenderer"></wicket:message></th><td class="edit"><select class="span2" wicket:id="commitMessageRenderer" tabindex="17" /></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="18" /></td></tr>
            </tbody>
        </table>
            <div wicket:id="namePanel"></div>
            <hr/>
            <div wicket:id="accessPolicyPanel"></div>
        </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="19" /> </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="20" /></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="21" /> &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="22" /> &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="23" /> &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>
            <h4><wicket:message key="gb.owners"></wicket:message></h4>
            <p><wicket:message key="gb.ownersDescription"></wicket:message></p>
            <div wicket:id="owners"></div>
            <hr />
            <h4><wicket:message key="gb.userPermissions"></wicket:message></h4>
            <p><wicket:message key="gb.userPermissionsDescription"></wicket:message></p>
            <div wicket:id="users"></div>
            <hr />
            <h4><wicket:message key="gb.teamPermissions"></wicket:message></h4>
            <p><wicket:message key="gb.teamPermissionsDescription"></wicket:message></p>
            <div wicket:id="teams"></div>
        </div>
        <!-- federation -->
        <div class="tab-pane" id="federation">
        <!-- receive -->
        <div class="tab-pane" id="receive">
            <h4><wicket:message key="gb.receiveSettings"></wicket:message></h4>
            <p><wicket:message key="gb.receiveSettingsDescription"></wicket:message></p>
            <hr/>
            <div wicket:id="isFrozen"></div>
            <div wicket:id="verifyCommitter"></div>
            <div wicket:id="incrementalPushTags"></div>
            <hr />
            <h4><wicket:message key="gb.preReceiveScripts"></wicket:message></h4>
            <p><wicket:message key="gb.preReceiveDescription"></wicket:message></p>
            <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="24" /></td></tr>
                    <tr><th><wicket:message key="gb.federationSets"></wicket:message></th><td style="padding:2px;"><span wicket:id="federationSets"></span></td></tr>
                    <tr>
                        <td style="padding:2px;"><span wicket:id="preReceiveScripts"></span></td>
                        <td style="vertical-align: top;"><span wicket:id="inheritedPreReceive"></span></td>
                    </tr>
                </tbody>
            </table>
            <hr />
            <h4><wicket:message key="gb.postReceiveScripts"></wicket:message></h4>
            <p><wicket:message key="gb.postReceiveDescription"></wicket:message></p>
            <table class="plain">
                <tbody class="settings">
                    <tr>
                        <td style="padding:2px;"><span wicket:id="postReceiveScripts"></span></td>
                        <td style="vertical-align: top;"><span wicket:id="inheritedPostReceive"></span></td>
                    </tr>
                </tbody>
            </table>
            <div wicket:id="customFieldsSection">
                <hr />
                <h4><wicket:message key="gb.customFields"></wicket:message></h4>
                <p><wicket:message key="gb.customFieldsDescription"></wicket:message></p>
                <table class="plain">
                    <tbody class="settings">
                        <tr wicket:id="customFieldsListView"><th><span wicket:id="customFieldLabel"></span></th><td class="edit"><input class="span8" type="text" wicket:id="customFieldValue" /></td></tr>
                    </tbody>
                </table>
            </div>
        </div>
        <!-- tickets tab -->
        <div class="tab-pane" id="tickets">
            <h4><wicket:message key="gb.ticketSettings"></wicket:message></h4>
            <p><wicket:message key="gb.ticketsWelcome"></wicket:message></p>
            <hr/>
            <div wicket:id="acceptNewPatchsets"></div>
            <div wicket:id="acceptNewTickets"></div>
            <div wicket:id="requireApproval"></div>
            <div wicket:id="mergeTo"></div>
        </div>
        <!-- federation -->
        <div class="tab-pane" id="federation">
            <h4><wicket:message key="gb.federation"></wicket:message></h4>
            <p><wicket:message key="gb.federationRepositoryDescription"></wicket:message></p>
            <hr/>
            <div wicket:id="federationStrategy"></div>
            <hr />
            <h4><wicket:message key="gb.federationSets"></wicket:message></h4>
            <p><wicket:message key="gb.federationSetsDescription"></wicket:message></p>
            <div wicket:id="federationSets"></div>
        </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>
            <h4><wicket:message key="gb.indexedBranches"></wicket:message></h4>
            <p><wicket:message key="gb.indexedBranchesDescription"></wicket:message></p>
            <div wicket:id="indexedBranches"></div>
        </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>
        <!-- garbage collection -->
        <div class="tab-pane" id="gc">
            <h4><wicket:message key="gb.garbageCollection"></wicket:message></h4>
            <p><wicket:message key="gb.garbageCollectionDescription"></wicket:message></p>
            <div wicket:id="gcPeriod"></div>
            <div wicket:id="gcThreshold"></div>
        </div>
        <!-- misc -->
        <div class="tab-pane" id="misc">
            <div wicket:id="origin"></div>
            <div wicket:id="head"></div>
            <hr/>
            <div wicket:id="showRemoteBranches"></div>
            <div wicket:id="skipSizeCalculation"></div>
            <div wicket:id="skipSummaryMetrics"></div>
            <div wicket:id="maxActivityCommits"></div>
            <div wicket:id="commitMessageRenderer"></div>
            <div wicket:id="metricAuthorExclusions"></div>
            <div wicket:id="mailingLists"></div>
        </div>
        <div class="form-actions"><input class="btn btn-appmenu" type="submit" value="Save" wicket:message="value:gb.save" wicket:id="save" /> &nbsp; <input class="btn" type="submit" value="Cancel" wicket:message="value:gb.cancel" wicket:id="cancel" /> &nbsp; <input class="btn btn-danger" type="submit" value="Delete" wicket:message="value:gb.delete" wicket:id="delete" /></div>
        </div>
    </div>
</div>
</div>
</div>
<div class="row">
<div class="span12">
    <div class="form-actions"><input class="btn btn-appmenu" type="submit" value="Save" wicket:message="value:gb.save" wicket:id="save" /> &nbsp; <input class="btn" type="submit" value="Cancel" wicket:message="value:gb.cancel" wicket:id="cancel" /> &nbsp; <input class="btn btn-danger" type="submit" value="Delete" wicket:message="value:gb.delete" wicket:id="delete" /></div>
</div>
</div>
</form>    
</body>
</wicket:extend>
</html>
src/main/java/com/gitblit/wicket/pages/EditRepositoryPage.java
@@ -29,7 +29,6 @@
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;
@@ -40,7 +39,6 @@
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.link.Link;
import org.apache.wicket.markup.html.list.ListItem;
@@ -48,6 +46,7 @@
import org.apache.wicket.model.CompoundPropertyModel;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.Model;
import org.apache.wicket.model.PropertyModel;
import org.apache.wicket.model.util.CollectionModel;
import org.apache.wicket.model.util.ListModel;
import org.eclipse.jgit.lib.Repository;
@@ -69,13 +68,22 @@
import com.gitblit.wicket.GitBlitWebSession;
import com.gitblit.wicket.StringChoiceRenderer;
import com.gitblit.wicket.WicketUtils;
import com.gitblit.wicket.panels.AccessPolicyPanel;
import com.gitblit.wicket.panels.BasePanel.JavascriptEventConfirmation;
import com.gitblit.wicket.panels.BulletListPanel;
import com.gitblit.wicket.panels.CheckboxOption;
import com.gitblit.wicket.panels.ChoiceOption;
import com.gitblit.wicket.panels.RegistrantPermissionsPanel;
import com.gitblit.wicket.panels.RepositoryNamePanel;
import com.gitblit.wicket.panels.TextOption;
public class EditRepositoryPage extends RootSubPage {
    private final boolean isCreate;
    RepositoryNamePanel namePanel;
    AccessPolicyPanel accessPolicyPanel;
    private boolean isAdmin;
@@ -196,7 +204,7 @@
            }
        }
        final Palette<UserChoice> ownersPalette = new Palette<UserChoice>("owners", new ListModel<UserChoice>(owners), new CollectionModel<UserChoice>(
              persons), new ChoiceRenderer<UserChoice>(null, "userId"), 12, true);
              persons), new ChoiceRenderer<UserChoice>(null, "userId"), 12, false);
        // indexed local branches palette
        List<String> allLocalBranches = new ArrayList<String>();
@@ -263,59 +271,8 @@
            @Override
            protected void onSubmit() {
                try {
                    // confirm a repository name was entered
                    if (repositoryModel.name == null && StringUtils.isEmpty(repositoryModel.name)) {
                        error(getString("gb.pleaseSetRepositoryName"));
                    if (!namePanel.updateModel(repositoryModel)) {
                        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
@@ -426,33 +383,15 @@
                    }
                } catch (GitBlitException e) {
                    error(e.getMessage());
                    namePanel.resetModel(repositoryModel);
                    return;
                }
                setRedirect(false);
                if (isCreate) {
                    setResponsePage(RepositoriesPage.class);
                } else {
                    setResponsePage(SummaryPage.class, WicketUtils.newRepositoryParameter(repositoryModel.name));
                }
                setResponsePage(SummaryPage.class, WicketUtils.newRepositoryParameter(repositoryModel.name));
            }
        };
        // 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(app().settings().getBoolean(Keys.web.allowForking, true)));
        DropDownChoice<AccessRestrictionType> accessRestriction = new DropDownChoice<AccessRestrictionType>("accessRestriction",
                AccessRestrictionType.choices(app().settings().getBoolean(Keys.git.allowAnonymousPushes, false)), 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
        // Determine available refs & branches
        List<String> availableRefs = new ArrayList<String>();
        List<String> availableBranches = new ArrayList<String>();
        if (!ArrayUtils.isEmpty(repositoryModel.availableRefs)) {
@@ -465,58 +404,79 @@
                }
            }
        }
        form.add(new DropDownChoice<String>("HEAD", availableRefs).setEnabled(availableRefs.size() > 0));
        boolean gcEnabled = app().settings().getBoolean(Keys.git.enableGarbageCollection, false);
        int defaultGcPeriod = app().settings().getInteger(Keys.git.defaultGarbageCollectionPeriod, 7);
        if (repositoryModel.gcPeriod == 0) {
            repositoryModel.gcPeriod = defaultGcPeriod;
        }
        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));
        // do not let the browser pre-populate these fields
        form.add(new SimpleAttributeModifier("autocomplete", "off"));
        // 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("acceptNewPatchsets"));
        form.add(new CheckBox("acceptNewTickets"));
        form.add(new CheckBox("requireApproval"));
        form.add(new DropDownChoice<String>("mergeTo", availableBranches).setEnabled(availableBranches.size() > 0));
        form.add(new CheckBox("useIncrementalPushTags"));
        form.add(new CheckBox("showRemoteBranches"));
        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));
        //
        //
        // GENERAL
        //
        namePanel = new RepositoryNamePanel("namePanel", repositoryModel);
        namePanel.setEditable(allowEditName);
        form.add(namePanel);
        mailingLists = new Model<String>(ArrayUtils.isEmpty(repositoryModel.mailingLists) ? ""
                : StringUtils.flattenStrings(repositoryModel.mailingLists, " "));
        form.add(new TextField<String>("mailingLists", mailingLists));
        form.add(indexedBranchesPalette);
        // XXX AccessPolicyPanel is defined later.
        List<AuthorizationControl> acList = Arrays.asList(AuthorizationControl.values());
        final RadioChoice<AuthorizationControl> authorizationControl = new RadioChoice<Constants.AuthorizationControl>(
                "authorizationControl", acList, new AuthorizationControlRenderer());
        form.add(authorizationControl);
        form.add(new ChoiceOption<String>("head",
                getString("gb.headRef"),
                getString("gb.headRefDescription"),
                new PropertyModel<String>(repositoryModel, "HEAD"),
                availableRefs));
        final CheckBox verifyCommitter = new CheckBox("verifyCommitter");
        verifyCommitter.setOutputMarkupId(true);
        form.add(verifyCommitter);
        //
        // PERMISSIONS
        //
        form.add(ownersPalette);
        form.add(usersPalette);
        form.add(teamsPalette);
        form.add(federationSetsPalette);
        //
        // TICKETS
        //
        form.add(new CheckboxOption("acceptNewPatchsets",
                getString("gb.acceptNewPatchsets"),
                getString("gb.acceptNewPatchsetsDescription"),
                new PropertyModel<Boolean>(repositoryModel, "acceptNewPatchsets")));
        form.add(new CheckboxOption("acceptNewTickets",
                getString("gb.acceptNewTickets"),
                getString("gb.acceptNewTicketsDescription"),
                new PropertyModel<Boolean>(repositoryModel, "acceptNewPatchsets")));
        form.add(new CheckboxOption("requireApproval",
                getString("gb.requireApproval"),
                getString("gb.requireApprovalDescription"),
                new PropertyModel<Boolean>(repositoryModel, "requireApproval")));
        form.add(new ChoiceOption<String>("mergeTo",
                getString("gb.mergeTo"),
                getString("gb.mergeToDescription"),
                new PropertyModel<String>(repositoryModel, "mergeTo"),
                availableBranches));
        //
        // RECEIVE
        //
        form.add(new CheckboxOption("isFrozen",
                getString("gb.isFrozen"),
                getString("gb.isFrozenDescription"),
                new PropertyModel<Boolean>(repositoryModel, "isFrozen")));
        form.add(new CheckboxOption("incrementalPushTags",
                getString("gb.enableIncrementalPushTags"),
                getString("gb.useIncrementalPushTagsDescription"),
                new PropertyModel<Boolean>(repositoryModel, "useIncrementalPushTags")));
        final CheckBox verifyCommitter = new CheckBox("checkbox", new PropertyModel<Boolean>(repositoryModel, "verifyCommitter"));
        verifyCommitter.setOutputMarkupId(true);
        form.add(new CheckboxOption("verifyCommitter",
                getString("gb.verifyCommitter"),
                getString("gb.verifyCommitterDescription") + "<br/>" + getString("gb.verifyCommitterNote"),
                verifyCommitter).setIsHtmlDescription(true));
        form.add(preReceivePalette);
        form.add(new BulletListPanel("inheritedPreReceive", getString("gb.inherited"), app().repositories()
                .getPreReceiveScriptsInherited(repositoryModel)));
@@ -528,17 +488,125 @@
        customFieldsSection.add(customFieldsListView);
        form.add(customFieldsSection.setVisible(!app().settings().getString(Keys.groovy.customFields, "").isEmpty()));
        //
        // FEDERATION
        //
        List<FederationStrategy> federationStrategies = new ArrayList<FederationStrategy>(
                Arrays.asList(FederationStrategy.values()));
        // federation strategies - remove ORIGIN choice if this repository has no origin.
        if (StringUtils.isEmpty(repositoryModel.origin)) {
            federationStrategies.remove(FederationStrategy.FEDERATE_ORIGIN);
        }
        form.add(new ChoiceOption<FederationStrategy>("federationStrategy",
                getString("gb.federationStrategy"),
                getString("gb.federationStrategyDescription"),
                new DropDownChoice<FederationStrategy>(
                        "choice",
                        new PropertyModel<FederationStrategy>(repositoryModel, "federationStrategy"),
                        federationStrategies,
                        new FederationTypeRenderer())));
        form.add(federationSetsPalette);
        //
        // SEARCH
        //
        form.add(indexedBranchesPalette);
        //
        // GARBAGE COLLECTION
        //
        boolean gcEnabled = app().settings().getBoolean(Keys.git.enableGarbageCollection, false);
        int defaultGcPeriod = app().settings().getInteger(Keys.git.defaultGarbageCollectionPeriod, 7);
        if (repositoryModel.gcPeriod == 0) {
            repositoryModel.gcPeriod = defaultGcPeriod;
        }
        List<Integer> gcPeriods = Arrays.asList(1, 2, 3, 4, 5, 7, 10, 14 );
        form.add(new ChoiceOption<Integer>("gcPeriod",
                getString("gb.gcPeriod"),
                getString("gb.gcPeriodDescription"),
                new DropDownChoice<Integer>("choice",
                        new PropertyModel<Integer>(repositoryModel, "gcPeriod"),
                        gcPeriods,
                        new GCPeriodRenderer())).setEnabled(gcEnabled));
        form.add(new TextOption("gcThreshold",
                getString("gb.gcThreshold"),
                getString("gb.gcThresholdDescription"),
                "span1",
                new PropertyModel<String>(repositoryModel, "gcThreshold")).setEnabled(gcEnabled));
        //
        // MISCELLANEOUS
        //
        form.add(new TextOption("origin",
                getString("gb.origin"),
                getString("gb.originDescription"),
                "span6",
                new PropertyModel<String>(repositoryModel, "origin")).setEnabled(false));
        form.add(new CheckboxOption("showRemoteBranches",
                getString("gb.showRemoteBranches"),
                getString("gb.showRemoteBranchesDescription"),
                new PropertyModel<Boolean>(repositoryModel, "showRemoteBranches")));
        form.add(new CheckboxOption("skipSizeCalculation",
                getString("gb.skipSizeCalculation"),
                getString("gb.skipSizeCalculationDescription"),
                new PropertyModel<Boolean>(repositoryModel, "skipSizeCalculation")));
        form.add(new CheckboxOption("skipSummaryMetrics",
                getString("gb.skipSummaryMetrics"),
                getString("gb.skipSummaryMetricsDescription"),
                new PropertyModel<Boolean>(repositoryModel, "skipSummaryMetrics")));
        List<Integer> maxActivityCommits  = Arrays.asList(-1, 0, 25, 50, 75, 100, 150, 200, 250, 500);
        form.add(new ChoiceOption<Integer>("maxActivityCommits",
                getString("gb.maxActivityCommits"),
                getString("gb.maxActivityCommitsDescription"),
                new DropDownChoice<Integer>("choice",
                        new PropertyModel<Integer>(repositoryModel, "maxActivityCommits"),
                        maxActivityCommits,
                        new MaxActivityCommitsRenderer())));
        List<CommitMessageRenderer> renderers = Arrays.asList(CommitMessageRenderer.values());
        form.add(new ChoiceOption<CommitMessageRenderer>("commitMessageRenderer",
                getString("gb.commitMessageRenderer"),
                getString("gb.commitMessageRendererDescription"),
                new DropDownChoice<CommitMessageRenderer>("choice",
                        new PropertyModel<CommitMessageRenderer>(repositoryModel, "commitMessageRenderer"),
                        renderers)));
        metricAuthorExclusions = new Model<String>(ArrayUtils.isEmpty(repositoryModel.metricAuthorExclusions) ? ""
                : StringUtils.flattenStrings(repositoryModel.metricAuthorExclusions, " "));
        form.add(new TextOption("metricAuthorExclusions",
                getString("gb.metricAuthorExclusions"),
                getString("gb.metricAuthorExclusions"),
                "span6",
                metricAuthorExclusions));
        mailingLists = new Model<String>(ArrayUtils.isEmpty(repositoryModel.mailingLists) ? ""
                : StringUtils.flattenStrings(repositoryModel.mailingLists, " "));
        form.add(new TextOption("mailingLists",
                getString("gb.mailingLists"),
                getString("gb.mailingLists"),
                "span6",
                mailingLists));
        // 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);
@@ -546,15 +614,18 @@
            teamsPalette.setEnabled(allowFineGrainedControls);
        }
        accessRestriction.add(new AjaxFormComponentUpdatingBehavior("onchange") {
        //
        // ACCESS POLICY PANEL (GENERAL)
        //
        AjaxFormChoiceComponentUpdatingBehavior callback = new AjaxFormChoiceComponentUpdatingBehavior() {
            private static final long serialVersionUID = 1L;
            @Override
            protected void onUpdate(AjaxRequestTarget target) {
                // enable/disable permissions panel based on access restriction
                accessPolicyPanel.updateModel(repositoryModel);
                boolean allowAuthorizationControl = repositoryModel.accessRestriction.exceeds(AccessRestrictionType.NONE);
                authorizationControl.setEnabled(allowAuthorizationControl);
                verifyCommitter.setEnabled(allowAuthorizationControl);
                boolean allowFineGrainedControls = allowAuthorizationControl && repositoryModel.authorizationControl.equals(AuthorizationControl.NAMED);
@@ -565,41 +636,19 @@
                    repositoryModel.authorizationControl = AuthorizationControl.NAMED;
                }
                target.addComponent(authorizationControl);
                target.addComponent(verifyCommitter);
                target.addComponent(usersPalette);
                target.addComponent(teamsPalette);
            }
        });
        };
        authorizationControl.add(new AjaxFormChoiceComponentUpdatingBehavior() {
        accessPolicyPanel = new AccessPolicyPanel("accessPolicyPanel", repositoryModel, callback);
        form.add(accessPolicyPanel);
            private static final long serialVersionUID = 1L;
            @Override
            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);
            }
        });
        List<CommitMessageRenderer> renderers = Arrays.asList(CommitMessageRenderer.values());
        DropDownChoice<CommitMessageRenderer> messageRendererChoice = new DropDownChoice<CommitMessageRenderer>("commitMessageRenderer", renderers);
        form.add(messageRendererChoice);
        //
        // FORM CONTROLS
        //
        form.add(new Button("save"));
        Button cancel = new Button("cancel") {
            private static final long serialVersionUID = 1L;
@@ -632,7 +681,15 @@
                if (canDelete) {
                    if (app().repositories().deleteRepositoryModel(latestModel)) {
                        info(MessageFormat.format(getString("gb.repositoryDeleted"), latestModel));
                        setResponsePage(RepositoriesPage.class);
                        if (latestModel.isPersonalRepository()) {
                            // redirect to user's profile page
                            String prefix = app().settings().getString(Keys.git.userRepositoryPrefix, "~");
                            String username = latestModel.projectPath.substring(prefix.length());
                            setResponsePage(UserPage.class, WicketUtils.newUsernameParameter(username));
                        } else {
                            // redirect to server repositories page
                            setResponsePage(RepositoriesPage.class);
                        }
                    } else {
                        error(MessageFormat.format(getString("gb.repositoryDeleteFailed"), latestModel));
                    }
@@ -697,26 +754,6 @@
        }
    }
    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> {
@@ -735,27 +772,6 @@
        @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);
        }
    }
src/main/java/com/gitblit/wicket/pages/EmptyRepositoryPage.html
@@ -10,7 +10,7 @@
<div class="markdown">
<div class="row">
<div class="span10 offset1">    
    <h2><center>Empty Repository</center></h2>
    <h3><center>Empty Repository</center></h3>
    <div class="alert alert-info">
        <span wicket:id="repository" style="font-weight: bold;">[repository]</span> is an empty repository and can not be viewed by Gitblit.
        <p></p>        
src/main/java/com/gitblit/wicket/pages/EmptyRepositoryPage_es.html
@@ -12,7 +12,7 @@
<div class="markdown">
<div class="row">
<div class="span10 offset1">    
    <h2><center>Repositorio Vac&iacute;o</center></h2>
    <h3><center>Repositorio Vac&iacute;o</center></h3>
    <div class="alert alert-info">
        <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>        
src/main/java/com/gitblit/wicket/pages/EmptyRepositoryPage_ko.html
@@ -10,7 +10,7 @@
<div class="markdown">
<div class="row">
<div class="span10 offset1">    
    <h2><center>비어있는 저장소</center></h2>
    <h3><center>비어있는 저장소</center></h3>
    <div class="alert alert-info">
        <span wicket:id="repository" style="font-weight: bold;">[repository]</span> 저장소는 비어 있어서 Gitblit 에서 볼 수 없습니다.
        <p></p>        
src/main/java/com/gitblit/wicket/pages/EmptyRepositoryPage_nl.html
@@ -10,7 +10,7 @@
<div class="markdown">
<div class="row">
<div class="span10 offset1">    
    <h2><center>Lege Repository</center></h2>
    <h3><center>Lege Repository</center></h3>
    <div class="alert alert-info">
        <span wicket:id="repository" style="font-weight: bold;">[repository]</span> is een lege repository en kan niet bekeken worden door Gitblit.
        <p></p>        
src/main/java/com/gitblit/wicket/pages/EmptyRepositoryPage_pl.html
@@ -12,7 +12,7 @@
<div class="markdown">
<div class="row">
<div class="span10 offset1">    
    <h2><center>Puste repozytorium</center></h2>
    <h3><center>Puste repozytorium</center></h3>
    <div class="alert alert-info">
        <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>        
src/main/java/com/gitblit/wicket/pages/EmptyRepositoryPage_pt_BR.html
@@ -10,7 +10,7 @@
<div class="markdown">
<div class="row">
<div class="span10 offset1">    
    <h2><center>Repositório Vazio</center></h2>
    <h3><center>Repositório Vazio</center></h3>
    <div class="alert alert-info">
        <span wicket:id="repository" style="font-weight: bold;">[repository]</span> é um repositório vazio e não pode ser visualizado pelo Gitblit.
        <p></p>        
src/main/java/com/gitblit/wicket/pages/EmptyRepositoryPage_zh_CN.html
@@ -10,7 +10,7 @@
<div class="markdown">
<div class="row">
<div class="span10 offset1">    
    <h2><center>空版本库</center></h2>
    <h3><center>空版本库</center></h3>
    <div class="alert alert-info">
        <span wicket:id="repository" style="font-weight: bold;">[repository]</span> 版本库目前为空。
        Gitblit 无法查看。
src/main/java/com/gitblit/wicket/pages/NewRepositoryPage.html
New file
@@ -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">
<wicket:extend>
<body onload="document.getElementById('name').focus();">
    <form style="padding-top:5px;" wicket:id="editForm">
<div class="row">
    <div class="span12">
        <div wicket:id="namePanel"></div>
        <hr/>
        <div wicket:id="accessPolicyPanel"></div>
        <hr/>
        <h4><wicket:message key="gb.initialCommit"></wicket:message></h4>
        <p><wicket:message key="gb.initialCommitDescription"></wicket:message></p>
        <div wicket:id="addReadme"></div>
        <div wicket:id="addGitIgnore"></div>
        <div wicket:id="addGitFlow"></div>
    </div>
</div>
<div class="row">
<div class="span12">
    <div class="form-actions"><input class="btn btn-appmenu" type="submit" value="Create" wicket:message="value:gb.create" wicket:id="create" /></div>
</div>
</div>
</form>
</body>
</wicket:extend>
</html>
src/main/java/com/gitblit/wicket/pages/NewRepositoryPage.java
New file
@@ -0,0 +1,373 @@
/*
 * Copyright 2014 gitblit.com.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.gitblit.wicket.pages;
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.apache.wicket.behavior.SimpleAttributeModifier;
import org.apache.wicket.markup.html.form.Button;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.model.CompoundPropertyModel;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.Model;
import org.eclipse.jgit.dircache.DirCache;
import org.eclipse.jgit.dircache.DirCacheBuilder;
import org.eclipse.jgit.dircache.DirCacheEntry;
import org.eclipse.jgit.lib.CommitBuilder;
import org.eclipse.jgit.lib.Config;
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 com.gitblit.Constants;
import com.gitblit.Constants.AccessRestrictionType;
import com.gitblit.Constants.AuthorizationControl;
import com.gitblit.GitBlitException;
import com.gitblit.Keys;
import com.gitblit.models.RepositoryModel;
import com.gitblit.models.UserModel;
import com.gitblit.utils.ArrayUtils;
import com.gitblit.utils.FileUtils;
import com.gitblit.utils.StringUtils;
import com.gitblit.wicket.GitBlitWebSession;
import com.gitblit.wicket.WicketUtils;
import com.gitblit.wicket.panels.AccessPolicyPanel;
import com.gitblit.wicket.panels.CheckboxOption;
import com.gitblit.wicket.panels.ConditionalChoiceOption;
import com.gitblit.wicket.panels.RepositoryNamePanel;
public class NewRepositoryPage extends RootSubPage {
    private final RepositoryModel repositoryModel;
    private IModel<Boolean> addReadmeModel;
    private Model<String> gitignoreModel;
    private IModel<Boolean> addGitflowModel;
    private IModel<Boolean> addGitignoreModel;
    private AccessPolicyPanel accessPolicyPanel;
    private RepositoryNamePanel namePanel;
    public NewRepositoryPage() {
        // create constructor
        super();
        repositoryModel = new RepositoryModel();
        setupPage(getString("gb.newRepository"), "");
        setStatelessHint(false);
        setOutputMarkupId(true);
    }
    @Override
    protected boolean requiresPageMap() {
        return true;
    }
    @Override
    protected Class<? extends BasePage> getRootNavPageClass() {
        return RepositoriesPage.class;
    }
    @Override
    protected void onInitialize() {
        super.onInitialize();
        CompoundPropertyModel<RepositoryModel> rModel = new CompoundPropertyModel<>(repositoryModel);
        Form<RepositoryModel> form = new Form<RepositoryModel>("editForm", rModel) {
            private static final long serialVersionUID = 1L;
            @Override
            protected void onSubmit() {
                try {
                    if (!namePanel.updateModel(repositoryModel)) {
                        return;
                    }
                    accessPolicyPanel.updateModel(repositoryModel);
                    repositoryModel.owners = new ArrayList<String>();
                    repositoryModel.owners.add(GitBlitWebSession.get().getUsername());
                    // setup branch defaults
                    boolean useGitFlow = addGitflowModel.getObject();
                    repositoryModel.HEAD = Constants.R_MASTER;
                    repositoryModel.mergeTo = Constants.MASTER;
                    if (useGitFlow) {
                        // tickets normally merge to develop unless they are hotfixes
                        repositoryModel.mergeTo = Constants.DEVELOP;
                    }
                    repositoryModel.allowForks = app().settings().getBoolean(Keys.web.allowForking, true);
                    // optionally generate an initial commit
                    boolean addReadme = addReadmeModel.getObject();
                    String gitignore = null;
                    boolean addGitignore = addGitignoreModel.getObject();
                    if (addGitignore) {
                        gitignore = gitignoreModel.getObject();
                        if (StringUtils.isEmpty(gitignore)) {
                            throw new GitBlitException(getString("gb.pleaseSelectGitIgnore"));
                        }
                    }
                    // init the repository
                    app().gitblit().updateRepositoryModel(repositoryModel.name, repositoryModel, true);
                    // optionally create an initial commit
                    initialCommit(repositoryModel, addReadme, gitignore, useGitFlow);
                } catch (GitBlitException e) {
                    error(e.getMessage());
                    namePanel.resetModel(repositoryModel);
                    return;
                }
                setRedirect(true);
                setResponsePage(SummaryPage.class, WicketUtils.newRepositoryParameter(repositoryModel.name));
            }
        };
        // do not let the browser pre-populate these fields
        form.add(new SimpleAttributeModifier("autocomplete", "off"));
        namePanel = new RepositoryNamePanel("namePanel", repositoryModel);
        form.add(namePanel);
        // prepare the default access controls
        AccessRestrictionType defaultRestriction = AccessRestrictionType.fromName(
                app().settings().getString(Keys.git.defaultAccessRestriction, AccessRestrictionType.PUSH.name()));
        if (AccessRestrictionType.NONE == defaultRestriction) {
            defaultRestriction = AccessRestrictionType.PUSH;
        }
        AuthorizationControl defaultControl = AuthorizationControl.fromName(
                app().settings().getString(Keys.git.defaultAuthorizationControl, AuthorizationControl.NAMED.name()));
        if (AuthorizationControl.AUTHENTICATED == defaultControl) {
            defaultRestriction = AccessRestrictionType.PUSH;
        }
        repositoryModel.authorizationControl = defaultControl;
        repositoryModel.accessRestriction = defaultRestriction;
        accessPolicyPanel = new AccessPolicyPanel("accessPolicyPanel", repositoryModel);
        form.add(accessPolicyPanel);
        //
        // initial commit options
        //
        // add README
        addReadmeModel = Model.of(false);
        form.add(new CheckboxOption("addReadme",
                getString("gb.initWithReadme"),
                getString("gb.initWithReadmeDescription"),
                addReadmeModel));
        // add .gitignore
        File gitignoreDir = app().runtime().getFileOrFolder(Keys.git.gitignoreFolder, "${baseFolder}/gitignore");
        File [] files = gitignoreDir.listFiles();
        if (files == null) {
            files = new File[0];
        }
        List<String> gitignores = new ArrayList<String>();
        for (File file : files) {
            if (file.isFile() && file.getName().endsWith(".gitignore")) {
                gitignores.add(StringUtils.stripFileExtension(file.getName()));
            }
        }
        Collections.sort(gitignores);
        gitignoreModel = Model.of("");
        addGitignoreModel = Model.of(false);
        form.add(new ConditionalChoiceOption<String>("addGitIgnore",
                getString("gb.initWithGitignore"),
                getString("gb.initWithGitignoreDescription"),
                addGitignoreModel,
                gitignoreModel,
                gitignores));
        // TODO consider gitflow at creation (ticket-55)
        addGitflowModel = Model.of(false);
        form.add(new CheckboxOption("addGitFlow",
                "Include a .gitflow file",
                "This will generate a config file which guides Git clients in setting up Gitflow branches.",
                addGitflowModel).setVisible(false));
        form.add(new Button("create"));
        add(form);
    }
    /**
     * Prepare the initial commit for the repository.
     *
     * @param repository
     * @param addReadme
     * @param gitignore
     * @param addGitFlow
     * @return true if an initial commit was created
     */
    protected boolean initialCommit(RepositoryModel repository, boolean addReadme, String gitignore,
            boolean addGitFlow) {
        boolean initialCommit = addReadme || !StringUtils.isEmpty(gitignore) || addGitFlow;
        if (!initialCommit) {
            return false;
        }
        // build an initial commit
        boolean success = false;
        Repository db = app().repositories().getRepository(repositoryModel.name);
        ObjectInserter odi = db.newObjectInserter();
        try {
            UserModel user = GitBlitWebSession.get().getUser();
            PersonIdent author = new PersonIdent(user.getDisplayName(), user.emailAddress);
            DirCache newIndex = DirCache.newInCore();
            DirCacheBuilder indexBuilder = newIndex.builder();
            if (addReadme) {
                // insert a README
                String title = StringUtils.stripDotGit(StringUtils.getLastPathElement(repositoryModel.name));
                String description = repositoryModel.description == null ? "" : repositoryModel.description;
                String readme = String.format("## %s\n\n%s\n\n", title, description);
                byte [] bytes = readme.getBytes(Constants.ENCODING);
                DirCacheEntry entry = new DirCacheEntry("README.md");
                entry.setLength(bytes.length);
                entry.setLastModified(System.currentTimeMillis());
                entry.setFileMode(FileMode.REGULAR_FILE);
                entry.setObjectId(odi.insert(org.eclipse.jgit.lib.Constants.OBJ_BLOB, bytes));
                indexBuilder.add(entry);
            }
            if (!StringUtils.isEmpty(gitignore)) {
                // insert a .gitignore file
                File dir = app().runtime().getFileOrFolder(Keys.git.gitignoreFolder, "${baseFolder}/gitignore");
                File file = new File(dir, gitignore + ".gitignore");
                if (file.exists() && file.length() > 0) {
                    byte [] bytes = FileUtils.readContent(file);
                    if (!ArrayUtils.isEmpty(bytes)) {
                        DirCacheEntry entry = new DirCacheEntry(".gitignore");
                        entry.setLength(bytes.length);
                        entry.setLastModified(System.currentTimeMillis());
                        entry.setFileMode(FileMode.REGULAR_FILE);
                        entry.setObjectId(odi.insert(org.eclipse.jgit.lib.Constants.OBJ_BLOB, bytes));
                        indexBuilder.add(entry);
                    }
                }
            }
            if (addGitFlow) {
                // insert a .gitflow file
                Config config = new Config();
                config.setString("gitflow", null, "masterBranch", Constants.MASTER);
                config.setString("gitflow", null, "developBranch", Constants.DEVELOP);
                config.setString("gitflow", null, "featureBranchPrefix", "feature/");
                config.setString("gitflow", null, "releaseBranchPrefix", "release/");
                config.setString("gitflow", null, "hotfixBranchPrefix", "hotfix/");
                config.setString("gitflow", null, "supportBranchPrefix", "support/");
                config.setString("gitflow", null, "versionTagPrefix", "");
                byte [] bytes = config.toText().getBytes(Constants.ENCODING);
                DirCacheEntry entry = new DirCacheEntry(".gitflow");
                entry.setLength(bytes.length);
                entry.setLastModified(System.currentTimeMillis());
                entry.setFileMode(FileMode.REGULAR_FILE);
                entry.setObjectId(odi.insert(org.eclipse.jgit.lib.Constants.OBJ_BLOB, bytes));
                indexBuilder.add(entry);
            }
            indexBuilder.finish();
            if (newIndex.getEntryCount() == 0) {
                // nothing to commit
                return false;
            }
            ObjectId treeId = newIndex.writeTree(odi);
            // Create a commit object
            CommitBuilder commit = new CommitBuilder();
            commit.setAuthor(author);
            commit.setCommitter(author);
            commit.setEncoding(Constants.ENCODING);
            commit.setMessage("Initial commit");
            commit.setTreeId(treeId);
            // Insert the commit into the repository
            ObjectId commitId = odi.insert(commit);
            odi.flush();
            // set the branch refs
            RevWalk revWalk = new RevWalk(db);
            try {
                // set the master branch
                RevCommit revCommit = revWalk.parseCommit(commitId);
                RefUpdate masterRef = db.updateRef(Constants.R_MASTER);
                masterRef.setNewObjectId(commitId);
                masterRef.setRefLogMessage("commit: " + revCommit.getShortMessage(), false);
                Result masterRC = masterRef.update();
                switch (masterRC) {
                case NEW:
                    success = true;
                    break;
                default:
                    success = false;
                }
                if (addGitFlow) {
                    // set the develop branch for git-flow
                    RefUpdate developRef = db.updateRef(Constants.R_DEVELOP);
                    developRef.setNewObjectId(commitId);
                    developRef.setRefLogMessage("commit: " + revCommit.getShortMessage(), false);
                    Result developRC = developRef.update();
                    switch (developRC) {
                    case NEW:
                        success = true;
                        break;
                    default:
                        success = false;
                    }
                }
            } finally {
                revWalk.release();
            }
        } catch (UnsupportedEncodingException e) {
            logger().error(null, e);
        } catch (IOException e) {
            logger().error(null, e);
        } finally {
            odi.release();
            db.close();
        }
        return success;
    }
}
src/main/java/com/gitblit/wicket/pages/RootPage.java
@@ -607,7 +607,7 @@
            List<MenuItem> standardItems = new ArrayList<MenuItem>();
            standardItems.add(new MenuDivider());
            if (user.canAdmin() || user.canCreate()) {
                standardItems.add(new PageLinkMenuItem("gb.newRepository", EditRepositoryPage.class));
                standardItems.add(new PageLinkMenuItem("gb.newRepository", app().getNewRepositoryPage()));
            }
            standardItems.add(new PageLinkMenuItem("gb.myProfile", UserPage.class,
                    WicketUtils.newUsernameParameter(user.username)));
src/main/java/com/gitblit/wicket/pages/UserPage.java
@@ -30,8 +30,8 @@
import com.gitblit.Keys;
import com.gitblit.models.Menu.ParameterMenuItem;
import com.gitblit.models.NavLink.DropDownPageMenuNavLink;
import com.gitblit.models.NavLink;
import com.gitblit.models.NavLink.DropDownPageMenuNavLink;
import com.gitblit.models.ProjectModel;
import com.gitblit.models.RepositoryModel;
import com.gitblit.models.UserModel;
@@ -95,7 +95,7 @@
        UserModel sessionUser = GitBlitWebSession.get().getUser();
        if (sessionUser != null && user.canCreate() && sessionUser.equals(user)) {
            // user can create personal repositories
            add(new BookmarkablePageLink<Void>("newRepository", EditRepositoryPage.class));
            add(new BookmarkablePageLink<Void>("newRepository", app().getNewRepositoryPage()));
        } else {
            add(new Label("newRepository").setVisible(false));
        }
src/main/java/com/gitblit/wicket/panels/AccessPolicyPanel.html
New file
@@ -0,0 +1,28 @@
<!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>
    <h4><wicket:message key="gb.accessPolicy"></wicket:message></h4>
    <p><wicket:message key="gb.accessPolicyDescription"></wicket:message></p>
    <div wicket:id="policiesGroup">
        <div wicket:id="policies" style="padding-top:4px;">
            <div>
                <label style="font-weight:bold;"><input type="radio" wicket:id="radio" /> <img wicket:id="image"></img> <span wicket:id="name"></span></label>
            </div>
            <label class="checkbox" style="color:#777;" wicket:id="description"></label>
        </div>
    </div>
    <hr />
    <div wicket:id="allowForks"></div>
</wicket:panel>
</body>
</html>
src/main/java/com/gitblit/wicket/panels/AccessPolicyPanel.java
New file
@@ -0,0 +1,192 @@
/*
 * Copyright 2014 gitblit.com.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.gitblit.wicket.panels;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import org.apache.wicket.ajax.form.AjaxFormChoiceComponentUpdatingBehavior;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.form.Radio;
import org.apache.wicket.markup.html.form.RadioGroup;
import org.apache.wicket.markup.html.list.ListItem;
import org.apache.wicket.markup.html.list.ListView;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.Model;
import com.gitblit.Constants.AccessRestrictionType;
import com.gitblit.Constants.AuthorizationControl;
import com.gitblit.Keys;
import com.gitblit.models.RepositoryModel;
import com.gitblit.wicket.WicketUtils;
/**
 * A radio group panel of the 5 available authorization/access restriction combinations.
 *
 * @author James Moger
 *
 */
public class AccessPolicyPanel extends BasePanel {
    private static final long serialVersionUID = 1L;
    private final RepositoryModel repository;
    private final AjaxFormChoiceComponentUpdatingBehavior callback;
    private RadioGroup<AccessPolicy> policiesGroup;
    private IModel<Boolean> allowForks;
    public AccessPolicyPanel(String wicketId, RepositoryModel repository) {
        this(wicketId, repository, null);
    }
    public AccessPolicyPanel(String wicketId, RepositoryModel repository, AjaxFormChoiceComponentUpdatingBehavior callback) {
        super(wicketId);
        this.repository = repository;
        this.callback = callback;
    }
    @Override
    protected void onInitialize() {
        super.onInitialize();
        AccessPolicy anonymousPolicy = new AccessPolicy(getString("gb.anonymousPolicy"),
                getString("gb.anonymousPolicyDescription"),
                "blank.png",
                AuthorizationControl.AUTHENTICATED,
                AccessRestrictionType.NONE);
        AccessPolicy authenticatedPushPolicy = new AccessPolicy(getString("gb.authenticatedPushPolicy"),
                getString("gb.authenticatedPushPolicyDescription"),
                "lock_go_16x16.png",
                AuthorizationControl.AUTHENTICATED,
                AccessRestrictionType.PUSH);
        AccessPolicy namedPushPolicy = new AccessPolicy(getString("gb.namedPushPolicy"),
                getString("gb.namedPushPolicyDescription"),
                "lock_go_16x16.png",
                AuthorizationControl.NAMED,
                AccessRestrictionType.PUSH);
        AccessPolicy clonePolicy = new AccessPolicy(getString("gb.clonePolicy"),
                getString("gb.clonePolicyDescription"),
                "lock_pull_16x16.png",
                AuthorizationControl.NAMED,
                AccessRestrictionType.CLONE);
        AccessPolicy viewPolicy = new AccessPolicy(getString("gb.viewPolicy"),
                getString("gb.viewPolicyDescription"),
                "shield_16x16.png",
                AuthorizationControl.NAMED,
                AccessRestrictionType.VIEW);
        List<AccessPolicy> policies = new ArrayList<AccessPolicy>();
        if (app().settings().getBoolean(Keys.git.allowAnonymousPushes, false)) {
            policies.add(anonymousPolicy);
        }
        policies.add(authenticatedPushPolicy);
        policies.add(namedPushPolicy);
        policies.add(clonePolicy);
        policies.add(viewPolicy);
        AccessRestrictionType defaultRestriction = repository.accessRestriction;
        if (defaultRestriction == null) {
            defaultRestriction = AccessRestrictionType.fromName(app().settings().getString(Keys.git.defaultAccessRestriction,
                    AccessRestrictionType.PUSH.name()));
        }
        AuthorizationControl defaultControl = repository.authorizationControl;
        if (defaultControl == null) {
            defaultControl = AuthorizationControl.fromName(app().settings().getString(Keys.git.defaultAuthorizationControl,
                    AuthorizationControl.NAMED.name()));
        }
        AccessPolicy defaultPolicy = namedPushPolicy;
        for (AccessPolicy policy : policies) {
            if (policy.type == defaultRestriction && policy.control == defaultControl) {
                defaultPolicy = policy;
            }
        }
        policiesGroup = new RadioGroup<>("policiesGroup", new Model<AccessPolicy>(defaultPolicy));
        ListView<AccessPolicy> policiesList = new ListView<AccessPolicy>("policies", policies) {
            private static final long serialVersionUID = 1L;
            @Override
            protected void populateItem(ListItem<AccessPolicy> item) {
                AccessPolicy p = item.getModelObject();
                item.add(new Radio<AccessPolicy>("radio", item.getModel()));
                item.add(WicketUtils.newImage("image",  p.image));
                item.add(new Label("name", p.name));
                item.add(new Label("description", p.description));
            }
        };
        policiesGroup.add(policiesList);
        if (callback != null) {
            policiesGroup.add(callback);
            policiesGroup.setOutputMarkupId(true);
        }
        add(policiesGroup);
        allowForks = Model.of(true);
        add(new CheckboxOption("allowForks",
                getString("gb.allowForks"),
                getString("gb.allowForksDescription"),
                allowForks).setEnabled(app().settings().getBoolean(Keys.web.allowForking, true)));
        setOutputMarkupId(true);
    }
    public void updateModel(RepositoryModel repository) {
        AccessPolicy policy = policiesGroup.getModelObject();
        repository.authorizationControl = policy.control;
        repository.accessRestriction = policy.type;
        repository.allowForks = allowForks.getObject();
    }
    @Override
    protected boolean getStatelessHint() {
        return false;
    }
    public static class AccessPolicy implements Serializable {
        private static final long serialVersionUID = 1L;
        final String name;
        final String description;
        final String image;
        final AuthorizationControl control;
        final AccessRestrictionType type;
        AccessPolicy(String name, String description, String img, AuthorizationControl control, AccessRestrictionType type) {
            this.name = name;
            this.description = description;
            this.image = img;
            this.control = control;
            this.type = type;
        }
        @Override
        public String toString() {
            return name;
        }
    }
}
src/main/java/com/gitblit/wicket/panels/CheckboxOption.html
New file
@@ -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:panel>
    <div style="padding-top:4px;">
        <div>
            <label style="font-weight:bold;" class="checkbox"><input type="checkbox" wicket:id="checkbox" /> <span wicket:id="name"></span></label>
        </div>
        <label class="checkbox" style="color:#777;" wicket:id="description"></label>
    </div>
</wicket:panel>
</body>
</html>
src/main/java/com/gitblit/wicket/panels/CheckboxOption.java
New file
@@ -0,0 +1,54 @@
/*
 * Copyright 2014 gitblit.com.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.gitblit.wicket.panels;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.form.CheckBox;
import org.apache.wicket.model.IModel;
import org.parboiled.common.StringUtils;
/**
 * A re-usable checkbox option panel.
 *
 * [x] title
 *     description
 *
 * @author James Moger
 *
 */
public class CheckboxOption extends BasePanel {
    private static final long serialVersionUID = 1L;
    public CheckboxOption(String wicketId, String title, String description, IModel<Boolean> model) {
        super(wicketId);
        add(new Label("name", title));
        add(new Label("description", description).setVisible(!StringUtils.isEmpty(description)));
        add(new CheckBox("checkbox", model));
    }
    public CheckboxOption(String wicketId, String title, String description, CheckBox checkbox) {
        super(wicketId);
        add(new Label("name", title));
        add(new Label("description", description).setVisible(!StringUtils.isEmpty(description)));
        add(checkbox.setMarkupId("checkbox"));
    }
    public CheckboxOption setIsHtmlDescription(boolean val) {
        ((Label) get("description")).setEscapeModelStrings(!val);
        return this;
    }
}
src/main/java/com/gitblit/wicket/panels/ChoiceOption.html
New file
@@ -0,0 +1,19 @@
<!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 style="padding-top:4px;">
        <div>
            <b><span wicket:id="name"></span></b>
        </div>
        <label class="checkbox" style="color:#777;"> <span wicket:id="description"></span>
        <p style="padding-top:5px;"><select class="span3" wicket:id="choice" /></p>
        </label>
    </div>
</wicket:panel>
</body>
</html>
src/main/java/com/gitblit/wicket/panels/ChoiceOption.java
New file
@@ -0,0 +1,52 @@
/*
 * Copyright 2014 gitblit.com.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.gitblit.wicket.panels;
import java.util.List;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.form.DropDownChoice;
import org.apache.wicket.model.IModel;
import org.parboiled.common.StringUtils;
/**
 * A re-usable choice option panel.
 *
 * title
 *     description
 *     [choices]
 *
 * @author James Moger
 *
 */
public class ChoiceOption<T> extends BasePanel {
    private static final long serialVersionUID = 1L;
    public ChoiceOption(String wicketId, String title, String description, IModel<T> model, List<T> choices) {
        super(wicketId);
        add(new Label("name", title));
        add(new Label("description", description).setVisible(!StringUtils.isEmpty(description)));
        add(new DropDownChoice<>("choice", model, choices).setEnabled(choices.size() > 0));
    }
    public ChoiceOption(String wicketId, String title, String description, DropDownChoice<?> choice) {
        super(wicketId);
        add(new Label("name", title));
        add(new Label("description", description).setVisible(!StringUtils.isEmpty(description)));
        add(choice.setMarkupId("choice").setEnabled(choice.getChoices().size() > 0));
    }
}
src/main/java/com/gitblit/wicket/panels/ConditionalChoiceOption.html
New file
@@ -0,0 +1,19 @@
<!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 style="padding-top:4px;">
        <div>
            <label style="font-weight:bold;" class="checkbox"><input type="checkbox" wicket:id="checkbox" /> <span wicket:id="name"></span></label>
        </div>
        <label class="checkbox" style="color:#777;"> <span wicket:id="description"></span>
        <p style="padding-top:5px;"><select class="span3" wicket:id="choice" /></p>
        </label>
    </div>
</wicket:panel>
</body>
</html>
src/main/java/com/gitblit/wicket/panels/ConditionalChoiceOption.java
New file
@@ -0,0 +1,78 @@
/*
 * Copyright 2014 gitblit.com.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.gitblit.wicket.panels;
import java.util.List;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.form.AjaxFormComponentUpdatingBehavior;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.form.CheckBox;
import org.apache.wicket.markup.html.form.DropDownChoice;
import org.apache.wicket.model.IModel;
import org.parboiled.common.StringUtils;
/**
 * A re-usable conditional choice option panel.
 *
 * [x] title
 *     description
 *     [choices]
 *
 * @author James Moger
 *
 */
public class ConditionalChoiceOption<T> extends BasePanel {
    private static final long serialVersionUID = 1L;
    final CheckBox checkbox;
    final DropDownChoice<T> choice;
    public ConditionalChoiceOption(String wicketId, String title, String description, IModel<Boolean> checkboxModel, IModel<T> choiceModel, List<T> choices) {
        super(wicketId);
        add(new Label("name", title));
        add(new Label("description", description).setVisible(!StringUtils.isEmpty(description)));
        this.checkbox = new CheckBox("checkbox", checkboxModel);
        checkbox.setOutputMarkupId(true);
        this.choice = new DropDownChoice<T>("choice", choiceModel, choices);
        choice.setOutputMarkupId(true);
        setup();
    }
    private void setup() {
        add(checkbox);
        add(choice.setMarkupId("choice").setEnabled(choice.getChoices().size() > 0));
        choice.setEnabled(checkbox.getModelObject());
        checkbox.add(new AjaxFormComponentUpdatingBehavior("onchange") {
            private static final long serialVersionUID = 1L;
            @Override
            protected void onUpdate(AjaxRequestTarget target) {
                choice.setEnabled(checkbox.getModelObject());
                target.addComponent(choice);
                if (!choice.isEnabled()) {
                    choice.setModelObject(null);
                }
            }
        });
    }
}
src/main/java/com/gitblit/wicket/panels/FilterableRepositoryList.java
@@ -33,7 +33,6 @@
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,
@@ -98,7 +97,7 @@
        }
        if (allowCreate) {
            panel.add(new LinkPanel(ngList + "Button", "btn btn-mini", getString("gb.newRepository"), EditRepositoryPage.class));
            panel.add(new LinkPanel(ngList + "Button", "btn btn-mini", getString("gb.newRepository"), app().getNewRepositoryPage()));
        } else {
            panel.add(new Label(ngList + "Button").setVisible(false));
        }
src/main/java/com/gitblit/wicket/panels/RegistrantPermissionsPanel.html
@@ -16,7 +16,7 @@
    </form>
    
    <div style="clear:both;" wicket:id="permissionRow">
        <div style="padding-top:10px;border-left:1px solid #ccc;border-right:1px solid #ccc;" class="row-fluid">
        <div style="padding-top:10px;" class="row-fluid">
            <div style="padding-top:5px;padding-left:5px" class="span6"><span wicket:id="registrant"></span></div><div style="padding-top:5px;padding-right:5px;text-align:right;" class="span3"><span class="label" wicket:id="pType">[permission type]</span></div> <select class="input-medium" wicket:id="permission"></select>
        </div>
    </div>
src/main/java/com/gitblit/wicket/panels/RepositoriesPanel.java
@@ -51,7 +51,6 @@
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.ProjectPage;
import com.gitblit.wicket.pages.RepositoriesPage;
import com.gitblit.wicket.pages.SummaryPage;
@@ -87,12 +86,12 @@
                    setResponsePage(RepositoriesPage.class);
                }
            }.setVisible(app().settings().getBoolean(Keys.git.cacheRepositoryList, true)));
            managementLinks.add(new BookmarkablePageLink<Void>("newRepository", EditRepositoryPage.class));
            managementLinks.add(new BookmarkablePageLink<Void>("newRepository", app().getNewRepositoryPage()));
            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));
            managementLinks.add(new BookmarkablePageLink<Void>("newRepository", app().getNewRepositoryPage()));
            add(managementLinks);
        } else {
            // user has no management permissions
src/main/java/com/gitblit/wicket/panels/RepositoryNamePanel.html
New file
@@ -0,0 +1,30 @@
<!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>
    <table class="plain">
        <tbody class="settings">
            <tr>
                <th><wicket:message key="gb.project"></wicket:message></th>
                <th><wicket:message key="gb.name"></wicket:message></th>
            </tr>
            <tr>
                <td><select class="span2" wicket:id="projectPath" /></td>
                <td class="edit"><input class="span4" type="text" wicket:id="name" id="name" /> &nbsp;<span class="help-inline"><wicket:message key="gb.nameDescription"></wicket:message></span></td>
            </tr>
        </tbody>
    </table>
    <div>
        <b><wicket:message key="gb.description"></wicket:message></b><br/>
        <input class="span5" type="text" wicket:id="description" />
    </div>
</wicket:panel>
</body>
</html>
src/main/java/com/gitblit/wicket/panels/RepositoryNamePanel.java
New file
@@ -0,0 +1,176 @@
/*
 * Copyright 2014 gitblit.com.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.gitblit.wicket.panels;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Set;
import java.util.TreeSet;
import org.apache.wicket.markup.html.form.DropDownChoice;
import org.apache.wicket.markup.html.form.TextField;
import com.gitblit.models.ProjectModel;
import com.gitblit.models.RepositoryModel;
import com.gitblit.models.UserModel;
import com.gitblit.utils.StringUtils;
import com.gitblit.wicket.GitBlitWebSession;
/**
 * A panel for naming a repository, specifying it's project, and entering a description.
 *
 * @author James Moger
 *
 */
public class RepositoryNamePanel extends BasePanel {
    private static final long serialVersionUID = 1L;
    private String fullName;
    private DropDownChoice<String> projectChoice;
    private TextField<String> nameField;
    public RepositoryNamePanel(String wicketId, RepositoryModel repository) {
        super(wicketId);
        GitBlitWebSession session = GitBlitWebSession.get();
        UserModel user = session.getUser();
        // build project set for repository destination
        String defaultProject = null;
        Set<String> projectNames = new TreeSet<String>();
        // add the registered/known projects
        for (ProjectModel project : app().projects().getProjectModels(user, false)) {
            // TODO issue-351: user.canAdmin(project)
            if (user.canAdmin()) {
                if (project.isRoot) {
                    projectNames.add("/");
                } else {
                    projectNames.add(project.name + "/");
                }
            }
        }
        // add the user's personal project namespace
        if (user.canAdmin() || user.canCreate()) {
            projectNames.add(user.getPersonalPath() + "/");
        }
        if (!StringUtils.isEmpty(repository.name)) {
            // editing a repository name
            // set the defaultProject to the current repository project
            defaultProject = repository.projectPath;
            if (StringUtils.isEmpty(defaultProject)) {
                defaultProject = "/";
            } else {
                defaultProject += "/";
            }
            projectNames.add(defaultProject);
        }
        // if default project is not already set, set preference based on the user permissions
        if (defaultProject == null) {
            if (user.canAdmin()) {
                defaultProject = "/";
            } else if (user.canCreate()) {
                defaultProject = user.getPersonalPath() + "/";
            }
        }
        // update the model which is reflectively mapped to the Wicket fields by name
        repository.projectPath = defaultProject;
        if (repository.projectPath.length() > 1 && !StringUtils.isEmpty(repository.name)) {
            repository.name = repository.name.substring(repository.projectPath.length());
        }
        projectChoice = new DropDownChoice<String>("projectPath", new ArrayList<String>(projectNames));
        nameField = new TextField<String>("name");
        // only enable project selection if we actually have multiple choices
        add(projectChoice.setEnabled(projectNames.size() > 1));
        add(nameField);
        add(new TextField<String>("description"));
    }
    public void setEditable(boolean editable) {
        // only enable project selection if we actually have multiple choices
        projectChoice.setEnabled(projectChoice.getChoices().size() > 1 && editable);
        nameField.setEnabled(editable);
    }
    public boolean updateModel(RepositoryModel repositoryModel) {
        // confirm a project was selected
        if (StringUtils.isEmpty(repositoryModel.projectPath)) {
            error(getString("gb.pleaseSelectProject"));
            return false;
        }
        // confirm a repository name was entered
        if (StringUtils.isEmpty(repositoryModel.name)) {
            error(getString("gb.pleaseSetRepositoryName"));
            return false;
        }
        String project = repositoryModel.projectPath;
        fullName = (project + repositoryModel.name).trim();
        fullName = fullName.replace('\\', '/');
        fullName = fullName.replace("//", "/");
        if (fullName.charAt(0) == '/') {
            fullName = fullName.substring(1);
        }
        if (fullName.endsWith("/")) {
            fullName = fullName.substring(0, fullName.length() - 1);
        }
        if (fullName.contains("../")) {
            error(getString("gb.illegalRelativeSlash"));
            return false;
        }
        if (fullName.contains("/../")) {
            error(getString("gb.illegalRelativeSlash"));
            return false;
        }
        // confirm valid characters in repository name
        Character c = StringUtils.findInvalidCharacter(fullName);
        if (c != null) {
            error(MessageFormat.format(getString("gb.illegalCharacterRepositoryName"), c));
            return false;
        }
        repositoryModel.name = fullName;
        repositoryModel.projectPath = null;
        return true;
    }
    public void resetModel(RepositoryModel repositoryModel) {
        // restore project and name fields on error condition
        repositoryModel.projectPath = StringUtils.getFirstPathElement(fullName) + "/";
        if (repositoryModel.projectPath.length() > 1) {
            repositoryModel.name = fullName.substring(repositoryModel.projectPath.length());
        }
    }
    @Override
    protected boolean getStatelessHint() {
        return false;
    }
}
src/main/java/com/gitblit/wicket/panels/TextOption.html
New file
@@ -0,0 +1,20 @@
<!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 style="padding-top:4px;">
        <div>
            <b><span wicket:id="name"></span></b>
        </div>
        <label class="checkbox" style="color:#777;"> <span wicket:id="description"></span>
        <p style="padding-top:5px;"><input class="span2" type="text" wicket:id="text" /></p>
        </label>
    </div>
</wicket:panel>
</body>
</html>
src/main/java/com/gitblit/wicket/panels/TextOption.java
New file
@@ -0,0 +1,53 @@
/*
 * Copyright 2014 gitblit.com.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.gitblit.wicket.panels;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.form.TextField;
import org.apache.wicket.model.IModel;
import com.gitblit.utils.StringUtils;
import com.gitblit.wicket.WicketUtils;
/**
 * A re-usable textfield option panel.
 *
 * title
 *     description
 *     [textfield]
 *
 * @author James Moger
 *
 */
public class TextOption extends BasePanel {
    private static final long serialVersionUID = 1L;
    public TextOption(String wicketId, String title, String description, IModel<String> model) {
        this(wicketId, title, description, null, model);
    }
    public TextOption(String wicketId, String title, String description, String css, IModel<String> model) {
        super(wicketId);
        add(new Label("name", title));
        add(new Label("description", description).setVisible(!StringUtils.isEmpty(description)));
        TextField<String> tf = new TextField<String>("text", model);
        if (!StringUtils.isEmpty(css)) {
            WicketUtils.setCssClass(tf, css);
        }
        add(tf);
    }
}