]> source.dussan.org Git - gitblit.git/commitdiff
Owner editing. Frozen status. Grouped repositories. Documentation.
authorJames Moger <james.moger@gitblit.com>
Thu, 19 May 2011 21:13:50 +0000 (17:13 -0400)
committerJames Moger <james.moger@gitblit.com>
Thu, 19 May 2011 21:13:50 +0000 (17:13 -0400)
24 files changed:
distrib/gitblit.properties
docs/00_overview.mkd
docs/01_configuration.mkd
docs/01_eclipse.mkd
docs/architecture.odg
docs/architecture.png
docs/sslverify.png [new file with mode: 0644]
docs/sslverify2.png [new file with mode: 0644]
src/com/gitblit/BuildSite.java
src/com/gitblit/Constants.java
src/com/gitblit/GitBlit.java
src/com/gitblit/GitBlitServlet.java
src/com/gitblit/utils/StringUtils.java
src/com/gitblit/wicket/GitBlitWebApp.properties
src/com/gitblit/wicket/LoginPage.html
src/com/gitblit/wicket/RepositoryPage.java
src/com/gitblit/wicket/models/RepositoryModel.java
src/com/gitblit/wicket/pages/EditRepositoryPage.html
src/com/gitblit/wicket/pages/EditRepositoryPage.java
src/com/gitblit/wicket/pages/RepositoriesPage.html
src/com/gitblit/wicket/pages/RepositoriesPage.java
src/com/gitblit/wicket/resources/cold_16x16.png [new file with mode: 0644]
src/com/gitblit/wicket/resources/gitblit.css
src/com/gitblit/wicket/resources/welcome.mkd

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