From 0047fbba99b804d268a66ed7504a568596de6168 Mon Sep 17 00:00:00 2001
From: James Moger <james.moger@gitblit.com>
Date: Tue, 03 Jun 2014 10:34:50 -0400
Subject: [PATCH] Simplified repository creation with a NewRepositoryPage

---
 src/main/java/com/gitblit/wicket/pages/NewRepositoryPage.html         |   98 ++++++++
 .gitmodules                                                           |    3 
 src/main/java/com/gitblit/servlet/GitblitContext.java                 |   16 +
 src/main/java/com/gitblit/wicket/panels/FilterableRepositoryList.java |    3 
 src/main/java/com/gitblit/Constants.java                              |    8 
 src/main/java/com/gitblit/wicket/panels/RepositoriesPanel.java        |    5 
 src/main/java/com/gitblit/wicket/GitBlitWebApp.java                   |   10 
 src/main/java/com/gitblit/wicket/pages/NewRepositoryPage.java         |  493 +++++++++++++++++++++++++++++++++++++++++
 src/main/java/com/gitblit/wicket/pages/UserPage.java                  |    4 
 src/main/distrib/data/gitblit.properties                              |    5 
 src/main/distrib/data/gitignore                                       |    1 
 src/main/java/com/gitblit/wicket/GitBlitWebApp.properties             |   17 +
 src/main/java/com/gitblit/wicket/pages/RootPage.java                  |    2 
 releases.moxie                                                        |    2 
 build.xml                                                             |    6 
 src/main/java/com/gitblit/wicket/pages/EditRepositoryPage.java        |   16 
 16 files changed, 674 insertions(+), 15 deletions(-)

diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..01eaa2c
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "src/main/distrib/data/gitignore"]
+	path = src/main/distrib/data/gitignore
+	url = https://github.com/github/gitignore.git
diff --git a/build.xml b/build.xml
index f45c4ca..be6f1dd 100644
--- a/build.xml
+++ b/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>
 	
diff --git a/releases.moxie b/releases.moxie
index 4332238..5aa207f 100644
--- a/releases.moxie
+++ b/releases.moxie
@@ -36,6 +36,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 +56,7 @@
     - { name: 'web.allowDeletingNonEmptyRepositories', defaultValue: 'true' }
     - { name: 'mail.starttls', defaultValue: 'false' }
     - { name: 'execution.defaultThreadPoolSize', defaultValue: '1' }
+    - { name: 'git.gitignoreFolder', defaultValue: '${baseFolder}/gitignore' }
 }
 
 #
diff --git a/src/main/distrib/data/gitblit.properties b/src/main/distrib/data/gitblit.properties
index 6f55a3e..d5623cd 100644
--- a/src/main/distrib/data/gitblit.properties
+++ b/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!
diff --git a/src/main/distrib/data/gitignore b/src/main/distrib/data/gitignore
new file mode 160000
index 0000000..097db81
--- /dev/null
+++ b/src/main/distrib/data/gitignore
@@ -0,0 +1 @@
+Subproject commit 097db81c08b138dea7cb031eb18eeb16afe44bdf
diff --git a/src/main/java/com/gitblit/Constants.java b/src/main/java/com/gitblit/Constants.java
index 95eb944..3e30753 100644
--- a/src/main/java/com/gitblit/Constants.java
+++ b/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) {
diff --git a/src/main/java/com/gitblit/servlet/GitblitContext.java b/src/main/java/com/gitblit/servlet/GitblitContext.java
index 110e553..50f22d5 100644
--- a/src/main/java/com/gitblit/servlet/GitblitContext.java
+++ b/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);
 
diff --git a/src/main/java/com/gitblit/wicket/GitBlitWebApp.java b/src/main/java/com/gitblit/wicket/GitBlitWebApp.java
index 7291d03..f63ff3d 100644
--- a/src/main/java/com/gitblit/wicket/GitBlitWebApp.java
+++ b/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)
 	 */
diff --git a/src/main/java/com/gitblit/wicket/GitBlitWebApp.properties b/src/main/java/com/gitblit/wicket/GitBlitWebApp.properties
index 12430ad..ac58955 100644
--- a/src/main/java/com/gitblit/wicket/GitBlitWebApp.properties
+++ b/src/main/java/com/gitblit/wicket/GitBlitWebApp.properties
@@ -685,4 +685,19 @@
 gb.administration = administration
 gb.plugins = plugins
 gb.extensions = extensions
