From 00afd77a2182ece3d9522d41b86b4ddd7e132288 Mon Sep 17 00:00:00 2001
From: James Moger <james.moger@gitblit.com>
Date: Thu, 19 May 2011 17:13:50 -0400
Subject: [PATCH] Owner editing. Frozen status. Grouped repositories. Documentation.

---
 src/com/gitblit/utils/StringUtils.java               |    7 
 docs/architecture.png                                |    0 
 src/com/gitblit/wicket/pages/RepositoriesPage.java   |  155 ++++++++++++----
 src/com/gitblit/wicket/pages/EditRepositoryPage.java |   66 ++++++
 src/com/gitblit/wicket/RepositoryPage.java           |   19 -
 src/com/gitblit/BuildSite.java                       |    2 
 src/com/gitblit/wicket/models/RepositoryModel.java   |    8 
 distrib/gitblit.properties                           |   10 +
 docs/01_eclipse.mkd                                  |   17 +
 docs/architecture.odg                                |    0 
 src/com/gitblit/wicket/resources/gitblit.css         |   15 +
 docs/00_overview.mkd                                 |   37 ++-
 src/com/gitblit/GitBlit.java                         |   32 ++
 src/com/gitblit/wicket/GitBlitWebApp.properties      |    4 
 src/com/gitblit/wicket/resources/welcome.mkd         |    4 
 src/com/gitblit/GitBlitServlet.java                  |    6 
 src/com/gitblit/wicket/resources/cold_16x16.png      |    0 
 docs/01_configuration.mkd                            |   24 +-
 docs/sslverify2.png                                  |    0 
 src/com/gitblit/wicket/pages/RepositoriesPage.html   |   70 +++++--
 docs/sslverify.png                                   |    0 
 src/com/gitblit/wicket/pages/EditRepositoryPage.html |    5 
 src/com/gitblit/Constants.java                       |    2 
 src/com/gitblit/wicket/LoginPage.html                |    4 
 24 files changed, 363 insertions(+), 124 deletions(-)

diff --git a/distrib/gitblit.properties b/distrib/gitblit.properties
index e13b60c..de243d2 100644
--- a/distrib/gitblit.properties
+++ b/distrib/gitblit.properties
@@ -66,6 +66,16 @@
 web.datestampShortFormat = yyyy-MM-dd
 web.datetimestampLongFormat = EEEE, MMMM d, yyyy h:mm a z
 
+# Choose how to present the repositories list.
+# grouped = group nested/subfolder repositories together (no sorting)
+# flat = flat list of repositories (sorting allowed)
+web.repositoryListType = flat
+
+# If using a grouped repository list and there are repositories at the
+# root level of your repositories folder, you may specify the displayed
+# group name with this setting.  This value is only used for web presentation.
+web.repositoryRootGroupName = main
+
 # Choose the diff presentation style: gitblt, gitweb, or plain
 web.diffStyle = gitblit
 
