Browse Source

Owner editing. Frozen status. Grouped repositories. Documentation.

tags/v0.5.0
James Moger 13 years ago
parent
commit
00afd77a21

+ 10
- 0
distrib/gitblit.properties View File

@@ -66,6 +66,16 @@ web.useClientTimezone = false
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

+ 22
- 15
docs/00_overview.mkd View File

@@ -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 @@ Of course, since its pure-Java it should run with any JVM on any platform, but t
### 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 @@ The following dependencies are automatically downloaded from the Apache Maven re
- [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/>

+ 14
- 10
docs/01_configuration.mkd View File

@@ -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 @@ Whitespace is illegal.
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/>

+ 15
- 2
docs/01_eclipse.mkd View File

@@ -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.
![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"
how to push new unshared project to new repository

BIN
docs/architecture.odg View File


BIN
docs/architecture.png View File


BIN
docs/sslverify.png View File


BIN
docs/sslverify2.png View File


+ 1
- 1
src/com/gitblit/BuildSite.java View File

@@ -56,7 +56,7 @@ public class BuildSite {
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 {

+ 1
- 1
src/com/gitblit/Constants.java View File

@@ -4,7 +4,7 @@ public class Constants {
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.

+ 25
- 7
src/com/gitblit/GitBlit.java View File

@@ -4,6 +4,7 @@ import java.io.File;
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 org.slf4j.LoggerFactory;
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 class GitBlit implements ServletContextListener {
}
public List<String> getAllUsernames() {
return loginService.getAllUsernames();
List<String> names = loginService.getAllUsernames();
Collections.sort(names);
return names;
}
public UserModel getUserModel(String username) {
@@ -169,16 +173,29 @@ public class GitBlit implements ServletContextListener {
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 {
Repository r = null;
@@ -209,6 +226,7 @@ public class GitBlit implements ServletContextListener {
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) {

+ 3
- 3
src/com/gitblit/GitBlitServlet.java View File

@@ -44,12 +44,12 @@ public class GitBlitServlet extends GitServlet {
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;

+ 7
- 0
src/com/gitblit/utils/StringUtils.java View File

@@ -107,4 +107,11 @@ public class StringUtils {
throw new RuntimeException(t);
}
}
public static String getRootPath(String path) {
if (path.indexOf('/') > -1) {
return path.substring(0, path.indexOf('/'));
}
return "";
}
}

+ 3
- 1
src/com/gitblit/wicket/GitBlitWebApp.properties View File

@@ -90,4 +90,6 @@ gb.useTicketsDescription = distributed Ticgit issues
gb.useDocsDescription = enumerates Markdown documentation in repository
gb.showRemoteBranchesDescription = show remote branches
gb.canAdminDescription = can administer Git:Blit server
gb.permittedUsers = permitted users
gb.permittedUsers = permitted users
gb.isFrozen = is frozen
gb.isFrozenDescription = deny push operations

+ 2
- 2
src/com/gitblit/wicket/LoginPage.html View File

@@ -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" />

+ 7
- 12
src/com/gitblit/wicket/RepositoryPage.java View File

@@ -36,7 +36,6 @@ import com.gitblit.wicket.models.RepositoryModel;
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 @@ public abstract class RepositoryPage extends BasePage {
}
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 @@ public abstract class RepositoryPage extends BasePage {
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 @@ public abstract class RepositoryPage extends BasePage {
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 @@ public abstract class RepositoryPage extends BasePage {
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;
}

+ 7
- 1
src/com/gitblit/wicket/models/RepositoryModel.java View File

@@ -17,9 +17,14 @@ public class RepositoryModel implements Serializable {
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 @@ public class RepositoryModel implements Serializable {
this.description = description;
this.owner = owner;
this.lastChange = lastchange;
this.accessRestriction = AccessRestrictionType.NONE;
}
}

+ 3
- 2
src/com/gitblit/wicket/pages/EditRepositoryPage.html View File

@@ -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>

+ 61
- 5
src/com/gitblit/wicket/pages/EditRepositoryPage.java View File

@@ -3,6 +3,7 @@ package com.gitblit.wicket.pages;
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 org.apache.wicket.model.util.ListModel;
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 @@ public class EditRepositoryPage extends BasePage {
}
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 @@ public class EditRepositoryPage extends BasePage {
super.setupPage("", getString("gb.edit"));
if (repositoryModel.accessRestriction.exceeds(AccessRestrictionType.NONE)) {
repositoryUsers.addAll(GitBlit.self().getRepositoryUsers(repositoryModel));
Collections.sort(repositoryUsers);
}
}
@@ -99,10 +105,10 @@ public class EditRepositoryPage extends BasePage {
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();
@@ -110,6 +116,10 @@ public class EditRepositoryPage extends BasePage {
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);
}
} catch (GitBlitException e) {
@@ -124,8 +134,9 @@ public class EditRepositoryPage extends BasePage {
// 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 @@ public class EditRepositoryPage extends BasePage {
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> {

+ 50
- 20
src/com/gitblit/wicket/pages/RepositoriesPage.html View File

@@ -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>

+ 105
- 28
src/com/gitblit/wicket/pages/RepositoriesPage.java View File

@@ -4,8 +4,11 @@ import java.io.File;
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.link.BookmarkablePageLink;
import org.apache.wicket.markup.html.panel.Fragment;
import org.apache.wicket.markup.repeater.Item;
import org.apache.wicket.markup.repeater.data.DataView;
import org.apache.wicket.markup.repeater.data.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 class RepositoriesPage extends BasePage {
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 @@ public class RepositoriesPage extends BasePage {
// 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 @@ public class RepositoriesPage extends BasePage {
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 {
row.add(WicketUtils.newBlankImage("docsIcon"));
}
if (entry.isFrozen) {
row.add(WicketUtils.newImage("frozenIcon", "cold_16x16.png", getString("gb.isFrozen")));
} else {
item.add(WicketUtils.newBlankImage("docsIcon"));
row.add(WicketUtils.newClearPixel("frozenIcon").setVisible(false));
}
switch (entry.accessRestriction) {
case NONE:
item.add(WicketUtils.newBlankImage("accessRestrictionIcon"));
row.add(WicketUtils.newBlankImage("accessRestrictionIcon"));
break;
case PUSH:
item.add(WicketUtils.newImage("accessRestrictionIcon", "lock_go_16x16.png", accessRestrictionTranslations.get(entry.accessRestriction)));
row.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)));
row.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)));
row.add(WicketUtils.newImage("accessRestrictionIcon", "shield_16x16.png", accessRestrictionTranslations.get(entry.accessRestriction)));
break;
default:
item.add(WicketUtils.newBlankImage("accessRestrictionIcon"));
row.add(WicketUtils.newBlankImage("accessRestrictionIcon"));
}
item.add(new Label("repositoryOwner", entry.owner));
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 @@ public class RepositoriesPage extends BasePage {
};
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 @@ public class RepositoriesPage extends BasePage {
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));
}
}
}

BIN
src/com/gitblit/wicket/resources/cold_16x16.png View File


+ 15
- 0
src/com/gitblit/wicket/resources/gitblit.css View File

@@ -47,6 +47,10 @@ pre, code, pre.prettyprint, pre.plainprint {
font-style: italic;
}
img.inlineIcon {
padding-left: 1px;
padding-right: 1px;
}
a {
color: #0000cc;
@@ -552,6 +556,17 @@ tr th.wicket_orderDown a {background-image: url(arrow_down.png); }
tr th.wicket_orderUp a { background-image: url(arrow_up.png); }
tr th.wicket_orderNone a { background-image: url(arrow_off.png); }
tr.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;
}

+ 1
- 3
src/com/gitblit/wicket/resources/welcome.mkd View File

@@ -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)
A quick and easy way to host or view your own [Git](http://www.git-scm.com) repositories.

Loading…
Cancel
Save