-
+gb.anonymous = Anonymous
+gb.anonymousRepoDescription = Anyone can see, clone, and push to this repository.
+gb.public = Public
+gb.publicRepoDescription = Anyone can see and clone this repository. You choose who can push.
+gb.protected = Protected
+gb.protectedRepoDescription = Anyone can see this repository. You choose who can clone and push.
+gb.private = Private
+gb.privateRepoDescription = 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.initWithGitflow = Include a .gitflow file
+gb.initWithGitflowDescription = This will generate a config file which guides Git clients in setting up Gitflow branches.
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/pages/EditRepositoryPage.java b/src/main/java/com/gitblit/wicket/pages/EditRepositoryPage.java
index 412c0ec..c18cd51 100644
--- a/src/main/java/com/gitblit/wicket/pages/EditRepositoryPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/EditRepositoryPage.java
@@ -429,11 +429,7 @@
 					return;
 				}
 				setRedirect(false);
-				if (isCreate) {
-					setResponsePage(RepositoriesPage.class);
-				} else {
-					setResponsePage(SummaryPage.class, WicketUtils.newRepositoryParameter(repositoryModel.name));
-				}
+				setResponsePage(SummaryPage.class, WicketUtils.newRepositoryParameter(repositoryModel.name));
 			}
 		};
 
@@ -632,7 +628,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));
 					}