diff --git a/docs/00_overview.mkd b/docs/00_overview.mkd
index 63898f6..7e286c5 100644
--- a/docs/00_overview.mkd
+++ b/docs/00_overview.mkd
@@ -1,6 +1,6 @@
 ## Overview
 Git:Blit is an open-source, integrated pure-Java stack for managing, viewing, and serving [Git](http://git-scm.com) repositories.
-Its designed primarily as a tool for small workgroups who want to host Git repositories on a Windows machine.
+Its designed primarily as a tool for small workgroups who want to host [Git](http://git-scm.com) repositories on a Windows machine.
 
 Of course, since its pure-Java it should run with any JVM on any platform, but there are already [many compelling Git solutions](https://git.wiki.kernel.org/index.php/InterfacesFrontendsAndTools) for non-Windows environments.
  
@@ -10,46 +10,53 @@
 
 ### Features
 - Out-of-the-box integrated stack requiring minimal configuration
-- JGit SmartHTTP Servlet
-- Web and Git Servlet authentication
+- JGit SmartHTTP servlet
+- Browser and git client authentication
 - Four repository access control configurations
     - *Anonymous View, Clone & Push*
     - *Authenticated Push*
     - *Authenticated Clone & Push*
     - *Authenticated View, Clone & Push*    
-- Gitweb inspired UI (mostly plain html)
-- Repository administration through web UI
-- User administration through web UI
+- Repositories may also be frozen (deny push) temporarily or permanently
+- Gitweb inspired UI
+- Administrators may create, edit, rename, or delete repositories through the web UI
+- Administrators may create, edit, rename, or delete users through the web UI
+- Repository Owners may edit repositories through the web UI
 - Automatically generates a self-signed certificate for https communications
 - Dates can optionally be displayed using browser's reported timezone
 - Author and Committer email address display can be controlled
 - Syntax highlighting
 - Customizable regular expression handling for commit messages
+- Single text file for server configuration
+- Single text file for users configuration
 - Simple repository stats
-- Simple text file for server configuration
-- Simple text file for users configuration
-- Optional integrated Ticgit
-- Optional integrated Markdown
+- Optional read-only Docs page which enumerates all Markdown files within a repository
+- Optional read-only Ticgit Ticket pages *(based on last MIT release bf57b032 2009-01-27)*
 
 ### Limitations
 - HTTP/HTTPS are the only supported protocols
 - Access controls are not path-based, they are repository-based
-- Only admin users can create repositories
+- Only Administrators can create, rename or delete repositories
 - Git:Blit is a full-stack solution, its not just a webapp so at this time there is no WAR build
 
 ### Todo List
 - Review spots where Git:Blit can cache data instead of abusing the disk
+- Unit testing
 - Ticgit activity/timeline
 - Ticgit query feature with paging support
 - Ticgit ticket change history
 - Implement Markdown editing
 - View images on Blob page
-- View other binary files Blob page
+- View other binary files on Blob page
 
 ### License
 TBD
 
-### Architecture
+### Inspirations
+- [Gitweb](http://www.git-scm.com)
+- [Fossil](http://www.fossil-scm.org) 
+
+## Architecture
 
 ![block diagram](architecture.png "Git Blit Architecture")
 
@@ -73,8 +80,8 @@
 - [JCommander](http://jcommander.org)
 - [BouncyCastle](http://www.bouncycastle.org)
 
-### Building
-Eclipse is recommended for development as the project settings are preconfigured.
+## Building
+[Eclipse](http://eclipse.org) is recommended for development as the project settings are preconfigured.
 
 1. Clone the git repository from here.
 2. Import the gitblit project into your Eclipse workspace.<br/>
diff --git a/docs/01_configuration.mkd b/docs/01_configuration.mkd
index 9e8a9f0..415ad47 100644
--- a/docs/01_configuration.mkd
+++ b/docs/01_configuration.mkd
@@ -6,36 +6,39 @@
 Open `gitblit.properties` in your favorite text editor and make sure to review and set:
     - *git.repositoryFolder*
     - *server.tempFolder*
-    - *server.httpBindInterface* and *server.httpsBindInterface*
+    - *server.httpBindInterface* and *server.httpsBindInterface*<br/>
+**NOTE:** Consider using **https** exclusively because passwords for authentication are transmitted as clear text!     
     - *server.storePassword*<br/>
-**NOTE:**<br/>
-Its recommended to use **https** wherever possible instead of http because passwords are transmitted as clear text!     
+**NOTE:** The certificate password AND the keystore password must match!     
 3. Execute `gitblit.cmd` or `java -jar gitblit.jar` from a command-line
 4. Wait a minute or two while all dependencies are downloaded and your self-signed certificate is generated.
 5. Open your browser to <http://localhost> or <https://localhost> depending on your chosen configuration.
 6. Click the *Login* link and enter the default administrator credentials: **admin / admin**<br/>
-**NOTE:**<br/>
-Make sure to change the administrator username and/or password!! 
+**NOTE:** Make sure to change the administrator username and/or password!! 
 
 ### Administering Repositories
-Repositories can be created, edited, and deleted through the web UI.  They may also be created, edited, and deleted from the command-line using real Git or your favorite file manager and text editor.
+Repositories can be created, edited, renamed, and deleted through the web UI.  They may also be created, edited, and deleted from the command-line using real [Git](http://git-scm.com) or your favorite file manager and text editor.
 
 All repository settings are stored within the repository `.git/config` file under the *gitblit* section.
 
     [gitblit]
 	    description = master repository
-	    owner = Joe Owner
+	    owner = james
 	    useTickets = false
 	    useDocs = true
 	    showRemoteBranches = false
 	    accessRestriction = clone
+	    isFrozen = false
 	    
 #### Repository Names
 Repository names must be unique and are case-insensitive.  The name must be composed of letters, digits, or `/ _ - .`<br/>
 Whitespace is illegal.
 
+#### Repository Owner
+The *Repository Owner* has the special permission of being able to edit a repository through the web UI.  The Repository Owner is not permitted to rename the repository, delete the repository, or reassign ownership to another user.
+
 ### Administering Users
-In contrast, all users are stored in the `users.properties` file or in the file your specified in `gitblit.properties`.<br/>
+All users are stored in the `users.properties` file or in the file you specified in `gitblit.properties`.<br/>
 The format of `users.properties` follows Jetty's convention for HashRealms:
 
     username,password,role1,role2,role3...
@@ -48,11 +51,12 @@
 User passwords are CASE-SENSITIVE and may be *plain*, *md5*, or *crypt* formatted (see `gitblit.properties` -> *realm.passwordStorage*).
 
 #### User Roles
-There is only one actual *role* in Git:Blit and that is *#admin* which grants administrative powers to that user.  Administrators automatically have access to all repositories.  All other *roles* are actually repository names.  If a repository is access-restricted, the user must have the repository's name within his/her roles to bypass the access restriction.  This is how users are granted access to a restricted repository.
+There is only one actual *role* in Git:Blit and that is *#admin* which grants administrative powers to that user.  Administrators automatically have access to all repositories.  All other *roles* are repository names.  If a repository is access-restricted, the user must have the repository's name within his/her roles to bypass the access restriction.  This is how users are granted access to a restricted repository.
 
 ### Creating your own Self-Signed Certificate
 
-Review the contents of the `makekeystore.cmd` or `makekeystore_jdk.cmd`script and execute it.  Voila.
+Review the contents of the `makekeystore.cmd` or `makekeystore_jdk.cmd` script and execute it.<br/>
+**NOTE:** The certificate password AND the keystore password must match!
 
 ### Running as a Service
 Review the contents of the `installService.cmd` or `installService64.cmd`, as appropriate for your JVM.<br/>
diff --git a/docs/01_eclipse.mkd b/docs/01_eclipse.mkd
index 93fd531..c04834c 100644
--- a/docs/01_eclipse.mkd
+++ b/docs/01_eclipse.mkd
@@ -1,5 +1,18 @@
 ## Eclipse Tips
 
-verifySsl
+### Do Not Verify Self-Signed Certificates
+If you are using a self-signed certificate, like the one that is automatically generated by Git:Blit, you have to tell Eclipse/EGit to ignore certificate verification errors.
 
-how to push new unshared project to new repository 
\ No newline at end of file
+![sslverify](sslverify.png "http.sslVerify setting")
+
+![sslverify2](sslverify2.png "Adding http.sslVerify setting")    
+
+### Pushing a New Project to a New Git:Blit Repository
+1. Project Root->Team->Share->Git
+Create a Git repository inside the project
+
+### Pushing a Git-Controlled Project to another Git:Blit Repository
+1. Project Root->Team->Remote->Push
+2. Enter the URL information of the repository
+3. In the Refspec dialog click the buttons named  "All all branches spec" and "All all tags spec"
+
diff --git a/docs/architecture.odg b/docs/architecture.odg
index fc4ea7b..c2fc25c 100644
--- a/docs/architecture.odg
+++ b/docs/architecture.odg
Binary files differ
diff --git a/docs/architecture.png b/docs/architecture.png
index d2f3e0c..0dd7ddc 100644
--- a/docs/architecture.png
+++ b/docs/architecture.png
Binary files differ
diff --git a/docs/sslverify.png b/docs/sslverify.png
new file mode 100644
index 0000000..e987b9f
--- /dev/null
+++ b/docs/sslverify.png
Binary files differ
diff --git a/docs/sslverify2.png b/docs/sslverify2.png
new file mode 100644
index 0000000..94db977
--- /dev/null
+++ b/docs/sslverify2.png
Binary files differ
diff --git a/src/com/gitblit/BuildSite.java b/src/com/gitblit/BuildSite.java
index ae85c06..e122bc2 100644
--- a/src/com/gitblit/BuildSite.java
+++ b/src/com/gitblit/BuildSite.java
@@ -56,7 +56,7 @@
 		String html_footer = readContent(new File(params.pageFooter));
 		final String links = sb.toString();
 		final String header = MessageFormat.format(html_header, Constants.FULL_NAME, links);
-		final String date = new SimpleDateFormat("yyyy MMM dd").format(new Date());
+		final String date = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
 		final String footer = MessageFormat.format(html_footer, "generated " + date);
 		for (File file : markdownFiles) {
 			try {
diff --git a/src/com/gitblit/Constants.java b/src/com/gitblit/Constants.java
index 9cf6a54..7e19cef 100644
--- a/src/com/gitblit/Constants.java
+++ b/src/com/gitblit/Constants.java
@@ -4,7 +4,7 @@
 
 	public final static String NAME = "Git:Blit";
 	
-	public final static String FULL_NAME = "Git:Blit - a Pure Java Git Server";
+	public final static String FULL_NAME = "Git:Blit - a Pure Java Git Solution";
 
 	// The build script extracts this exact line so be careful editing it
 	// and only use A-Z a-z 0-9 .-_ in the string. 
diff --git a/src/com/gitblit/GitBlit.java b/src/com/gitblit/GitBlit.java
index 62ff55e..51c3b45 100644
--- a/src/com/gitblit/GitBlit.java
+++ b/src/com/gitblit/GitBlit.java
@@ -4,6 +4,7 @@
 import java.io.IOException;
 import java.text.MessageFormat;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 
 import javax.servlet.ServletContextEvent;
@@ -21,6 +22,7 @@
 
 import com.gitblit.Constants.AccessRestrictionType;
 import com.gitblit.utils.JGitUtils;
+import com.gitblit.utils.StringUtils;
 import com.gitblit.wicket.models.RepositoryModel;
 import com.gitblit.wicket.models.UserModel;
 
@@ -97,7 +99,9 @@
 	}
 	
 	public List<String> getAllUsernames() {
-		return loginService.getAllUsernames();
+		List<String> names = loginService.getAllUsernames();
+		Collections.sort(names);
+		return names;
 	}
 
 	public UserModel getUserModel(String username) {
@@ -169,15 +173,28 @@
 		model.lastChange = JGitUtils.getLastChange(r);
 		StoredConfig config = JGitUtils.readConfig(r);
 		if (config != null) {
-			model.description = config.getString("gitblit", null, "description");
-			model.owner = config.getString("gitblit", null, "owner");
-			model.useTickets = config.getBoolean("gitblit", "useTickets", false);
-			model.useDocs = config.getBoolean("gitblit", "useDocs", false);
-			model.accessRestriction = AccessRestrictionType.fromName(config.getString("gitblit", null, "accessRestriction"));
-			model.showRemoteBranches = config.getBoolean("gitblit", "showRemoteBranches", false);
+			model.description = getConfig(config, "description", "");
+			model.owner = getConfig(config, "owner", "");
+			model.useTickets = getConfig(config, "useTickets", false);
+			model.useDocs = getConfig(config, "useDocs", false);
+			model.accessRestriction = AccessRestrictionType.fromName(getConfig(config, "accessRestriction", null));
+			model.showRemoteBranches = getConfig(config, "showRemoteBranches", false);
+			model.isFrozen = getConfig(config, "isFrozen", false);
 		}
 		r.close();
 		return model;
+	}
+	
+	private String getConfig(StoredConfig config, String field, String defaultValue) {
+		String value = config.getString("gitblit", null, field);
+		if (StringUtils.isEmpty(value)) {
+			return defaultValue;
+		}
+		return value;
+	}
+	
+	private boolean getConfig(StoredConfig config, String field, boolean defaultValue) {
+		return config.getBoolean("gitblit", field, defaultValue);
 	}
 
 	public void editRepositoryModel(RepositoryModel repository, boolean isCreate) throws GitBlitException {
@@ -209,6 +226,7 @@
 		config.setBoolean("gitblit", null, "useDocs", repository.useDocs);
 		config.setString("gitblit", null, "accessRestriction", repository.accessRestriction.name());
 		config.setBoolean("gitblit", null, "showRemoteBranches", repository.showRemoteBranches);
+		config.setBoolean("gitblit", null, "isFrozen", repository.isFrozen);
 		try {
 			config.save();
 		} catch (IOException e) {
diff --git a/src/com/gitblit/GitBlitServlet.java b/src/com/gitblit/GitBlitServlet.java
index ffdc1b0..17642c6 100644
--- a/src/com/gitblit/GitBlitServlet.java
+++ b/src/com/gitblit/GitBlitServlet.java
@@ -44,12 +44,12 @@
 			String function = url.substring(forwardSlash + 1);
 			String query = req.getQueryString();
 			RepositoryModel model = GitBlit.self().getRepositoryModel(repository);
-			if (model != null) {
-				if (model.accessRestriction.atLeast(AccessRestrictionType.PUSH)) {
+			if (model != null) {				
+				if (model.isFrozen || model.accessRestriction.atLeast(AccessRestrictionType.PUSH)) {
 					boolean authorizedUser = req.isUserInRole(repository);
 					if (function.startsWith("git-receive-pack") || (query.indexOf("service=git-receive-pack") > -1)) {
 						// Push request
-						if (authorizedUser) {
+						if (!model.isFrozen && authorizedUser) {
 							// clone-restricted or push-authorized
 							super.service(req, rsp);
 							return;
diff --git a/src/com/gitblit/utils/StringUtils.java b/src/com/gitblit/utils/StringUtils.java
index ff47a51..8b7960b 100644
--- a/src/com/gitblit/utils/StringUtils.java
+++ b/src/com/gitblit/utils/StringUtils.java
@@ -107,4 +107,11 @@
 			throw new RuntimeException(t);
 		}
 	}
+	
+	public static String getRootPath(String path) {
+		if (path.indexOf('/') > -1) {
+			return path.substring(0, path.indexOf('/'));
+		}
+		return "";
+	}
 }
diff --git a/src/com/gitblit/wicket/GitBlitWebApp.properties b/src/com/gitblit/wicket/GitBlitWebApp.properties
index 3fe24d0..d07f0bc 100644
--- a/src/com/gitblit/wicket/GitBlitWebApp.properties
+++ b/src/com/gitblit/wicket/GitBlitWebApp.properties
@@ -90,4 +90,6 @@
 gb.useDocsDescription = enumerates Markdown documentation in repository
 gb.showRemoteBranchesDescription = show remote branches
 gb.canAdminDescription = can administer Git:Blit server
-gb.permittedUsers = permitted users
\ No newline at end of file
+gb.permittedUsers = permitted users
+gb.isFrozen = is frozen
+gb.isFrozenDescription = deny push operations
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/LoginPage.html b/src/com/gitblit/wicket/LoginPage.html
index 71421ab..037063c 100644
--- a/src/com/gitblit/wicket/LoginPage.html
+++ b/src/com/gitblit/wicket/LoginPage.html
@@ -25,10 +25,10 @@
 			<form style="text-align:center;" wicket:id="loginForm">
 				<div>
 					<p/>
-					<wicket:message key="gb.username"></wicket:message>
+					<wicket:message key="gb.username"></wicket:message> &nbsp;
 					<input type="text" id="username" wicket:id="username" value=""/>
 					<p/>
-					<wicket:message key="gb.password"></wicket:message>
+					<wicket:message key="gb.password"></wicket:message> &nbsp;
 					<input type="password"  wicket:id="password" value=""/>
 					<p/>
 					<input type="submit" value="Login" wicket:message="value:gb.login" />
diff --git a/src/com/gitblit/wicket/RepositoryPage.java b/src/com/gitblit/wicket/RepositoryPage.java
index 78fd33c..e3ae635 100644
--- a/src/com/gitblit/wicket/RepositoryPage.java
+++ b/src/com/gitblit/wicket/RepositoryPage.java
@@ -36,7 +36,6 @@
 import com.gitblit.wicket.pages.BranchesPage;
 import com.gitblit.wicket.pages.DocsPage;
 import com.gitblit.wicket.pages.LogPage;
-import com.gitblit.wicket.pages.RepositoriesPage;
 import com.gitblit.wicket.pages.SearchPage;
 import com.gitblit.wicket.pages.SummaryPage;
 import com.gitblit.wicket.pages.TagsPage;
@@ -79,10 +78,8 @@
 		}
 
 		Repository r = getRepository();
-		if (r == null) {
-			error(MessageFormat.format("Failed to open repository {0} for {1}!", repositoryName, getPageName()), true);
-		}
-
+		RepositoryModel model = getRepositoryModel();
+		
 		// standard page links
 		add(new BookmarkablePageLink<Void>("summary", SummaryPage.class, WicketUtils.newRepositoryParameter(repositoryName)));
 		add(new BookmarkablePageLink<Void>("log", LogPage.class, WicketUtils.newRepositoryParameter(repositoryName)));
@@ -94,12 +91,12 @@
 		List<String> extraPageLinks = new ArrayList<String>();
 
 		// Conditionally add tickets page
-		if (getRepositoryModel().useTickets && JGitUtils.getTicketsBranch(r) != null) {
+		if (model.useTickets && JGitUtils.getTicketsBranch(r) != null) {
 			extraPageLinks.add("tickets");
 		}
 
 		// Conditionally add docs page
-		if (getRepositoryModel().useDocs) {
+		if (model.useDocs) {
 			extraPageLinks.add("docs");
 		}
 
@@ -150,8 +147,7 @@
 		if (r == null) {
 			Repository r = GitBlit.self().getRepository(repositoryName);
 			if (r == null) {
-				error("Can not load repository " + repositoryName);
-				redirectToInterceptPage(new RepositoriesPage());
+				error("Can not load repository " + repositoryName, true);
 				return null;
 			}
 			this.r = r;
@@ -163,9 +159,8 @@
 		if (m == null) {
 			RepositoryModel model = GitBlit.self().getRepositoryModel(GitBlitWebSession.get().getUser(), repositoryName);
 			if (model == null) {
-				error("Unauthorized access for repository " + repositoryName);
-				redirectToInterceptPage(new RepositoriesPage());
-				return null;				
+				error("Unauthorized access for repository " + repositoryName, true);				
+				return null;
 			}
 			m = model;
 		}
diff --git a/src/com/gitblit/wicket/models/RepositoryModel.java b/src/com/gitblit/wicket/models/RepositoryModel.java
index 43a7ac1..2aabfb1 100644
--- a/src/com/gitblit/wicket/models/RepositoryModel.java
+++ b/src/com/gitblit/wicket/models/RepositoryModel.java
@@ -17,9 +17,14 @@
 	public boolean useTickets;
 	public boolean useDocs;
 	public AccessRestrictionType accessRestriction;
+	public boolean isFrozen;
 
 	public RepositoryModel() {
-
+		this.name = "";
+		this.description = "";
+		this.owner = "";
+		this.lastChange = new Date(0);
+		this.accessRestriction = AccessRestrictionType.NONE;
 	}
 
 	public RepositoryModel(String name, String description, String owner, Date lastchange) {
@@ -27,5 +32,6 @@
 		this.description = description;
 		this.owner = owner;
 		this.lastChange = lastchange;
+		this.accessRestriction = AccessRestrictionType.NONE;
 	}	
 }
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/pages/EditRepositoryPage.html b/src/com/gitblit/wicket/pages/EditRepositoryPage.html
index db5ab22..763d46f 100644
--- a/src/com/gitblit/wicket/pages/EditRepositoryPage.html
+++ b/src/com/gitblit/wicket/pages/EditRepositoryPage.html
@@ -17,13 +17,14 @@
 			<tbody>
 				<tr><th><wicket:message key="gb.name"></wicket:message></th><td class="edit"><input type="text" wicket:id="name" id="name" size="40" tabindex="1" /></td></tr>
 				<tr><th><wicket:message key="gb.description"></wicket:message></th><td class="edit"><input type="text" wicket:id="description" size="40" tabindex="2" /></td></tr>
-				<tr><th><wicket:message key="gb.owner"></wicket:message></th><td class="edit"><input type="text" wicket:id="owner" size="40" tabindex="3" /></td></tr>
+				<tr><th><wicket:message key="gb.owner"></wicket:message></th><td class="edit"><select wicket:id="owner" tabindex="3" /></td></tr>
 				<tr><th><wicket:message key="gb.enableTickets"></wicket:message></th><td class="edit"><input type="checkbox" wicket:id="useTickets" tabindex="4" /> &nbsp;<i><wicket:message key="gb.useTicketsDescription"></wicket:message></i></td></tr>
 				<tr><th><wicket:message key="gb.enableDocs"></wicket:message></th><td class="edit"><input type="checkbox" wicket:id="useDocs" tabindex="5" /> &nbsp;<i><wicket:message key="gb.useDocsDescription"></wicket:message></i></td></tr>
 				<tr><th><wicket:message key="gb.showRemoteBranches"></wicket:message></th><td class="edit"><input type="checkbox" wicket:id="showRemoteBranches" tabindex="6" /> &nbsp;<i><wicket:message key="gb.showRemoteBranchesDescription"></wicket:message></i></td></tr>
 				<tr><th><wicket:message key="gb.accessRestriction"></wicket:message></th><td class="edit"><select wicket:id="accessRestriction" tabindex="7" /></td></tr>				
+				<tr><th><wicket:message key="gb.isFrozen"></wicket:message></th><td class="edit"><input type="checkbox" wicket:id="isFrozen" tabindex="8" /> &nbsp;<i><wicket:message key="gb.isFrozenDescription"></wicket:message></i></td></tr>
 				<tr><th style="vertical-align: top;"><wicket:message key="gb.permittedUsers"></wicket:message></th><td style="padding:2px;"><span wicket:id="users"></span></td></tr>				
-				<tr><th></th><td class="editButton"><input type="submit" value="Save" wicket:message="value:gb.save" tabindex="8" /></td></tr>
+				<tr><th></th><td class="editButton"><input type="submit" value="Save" wicket:message="value:gb.save" tabindex="9" /></td></tr>
 			</tbody>
 		</table>
 	</form>	
diff --git a/src/com/gitblit/wicket/pages/EditRepositoryPage.java b/src/com/gitblit/wicket/pages/EditRepositoryPage.java
index 8820290..56d1d55 100644
--- a/src/com/gitblit/wicket/pages/EditRepositoryPage.java
+++ b/src/com/gitblit/wicket/pages/EditRepositoryPage.java
@@ -3,6 +3,7 @@
 import java.text.MessageFormat;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.Date;
 import java.util.Iterator;
 import java.util.List;
@@ -23,13 +24,14 @@
 import com.gitblit.Constants.AccessRestrictionType;
 import com.gitblit.GitBlit;
 import com.gitblit.GitBlitException;
+import com.gitblit.Keys;
 import com.gitblit.utils.StringUtils;
-import com.gitblit.wicket.AdminPage;
 import com.gitblit.wicket.BasePage;
+import com.gitblit.wicket.GitBlitWebSession;
 import com.gitblit.wicket.WicketUtils;
 import com.gitblit.wicket.models.RepositoryModel;
+import com.gitblit.wicket.models.UserModel;
 
-@AdminPage
 public class EditRepositoryPage extends BasePage {
 
 	private final boolean isCreate;
@@ -51,6 +53,9 @@
 	}
 
 	protected void setupPage(final RepositoryModel repositoryModel) {
+		// ensure this user can create or edit this repository
+		checkPermissions(repositoryModel);
+		
 		List<String> repositoryUsers = new ArrayList<String>();
 		if (isCreate) {
 			super.setupPage("", getString("gb.newRepository"));
@@ -58,6 +63,7 @@
 			super.setupPage("", getString("gb.edit"));
 			if (repositoryModel.accessRestriction.exceeds(AccessRestrictionType.NONE)) {
 				repositoryUsers.addAll(GitBlit.self().getRepositoryUsers(repositoryModel));
+				Collections.sort(repositoryUsers);
 			}
 		}
 
@@ -99,16 +105,20 @@
 						error("Please select access restriction!");
 						return;
 					}
-					
+
 					// save the repository
 					GitBlit.self().editRepositoryModel(repositoryModel, isCreate);
-					
+
 					// save the repository access list
 					if (repositoryModel.accessRestriction.exceeds(AccessRestrictionType.NONE)) {
 						Iterator<String> users = usersPalette.getSelectedChoices();
 						List<String> repositoryUsers = new ArrayList<String>();
 						while (users.hasNext()) {
 							repositoryUsers.add(users.next());
+						}
+						// ensure the owner is added to the user list
+						if (!repositoryUsers.contains(repositoryModel.owner)) {
+							repositoryUsers.add(repositoryModel.owner);
 						}
 						GitBlit.self().setRepositoryUsers(repositoryModel, repositoryUsers);
 					}
@@ -124,8 +134,9 @@
 		// field names reflective match RepositoryModel fields
 		form.add(new TextField<String>("name").setEnabled(isCreate));
 		form.add(new TextField<String>("description"));
-		form.add(new TextField<String>("owner"));
+		form.add(new DropDownChoice<String>("owner", GitBlit.self().getAllUsernames()).setEnabled(GitBlitWebSession.get().canAdmin()));
 		form.add(new DropDownChoice<AccessRestrictionType>("accessRestriction", Arrays.asList(AccessRestrictionType.values()), new AccessRestrictionRenderer()));
+		form.add(new CheckBox("isFrozen"));
 		form.add(new CheckBox("useTickets"));
 		form.add(new CheckBox("useDocs"));
 		form.add(new CheckBox("showRemoteBranches"));
@@ -133,6 +144,51 @@
 
 		add(form);
 	}
+	
+	/**
+	 * Unfortunately must repeat part of AuthorizaitonStrategy here because that
+	 * mechanism does not take PageParameters into consideration, only page
+	 * instantiation.
+	 * 
+	 * Repository Owners should be able to edit their repository.
+	 */
+	private void checkPermissions(RepositoryModel model) {
+		boolean authenticateAdmin = GitBlit.self().settings().getBoolean(Keys.web.authenticateAdminPages, true);
+		boolean allowAdmin = GitBlit.self().settings().getBoolean(Keys.web.allowAdministration, true);
+
+		GitBlitWebSession session = GitBlitWebSession.get();
+		UserModel user = session.getUser();
+
+		if (allowAdmin) {
+			if (authenticateAdmin) {
+				if (user == null) {
+					// No Login Available
+					error("Administration requires a login", true);
+				}
+				if (isCreate) {
+					// Create Repository
+					if (!user.canAdmin()) {
+						// Only Administrators May Create
+						error("Only an administrator may create a repository", true);
+					}
+				} else {
+					// Edit Repository
+					if (user.canAdmin()) {
+						// Admins can edit everything
+						return;
+					} else {
+						if (!model.owner.equalsIgnoreCase(user.getUsername())) {
+							// User is not an Admin nor Owner
+							error("Only an administrator or the owner may edit a repository", true);
+						}
+					}					
+				}
+			}
+		} else {
+			// No Administration Permitted
+			error("Administration is disabled", true);
+		}
+	}
 
 	private class AccessRestrictionRenderer implements IChoiceRenderer<AccessRestrictionType> {
 
diff --git a/src/com/gitblit/wicket/pages/RepositoriesPage.html b/src/com/gitblit/wicket/pages/RepositoriesPage.html
index 9c27f79..d00c498 100644
--- a/src/com/gitblit/wicket/pages/RepositoriesPage.html
+++ b/src/com/gitblit/wicket/pages/RepositoriesPage.html
@@ -10,30 +10,21 @@
 </wicket:head>
 
 <body>
-<wicket:extend>	
-	<div style="text-align:center;padding-top:5px;" wicket:id="feedback">[Feedback Panel]</div>
+<wicket:extend>
+	<!-- Filler div -->
+	<div style="padding-top:18px;"></div>
+		
+	<div style="text-align:center;padding-bottom:5px;" wicket:id="feedback">[Feedback Panel]</div>
 	
-	<div class="markdown" style="padding-top:5px;" wicket:id="repositoriesMessage">[repositories message]</div>
+	<div class="markdown" style="margin-top:-0.5em;padding-bottom:5px;" wicket:id="repositoriesMessage">[repositories message]</div>
 	
-	<div style="padding-top:5px;" wicket:id="adminPanel">[admin links]</div>
+	<div wicket:id="adminPanel">[admin links]</div>
 		
 	<table class="repositories">
-		<tr>
-			<th wicket:id="orderByRepository"><wicket:message key="gb.repository">Repository</wicket:message></th>
-			<th wicket:id="orderByDescription"><wicket:message key="gb.description">Description</wicket:message></th>
-			<th wicket:id="orderByOwner"><wicket:message key="gb.owner">Owner</wicket:message></th>
-			<th></th>
-			<th wicket:id="orderByDate"><wicket:message key="gb.lastChange">Last Change</wicket:message></th>
-			<th></th>
-		</tr>
-		<tbody>
-       		<tr wicket:id="repository">
-         		<td><div class="list" wicket:id="repositoryName">[repository name]</div></td>
-         		<td><div class="list" wicket:id="repositoryDescription">[repository description]</div></td>
-         		<td class="author"><span wicket:id="repositoryOwner">[repository owner]</span></td>
-         		<td class="icon"><img wicket:id="ticketsIcon" /><img wicket:id="docsIcon" /><img wicket:id="accessRestrictionIcon" /></td>
-         		<td><span wicket:id="repositoryLastChange">[last change]</span></td>
-         		<td class="rightAlign"><span wicket:id="repositoryLinks"></span></td>
+		<span wicket:id="headerContent"></span>
+		<tbody>		
+       		<tr wicket:id="row">
+       			<span wicket:id="rowContent"></span>
        		</tr>
     	</tbody>
 	</table>
@@ -48,6 +39,45 @@
 	<wicket:fragment wicket:id="repositoryAdminLinks">
 		<span class="link"><a wicket:id="editRepository"><wicket:message key="gb.edit">[edit]</wicket:message></a> | <a wicket:id="renameRepository"><wicket:message key="gb.rename">[rename]</wicket:message></a> | <a wicket:id="deleteRepository"><wicket:message key="gb.delete">[delete]</wicket:message></a></span>
 	</wicket:fragment>
+
+	<wicket:fragment wicket:id="repositoryOwnerLinks">
+		<span class="link"><a wicket:id="editRepository"><wicket:message key="gb.edit">[edit]</wicket:message></a></span>
+	</wicket:fragment>
+
+	<wicket:fragment wicket:id="flatHeader">
+		<tr>
+			<th wicket:id="orderByRepository"><wicket:message key="gb.repository">Repository</wicket:message></th>
+			<th wicket:id="orderByDescription"><wicket:message key="gb.description">Description</wicket:message></th>
+			<th wicket:id="orderByOwner"><wicket:message key="gb.owner">Owner</wicket:message></th>
+			<th></th>
+			<th wicket:id="orderByDate"><wicket:message key="gb.lastChange">Last Change</wicket:message></th>
+			<th></th>
+		</tr>
+	</wicket:fragment>
+	
+	<wicket:fragment wicket:id="groupHeader">
+		<tr>
+			<th><wicket:message key="gb.repository">Repository</wicket:message></th>
+			<th><wicket:message key="gb.description">Description</wicket:message></th>
+			<th><wicket:message key="gb.owner">Owner</wicket:message></th>
+			<th></th>
+			<th><wicket:message key="gb.lastChange">Last Change</wicket:message></th>
+			<th></th>
+		</tr>
+	</wicket:fragment>
+	
+	<wicket:fragment wicket:id="groupRow">
+        <td colspan="6"><span wicket:id="groupName">[group name]</span></td>
+	</wicket:fragment>
+		
+	<wicket:fragment wicket:id="repositoryRow">
+        <td><div class="list" wicket:id="repositoryName">[repository name]</div></td>
+        <td><div class="list" wicket:id="repositoryDescription">[repository description]</div></td>
+        <td class="author"><span wicket:id="repositoryOwner">[repository owner]</span></td>
+        <td style="text-align: right;padding-right:10px;"><img class="inlineIcon" wicket:id="ticketsIcon" /><img class="inlineIcon" wicket:id="docsIcon" /><img class="inlineIcon" wicket:id="frozenIcon" /><img class="inlineIcon" wicket:id="accessRestrictionIcon" /></td>
+        <td><span wicket:id="repositoryLastChange">[last change]</span></td>
+        <td class="rightAlign"><span wicket:id="repositoryLinks"></span></td>
+	</wicket:fragment>
 	
 </wicket:extend>
 </body>
diff --git a/src/com/gitblit/wicket/pages/RepositoriesPage.java b/src/com/gitblit/wicket/pages/RepositoriesPage.java
index 53e3c2f..14a5426 100644
--- a/src/com/gitblit/wicket/pages/RepositoriesPage.java
+++ b/src/com/gitblit/wicket/pages/RepositoriesPage.java
@@ -4,8 +4,11 @@
 import java.io.FileReader;
 import java.io.InputStream;
 import java.io.InputStreamReader;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
+import java.util.Date;
+import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
@@ -20,6 +23,8 @@
 import org.apache.wicket.markup.html.panel.Fragment;
 import org.apache.wicket.markup.repeater.Item;
 import org.apache.wicket.markup.repeater.data.DataView;
+import org.apache.wicket.markup.repeater.data.IDataProvider;
+import org.apache.wicket.markup.repeater.data.ListDataProvider;
 import org.apache.wicket.model.IModel;
 import org.apache.wicket.model.Model;
 import org.apache.wicket.resource.ContextRelativeResource;
@@ -42,13 +47,22 @@
 	public RepositoriesPage() {
 		super();
 		setupPage("", "");
-
+		
 		final boolean showAdmin;
 		if (GitBlit.self().settings().getBoolean(Keys.web.authenticateAdminPages, true)) {
 			boolean allowAdmin = GitBlit.self().settings().getBoolean(Keys.web.allowAdministration, false);
 			showAdmin = allowAdmin && GitBlitWebSession.get().canAdmin();
+			// authentication requires state and session
+			setStatelessHint(false);
 		} else {
 			showAdmin = GitBlit.self().settings().getBoolean(Keys.web.allowAdministration, false);
+			if (GitBlit.self().settings().getBoolean(Keys.web.authenticateViewPages, false)) {
+				// authentication requires state and session
+				setStatelessHint(false);
+			} else {
+				// no authentication required, no state and no session required
+				setStatelessHint(true);
+			}
 		}
 
 		Fragment adminLinks = new Fragment("adminPanel", "adminLinks", this);
@@ -66,7 +80,7 @@
 
 		// Load the markdown welcome message
 		String messageSource = GitBlit.self().settings().getString(Keys.web.repositoriesMessage, "gitblit");
-		String message = "";
+		String message = "<br/>";
 		if (messageSource.equalsIgnoreCase("gitblit")) {
 			// Read default welcome message
 			try {
@@ -99,70 +113,114 @@
 		add(repositoriesMessage);
 
 		final Map<AccessRestrictionType, String> accessRestrictionTranslations = getAccessRestrictions();
-		UserModel user = GitBlitWebSession.get().getUser();
-		List<RepositoryModel> rows = GitBlit.self().getRepositoryModels(user);
-		DataProvider dp = new DataProvider(rows);
-		DataView<RepositoryModel> dataView = new DataView<RepositoryModel>("repository", dp) {
+		final UserModel user = GitBlitWebSession.get().getUser();
+		List<RepositoryModel> models = GitBlit.self().getRepositoryModels(user);
+		IDataProvider<RepositoryModel> dp;
+		
+		if (GitBlit.self().settings().getString(Keys.web.repositoryListType, "flat").equalsIgnoreCase("grouped")) {
+			Map<String, List<RepositoryModel>> groups = new HashMap<String, List<RepositoryModel>>();
+			for (RepositoryModel model : models) {
+				String rootPath = StringUtils.getRootPath(model.name);
+				if (StringUtils.isEmpty(rootPath)) {
+					rootPath = GitBlit.self().settings().getString(Keys.web.repositoryRootGroupName, " ");
+				}
+				if (!groups.containsKey(rootPath)) {
+					groups.put(rootPath, new ArrayList<RepositoryModel>());
+				}
+				groups.get(rootPath).add(model);
+			}
+			List<String> roots = new ArrayList<String>(groups.keySet());
+			Collections.sort(roots);
+			List<RepositoryModel> groupedModels = new ArrayList<RepositoryModel>();
+			for (String root : roots) {
+				groupedModels.add(new GroupRepositoryModel(root));
+				groupedModels.addAll(groups.get(root));
+			}
+			dp = new ListDataProvider<RepositoryModel>(groupedModels);
+		} else {
+			dp = new DataProvider(models);
+		}
+
+		DataView<RepositoryModel> dataView = new DataView<RepositoryModel>("row", dp) {
 			private static final long serialVersionUID = 1L;
 			int counter = 0;
 
 			public void populateItem(final Item<RepositoryModel> item) {
 				final RepositoryModel entry = item.getModelObject();
+				if (entry instanceof GroupRepositoryModel) {
+					Fragment row = new Fragment("rowContent", "groupRow", this);
+					item.add(row);
+					row.add(new Label("groupName", entry.name));
+					WicketUtils.setCssClass(item, "group");
+					return;
+				}
+				Fragment row = new Fragment("rowContent", "repositoryRow", this);
+				item.add(row);
 				if (entry.hasCommits) {
 					// Existing repository
 					PageParameters pp = WicketUtils.newRepositoryParameter(entry.name);
-					item.add(new LinkPanel("repositoryName", "list", entry.name, SummaryPage.class, pp));
-					item.add(new LinkPanel("repositoryDescription", "list", entry.description, SummaryPage.class, pp));
+					row.add(new LinkPanel("repositoryName", "list", entry.name, SummaryPage.class, pp));
+					row.add(new LinkPanel("repositoryDescription", "list", entry.description, SummaryPage.class, pp));
 				} else {
 					// New repository
-					item.add(new Label("repositoryName", entry.name + "<span class='empty'>(empty)</span>").setEscapeModelStrings(false));
-					item.add(new Label("repositoryDescription", entry.description));
+					row.add(new Label("repositoryName", entry.name + "<span class='empty'>(empty)</span>").setEscapeModelStrings(false));
+					row.add(new Label("repositoryDescription", entry.description));
 				}
 
 				if (entry.useTickets) {
-					item.add(WicketUtils.newImage("ticketsIcon", "bug_16x16.png", getString("gb.tickets")));
+					row.add(WicketUtils.newImage("ticketsIcon", "bug_16x16.png", getString("gb.tickets")));
 				} else {
-					item.add(WicketUtils.newBlankImage("ticketsIcon"));
+					row.add(WicketUtils.newBlankImage("ticketsIcon"));
 				}
 
 				if (entry.useDocs) {
-					item.add(WicketUtils.newImage("docsIcon", "book_16x16.png", getString("gb.docs")));
+					row.add(WicketUtils.newImage("docsIcon", "book_16x16.png", getString("gb.docs")));
 				} else {
-					item.add(WicketUtils.newBlankImage("docsIcon"));
-				}
-				
-				switch (entry.accessRestriction) {
-				case NONE:
-					item.add(WicketUtils.newBlankImage("accessRestrictionIcon"));
-					break;
-				case PUSH:
-					item.add(WicketUtils.newImage("accessRestrictionIcon", "lock_go_16x16.png", accessRestrictionTranslations.get(entry.accessRestriction)));
-					break;
-				case CLONE:
-					item.add(WicketUtils.newImage("accessRestrictionIcon", "lock_pull_16x16.png", accessRestrictionTranslations.get(entry.accessRestriction)));
-					break;
-				case VIEW:
-					item.add(WicketUtils.newImage("accessRestrictionIcon", "shield_16x16.png", accessRestrictionTranslations.get(entry.accessRestriction)));
-					break;
-				default:
-					item.add(WicketUtils.newBlankImage("accessRestrictionIcon"));
+					row.add(WicketUtils.newBlankImage("docsIcon"));
 				}
 
-				item.add(new Label("repositoryOwner", entry.owner));
+				if (entry.isFrozen) {
+					row.add(WicketUtils.newImage("frozenIcon", "cold_16x16.png", getString("gb.isFrozen")));
+				} else {
+					row.add(WicketUtils.newClearPixel("frozenIcon").setVisible(false));
+				}
+				switch (entry.accessRestriction) {
+				case NONE:
+					row.add(WicketUtils.newBlankImage("accessRestrictionIcon"));
+					break;
+				case PUSH:
+					row.add(WicketUtils.newImage("accessRestrictionIcon", "lock_go_16x16.png", accessRestrictionTranslations.get(entry.accessRestriction)));
+					break;
+				case CLONE:
+					row.add(WicketUtils.newImage("accessRestrictionIcon", "lock_pull_16x16.png", accessRestrictionTranslations.get(entry.accessRestriction)));
+					break;
+				case VIEW:
+					row.add(WicketUtils.newImage("accessRestrictionIcon", "shield_16x16.png", accessRestrictionTranslations.get(entry.accessRestriction)));
+					break;
+				default:
+					row.add(WicketUtils.newBlankImage("accessRestrictionIcon"));
+				}
+
+				row.add(new Label("repositoryOwner", entry.owner));
 
 				String lastChange = TimeUtils.timeAgo(entry.lastChange);
 				Label lastChangeLabel = new Label("repositoryLastChange", lastChange);
-				item.add(lastChangeLabel);
+				row.add(lastChangeLabel);
 				WicketUtils.setCssClass(lastChangeLabel, TimeUtils.timeAgoCss(entry.lastChange));
 
+				boolean showOwner = user != null && user.getUsername().equalsIgnoreCase(entry.owner);				
 				if (showAdmin) {
 					Fragment repositoryLinks = new Fragment("repositoryLinks", "repositoryAdminLinks", this);
 					repositoryLinks.add(new BookmarkablePageLink<Void>("editRepository", EditRepositoryPage.class, WicketUtils.newRepositoryParameter(entry.name)));
 					repositoryLinks.add(new BookmarkablePageLink<Void>("renameRepository", EditRepositoryPage.class, WicketUtils.newRepositoryParameter(entry.name)).setEnabled(false));
 					repositoryLinks.add(new BookmarkablePageLink<Void>("deleteRepository", EditRepositoryPage.class, WicketUtils.newRepositoryParameter(entry.name)).setEnabled(false));
-					item.add(repositoryLinks);
+					row.add(repositoryLinks);
+				} else if (showOwner) {
+					Fragment repositoryLinks = new Fragment("repositoryLinks", "repositoryOwnerLinks", this);
+					repositoryLinks.add(new BookmarkablePageLink<Void>("editRepository", EditRepositoryPage.class, WicketUtils.newRepositoryParameter(entry.name)));
+					row.add(repositoryLinks);
 				} else {
-					item.add(new Label("repositoryLinks"));
+					row.add(new Label("repositoryLinks"));
 				}
 				WicketUtils.setAlternatingBackground(item, counter);
 				counter++;
@@ -170,10 +228,20 @@
 		};
 		add(dataView);
 
-		add(newSort("orderByRepository", SortBy.repository, dp, dataView));
-		add(newSort("orderByDescription", SortBy.description, dp, dataView));
-		add(newSort("orderByOwner", SortBy.owner, dp, dataView));
-		add(newSort("orderByDate", SortBy.date, dp, dataView));
+		if (dp instanceof SortableDataProvider<?>) {
+			// add sortable header
+			SortableDataProvider<?> sdp = (SortableDataProvider<?>) dp;
+			Fragment fragment = new Fragment("headerContent", "flatHeader", this);
+			fragment.add(newSort("orderByRepository", SortBy.repository, sdp, dataView));
+			fragment.add(newSort("orderByDescription", SortBy.description, sdp, dataView));
+			fragment.add(newSort("orderByOwner", SortBy.owner, sdp, dataView));
+			fragment.add(newSort("orderByDate", SortBy.date, sdp, dataView));
+			add(fragment);
+		} else {
+			// not sortable
+			Fragment fragment = new Fragment("headerContent", "groupHeader", this);
+			add(fragment);
+		}
 	}
 
 	protected enum SortBy {
@@ -258,4 +326,13 @@
 			return list.subList(first, first + count).iterator();
 		}
 	}
+
+	private class GroupRepositoryModel extends RepositoryModel {
+
+		private static final long serialVersionUID = 1L;
+
+		GroupRepositoryModel(String name) {
+			super(name, "", "", new Date(0));
+		}
+	}
 }
diff --git a/src/com/gitblit/wicket/resources/cold_16x16.png b/src/com/gitblit/wicket/resources/cold_16x16.png
new file mode 100644
index 0000000..79cb756
--- /dev/null
+++ b/src/com/gitblit/wicket/resources/cold_16x16.png
Binary files differ
diff --git a/src/com/gitblit/wicket/resources/gitblit.css b/src/com/gitblit/wicket/resources/gitblit.css
index 2d41872..36afae5 100644
--- a/src/com/gitblit/wicket/resources/gitblit.css
+++ b/src/com/gitblit/wicket/resources/gitblit.css
@@ -47,6 +47,10 @@
 	font-style: italic;
 }
 
+img.inlineIcon {
+	padding-left: 1px;
+	padding-right: 1px;
+}
 
 a {
 	color: #0000cc;
@@ -552,6 +556,17 @@
 tr th.wicket_orderUp a { background-image: url(arrow_up.png); }
 tr th.wicket_orderNone a { background-image: url(arrow_off.png); }
 
+tr.group {
+	background-color: #E66C2C;
+}
+
+tr.group td {
+	font-weight: bold;
+	border-bottom: 1px solid orange;
+	color: white;
+	background-color: #E66C2C;	
+}
+
 tr.light {
 	background-color: #ffffff;
 }
diff --git a/src/com/gitblit/wicket/resources/welcome.mkd b/src/com/gitblit/wicket/resources/welcome.mkd
index a9248ac..769baa4 100644
--- a/src/com/gitblit/wicket/resources/welcome.mkd
+++ b/src/com/gitblit/wicket/resources/welcome.mkd
@@ -1,5 +1,3 @@
 ## Welcome to Git:Blit
 
-A quick and easy way to host or view your own Git repositories.
-
-Built with [JGit](http://eclipse.org/jgit), [Wicket](http://wicket.apache.org), [WicketStuff GoogleCharts](https://github.com/wicketstuff/core/wiki/GoogleCharts), [MarkdownPapers](http://markdown.tautua.org), [Jetty](http://eclipse.org/jetty), [SLF4J](http://www.slf4j.org), [Log4j](http://logging.apache.org/log4j), [google-code-prettify](http://code.google.com/p/google-code-prettify), [JCommander](http://jcommander.org), [BouncyCastle](http://www.bouncycastle.org), [JavaService](http://forge.ow2.org/projects/javaservice), and most icons courtesy of [FatCow Hosting](http://www.fatcow.com/free-icons)
\ No newline at end of file
+A quick and easy way to host or view your own [Git](http://www.git-scm.com) repositories.

--
Gitblit v1.9.1