diff --git a/.classpath b/.classpath
index 42cbfbc87..c66b3856a 100644
--- a/.classpath
+++ b/.classpath
@@ -80,6 +80,7 @@
+
diff --git a/build.moxie b/build.moxie
index f21241d1b..fd6471eb6 100644
--- a/build.moxie
+++ b/build.moxie
@@ -181,6 +181,7 @@ dependencies:
- compile 'ro.fortsoft.pf4j:pf4j:0.9.0' :war
- compile 'org.apache.tika:tika-core:1.5' :war
- compile 'org.jsoup:jsoup:1.7.3' :war
+- compile 'javax.activation:javax.activation-api:1.2.0'
- test 'junit:junit:4.12'
# Dependencies for Selenium web page testing
- test 'org.seleniumhq.selenium:selenium-java:${selenium.version}' @jar
diff --git a/gitblit.iml b/gitblit.iml
index 1f4aa2483..0c059a5e1 100644
--- a/gitblit.iml
+++ b/gitblit.iml
@@ -824,6 +824,17 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/distrib/data/defaults.properties b/src/main/distrib/data/defaults.properties
index 51fa1253a..157731c5c 100644
--- a/src/main/distrib/data/defaults.properties
+++ b/src/main/distrib/data/defaults.properties
@@ -2147,3 +2147,17 @@ filestore.storageFolder = ${baseFolder}/lfs
# Common unit suffixes of k, m, or g are supported.
# SINCE 1.7.0
filestore.maxUploadSize = -1
+
+# Specify the behaviour of the Repository groups on the "Repositories"
+# page, specifically whether they can be collapsed and expanded, and
+# their default state on loading the page.
+# Only on repositoryListType grouped
+#
+# Values (case-insensitive):
+# disabled - Repository groups cannot collapsed; maintains behaviour
+# from previous versions of GitBlit.
+# expanded - On loading the page all repository groups are expanded.
+# collapsed - On loading the page all repository groups are collapsed.
+#
+# SINCE 1.9.0
+web.collapsibleRepositoryGroups = disabled
diff --git a/src/main/java/com/gitblit/models/TreeNodeModel.java b/src/main/java/com/gitblit/models/TreeNodeModel.java
new file mode 100644
index 000000000..a69393e25
--- /dev/null
+++ b/src/main/java/com/gitblit/models/TreeNodeModel.java
@@ -0,0 +1,178 @@
+package com.gitblit.models;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import com.gitblit.utils.StringUtils;
+
+public class TreeNodeModel implements Serializable, Comparable {
+
+ private static final long serialVersionUID = 1L;
+ final TreeNodeModel parent;
+ final String name;
+ final List subFolders = new ArrayList<>();
+ final List repositories = new ArrayList<>();
+
+ /**
+ * Create a new tree root
+ */
+ public TreeNodeModel() {
+ this.name = "/";
+ this.parent = null;
+ }
+
+ protected TreeNodeModel(String name, TreeNodeModel parent) {
+ this.name = name;
+ this.parent = parent;
+ }
+
+ public int getDepth() {
+ if(parent == null) {
+ return 0;
+ }else {
+ return parent.getDepth() +1;
+ }
+ }
+
+ /**
+ * Add a new sub folder to the current folder
+ *
+ * @param subFolder the subFolder to create
+ * @return the newly created folder to allow chaining
+ */
+ public TreeNodeModel add(String subFolder) {
+ TreeNodeModel n = new TreeNodeModel(subFolder, this);
+ subFolders.add(n);
+ Collections.sort(subFolders);
+ return n;
+ }
+
+ /**
+ * Add the given repo to the current folder
+ *
+ * @param repo
+ */
+ public void add(RepositoryModel repo) {
+ repositories.add(repo);
+ Collections.sort(repositories);
+ }
+
+ /**
+ * Adds the given repository model within the given path. Creates parent folders if they do not exist
+ *
+ * @param path
+ * @param model
+ */
+ public void add(String path, RepositoryModel model) {
+ TreeNodeModel folder = getSubTreeNode(this, path, true);
+ folder.add(model);
+ }
+
+ @Override
+ public String toString() {
+ String string = name + "\n";
+ for(TreeNodeModel n : subFolders) {
+ string += "f";
+ for(int i = 0; i < n.getDepth(); i++) {
+ string += "-";
+ }
+ string += n.toString();
+ }
+
+ for(RepositoryModel n : repositories) {
+ string += "r";
+ for(int i = 0; i < getDepth()+1; i++) {
+ string += "-";
+ }
+ string += n.toString() + "\n";
+ }
+
+ return string;
+ }
+
+ public boolean containsSubFolder(String path) {
+ return containsSubFolder(this, path);
+ }
+
+ public TreeNodeModel getSubFolder(String path) {
+ return getSubTreeNode(this, path, false);
+ }
+
+ public List getTreeAsListForFrontend(){
+ List l = new ArrayList<>();
+ getTreeAsListForFrontend(l, this);
+ return l;
+ }
+
+ private static void getTreeAsListForFrontend(List list, TreeNodeModel node) {
+ list.add(node);
+ for(TreeNodeModel t : node.subFolders) {
+ getTreeAsListForFrontend(list, t);
+ }
+ for(RepositoryModel r : node.repositories) {
+ list.add(r);
+ }
+ }
+
+ private static TreeNodeModel getSubTreeNode(TreeNodeModel node, String path, boolean create) {
+ if(!StringUtils.isEmpty(path)) {
+ boolean isPathInCurrentHierarchyLevel = path.lastIndexOf('/') < 0;
+ if(isPathInCurrentHierarchyLevel) {
+ for(TreeNodeModel t : node.subFolders) {
+ if(t.name.equals(path) ) {
+ return t;
+ }
+ }
+
+ if(create) {
+ node.add(path);
+ return getSubTreeNode(node, path, true);
+ }
+ }else {
+ //traverse into subFolder
+ String folderInCurrentHierarchyLevel = StringUtils.getFirstPathElement(path);
+
+ for(TreeNodeModel t : node.subFolders) {
+ if(t.name.equals(folderInCurrentHierarchyLevel) ) {
+ String folderInNextHierarchyLevel = path.substring(path.indexOf('/') + 1, path.length());
+ return getSubTreeNode(t, folderInNextHierarchyLevel, create);
+ }
+ }
+
+ if(create) {
+ String folderInNextHierarchyLevel = path.substring(path.indexOf('/') + 1, path.length());
+ return getSubTreeNode(node.add(folderInCurrentHierarchyLevel), folderInNextHierarchyLevel, true);
+ }
+ }
+ }
+
+ return null;
+ }
+
+ private static boolean containsSubFolder(TreeNodeModel node, String path) {
+ return getSubTreeNode(node, path, false) != null;
+ }
+
+ @Override
+ public int compareTo(TreeNodeModel t) {
+ return StringUtils.compareRepositoryNames(name, t.name);
+ }
+
+ public TreeNodeModel getParent() {
+ return parent;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public List getSubFolders() {
+ return subFolders;
+ }
+
+ public List getRepositories() {
+ return repositories;
+ }
+}
diff --git a/src/main/java/com/gitblit/wicket/pages/BasePage.html b/src/main/java/com/gitblit/wicket/pages/BasePage.html
index 4dbc2e574..49810ddc0 100644
--- a/src/main/java/com/gitblit/wicket/pages/BasePage.html
+++ b/src/main/java/com/gitblit/wicket/pages/BasePage.html
@@ -51,7 +51,8 @@
-
+
+