diff --git a/src/main/java/com/gitblit/wicket/pages/NewRepositoryPage.html b/src/main/java/com/gitblit/wicket/pages/NewRepositoryPage.html
new file mode 100644
index 0000000..e9f1202
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/NewRepositoryPage.html
@@ -0,0 +1,98 @@
+<!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">
+
+		<table class="plain">
+			<tbody class="settings">
+				<tr>
+					<th><wicket:message key="gb.project"></wicket:message></th>
+					<td></td>
+					<th><wicket:message key="gb.name"></wicket:message></th>
+				</tr>
+				<tr>
+					<td><select class="span2" wicket:id="projectPath" /></td>
+					<td style="font-size:24px;color:#ccc;">/</td>
+					<td class="edit"><input class="span3" 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="span6" type="text" wicket:id="description" />
+		</div>
+		
+		<hr/>
+		
+		<span wicket:id="permissionsGroup">
+			<div wicket:id="permissions">
+				<div style="display: inline-block;vertical-align: top;">
+					<input type="radio" wicket:id="radio" />
+					<img wicket:id="image"></img> 
+				</div>
+				<div style="display: inline-block;vertical-align: top;">
+					<b><span wicket:id="name"></span></b><br/>
+					<span wicket:id="description"></span>
+				</div>
+			</div>
+		</span>
+		
+		<hr/>
+		
+		<h4><wicket:message key="gb.initialCommit"></wicket:message></h4>
+		<div>
+			<p><wicket:message key="gb.initialCommitDescription"></wicket:message></p>
+		</div>
+		
+		<div style="clear:both;padding-top:10px;">
+			<div style="display: inline-block;vertical-align: top;">
+				<input type="checkbox" wicket:id="addReadme" />
+			</div>
+			<div style="display: inline-block;">
+				<b><wicket:message key="gb.initWithReadme"></wicket:message></b><br/>
+				<p><wicket:message key="gb.initWithReadmeDescription"></wicket:message></p>
+			</div>
+		</div>
+		
+		<div style="clear:both;padding-top:10px;display:none;">
+			<!-- future GitFlow -->
+			<div style="display: inline-block;vertical-align: top;">
+				<input type="checkbox" wicket:id="addGitflow" />
+			</div>
+			<div style="display: inline-block;">
+				<b><wicket:message key="gb.initWithGitflow"></wicket:message></b>
+				<p><wicket:message key="gb.initWithGitflowDescription"></wicket:message></p>
+			</div>
+		</div>
+		
+		<div style="clear:both;padding-top:10px;">
+			<div style="display: inline-block;vertical-align: top;">
+				<input type="checkbox" wicket:id="addGitignore" />
+			</div>
+			<div style="display:inline-block;">
+				<b><wicket:message key="gb.initWithGitignore"></wicket:message></b><br/>
+				<p><wicket:message key="gb.initWithGitignoreDescription"></wicket:message></p>
+				<p style="padding-top:5px;"><select class="span2" wicket:id="gitignore" /></p>
+			</div>
+		</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>
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/pages/NewRepositoryPage.java b/src/main/java/com/gitblit/wicket/pages/NewRepositoryPage.java
new file mode 100644
index 0000000..b0cc3e9
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/NewRepositoryPage.java
@@ -0,0 +1,493 @@
+/*
+ * 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.Serializable;
+import java.io.UnsupportedEncodingException;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.ajax.form.AjaxFormComponentUpdatingBehavior;
+import org.apache.wicket.behavior.SimpleAttributeModifier;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.form.Button;
+import org.apache.wicket.markup.html.form.CheckBox;
+import org.apache.wicket.markup.html.form.DropDownChoice;
+import org.apache.wicket.markup.html.form.Form;
+import org.apache.wicket.markup.html.form.Radio;
+import org.apache.wicket.markup.html.form.RadioGroup;
+import org.apache.wicket.markup.html.form.TextField;
+import org.apache.wicket.markup.html.list.ListItem;
+import org.apache.wicket.markup.html.list.ListView;
+import org.apache.wicket.model.CompoundPropertyModel;
+import org.apache.wicket.model.IModel;
+import org.apache.wicket.model.Model;
+import org.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;
+
+public class NewRepositoryPage extends RootSubPage {
+
+	private final RepositoryModel repositoryModel;
+	private RadioGroup<Permission> permissionGroup;
+	private IModel<Boolean> addReadmeModel;
+	private Model<String> gitignoreModel;
+	private IModel<Boolean> addGitflowModel;
+	private IModel<Boolean> addGitignoreModel;
+
+	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() {
+
+				// confirm a repository name was entered
+				if (StringUtils.isEmpty(repositoryModel.name)) {
+					error(getString("gb.pleaseSetRepositoryName"));
+					return;
+				}
+
+				String project = repositoryModel.projectPath;
+				String 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);
+				}
+
+				try {
+					if (fullName.contains("../")) {
+						error(getString("gb.illegalRelativeSlash"));
+						return;
+					}
+					if (fullName.contains("/../")) {
+						error(getString("gb.illegalRelativeSlash"));
+						return;
+					}
+
+					// confirm valid characters in repository name
+					Character c = StringUtils.findInvalidCharacter(fullName);
+					if (c != null) {
+						error(MessageFormat.format(getString("gb.illegalCharacterRepositoryName"),
+								c));
+						return;
+					}
+
+					repositoryModel.name = fullName;
+					repositoryModel.projectPath = null;
+
+					Permission permisison = permissionGroup.getModelObject();
+					repositoryModel.accessRestriction = permisison.type;
+					repositoryModel.authorizationControl = AuthorizationControl.NAMED;
+
+					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("Please select a .gitignore file");
+						}
+					}
+
+					// 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());
+
+					// restore project and name fields on error condition
+					repositoryModel.projectPath = StringUtils.getFirstPathElement(fullName);
+					if (!StringUtils.isEmpty(repositoryModel.projectPath)) {
+						repositoryModel.name = fullName.substring(repositoryModel.projectPath.length() + 1);
+					}
+					return;
+				}
+				setRedirect(true);
+				setResponsePage(SummaryPage.class, WicketUtils.newRepositoryParameter(fullName));
+			}
+		};
+
+		GitBlitWebSession session = GitBlitWebSession.get();
+		UserModel user = session.getUser();
+
+		// build project list for repository destination
+		String defaultProject = null;
+		List<String> projects = new ArrayList<String>();
+
+		if (user.canAdmin()) {
+			String main = app().settings().getString(Keys.web.repositoryRootGroupName, "main");
+			projects.add(main);
+			defaultProject = main;
+		}
+
+		if (user.canCreate()) {
+			projects.add(user.getPersonalPath());
+			if (defaultProject == null) {
+				// only prefer personal namespace if default is not already set
+				defaultProject = user.getPersonalPath();
+			}
+		}
+
+		repositoryModel.projectPath = defaultProject;
+
+		// do not let the browser pre-populate these fields
+		form.add(new SimpleAttributeModifier("autocomplete", "off"));
+
+		form.add(new DropDownChoice<String>("projectPath", projects));
+		form.add(new TextField<String>("name"));
+		form.add(new TextField<String>("description"));
+
+		Permission anonymousPermission = new Permission(getString("gb.anonymous"), getString("gb.anonymousRepoDescription"), "blank.png", AccessRestrictionType.NONE);
+		Permission publicPermission = new Permission(getString("gb.public"), getString("gb.publicRepoDescription"), "lock_go_16x16.png", AccessRestrictionType.PUSH);
+		Permission protectedPermission = new Permission(getString("gb.protected"), getString("gb.protectedRepoDescription"), "lock_pull_16x16.png", AccessRestrictionType.CLONE);
+		Permission privatePermission = new Permission(getString("gb.private"), getString("gb.privateRepoDescription"), "shield_16x16.png", AccessRestrictionType.VIEW);
+
+		List<Permission> permissions = new ArrayList<Permission>();
+		if (app().settings().getBoolean(Keys.git.allowAnonymousPushes, false)) {
+			permissions.add(anonymousPermission);
+		}
+		permissions.add(publicPermission);
+		permissions.add(protectedPermission);
+		permissions.add(privatePermission);
+
+		// determine default permission selection
+		AccessRestrictionType defaultRestriction = AccessRestrictionType.fromName(
+				app().settings().getString(Keys.git.defaultAccessRestriction, AccessRestrictionType.PUSH.name()));
+		if (AccessRestrictionType.NONE == defaultRestriction) {
+			defaultRestriction = AccessRestrictionType.PUSH;
+		}
+
+		Permission defaultPermission = publicPermission;
+		for (Permission permission : permissions) {
+			if (permission.type == defaultRestriction) {
+				defaultPermission = permission;
+			}
+		}
+
+		permissionGroup = new RadioGroup<>("permissionsGroup", new Model<Permission>(defaultPermission));
+		form.add(permissionGroup);
+
+		ListView<Permission> permissionsList = new ListView<Permission>("permissions", permissions) {
+
+			private static final long serialVersionUID = 1L;
+
+			@Override
+			protected void populateItem(ListItem<Permission> item) {
+				Permission p = item.getModelObject();
+				item.add(new Radio<Permission>("radio", item.getModel()));
+				item.add(WicketUtils.newImage("image",  p.image));
+				item.add(new Label("name", p.name));
+				item.add(new Label("description", p.description));
+			}
+		};
+		permissionGroup.add(permissionsList);
+
+		//
+		// initial commit options
+		//
+
+		// add README
+		addReadmeModel = Model.of(false);
+		form.add(new CheckBox("addReadme", 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("");
+		final DropDownChoice<String> gitignoreChoice = new DropDownChoice<String>("gitignore", gitignoreModel, gitignores);
+		gitignoreChoice.setOutputMarkupId(true);
+		form.add(gitignoreChoice.setEnabled(false));
+
+		addGitignoreModel = Model.of(false);
+		final CheckBox gitignoreCheckbox = new CheckBox("addGitignore", addGitignoreModel);
+		form.add(gitignoreCheckbox);
+
+		gitignoreCheckbox.add(new AjaxFormComponentUpdatingBehavior("onchange") {
+
+			private static final long serialVersionUID = 1L;
+
+			@Override
+			protected void onUpdate(AjaxRequestTarget target) {
+				gitignoreChoice.setEnabled(addGitignoreModel.getObject());
+				target.addComponent(gitignoreChoice);
+			}
+		});
+
+		// TODO add .gitflow
+		addGitflowModel = Model.of(false);
+		form.add(new CheckBox("addGitflow", addGitflowModel));
+
+		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;
+	}
+
+	private static class Permission implements Serializable {
+
+		private static final long serialVersionUID = 1L;
+
+		final String name;
+		final String description;
+		final String image;
+		final AccessRestrictionType type;
+
+		Permission(String name, String description, String img, AccessRestrictionType type) {
+			this.name = name;
+			this.description = description;
+			this.image = img;
+			this.type = type;
+		}
+	}
+}
diff --git a/src/main/java/com/gitblit/wicket/pages/RootPage.java b/src/main/java/com/gitblit/wicket/pages/RootPage.java
index a2f3a49..b1c3639 100644
--- a/src/main/java/com/gitblit/wicket/pages/RootPage.java
+++ b/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)));
diff --git a/src/main/java/com/gitblit/wicket/pages/UserPage.java b/src/main/java/com/gitblit/wicket/pages/UserPage.java
index 6cb791e..29b49b3 100644
--- a/src/main/java/com/gitblit/wicket/pages/UserPage.java
+++ b/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));
 		}
diff --git a/src/main/java/com/gitblit/wicket/panels/FilterableRepositoryList.java b/src/main/java/com/gitblit/wicket/panels/FilterableRepositoryList.java
index 45b0bab..4433b04 100644
--- a/src/main/java/com/gitblit/wicket/panels/FilterableRepositoryList.java
+++ b/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));
 		}
diff --git a/src/main/java/com/gitblit/wicket/panels/RepositoriesPanel.java b/src/main/java/com/gitblit/wicket/panels/RepositoriesPanel.java
index dd208e2..8573e1a 100644
--- a/src/main/java/com/gitblit/wicket/panels/RepositoriesPanel.java
+++ b/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

--
Gitblit v1.9.1