]> source.dussan.org Git - gitblit.git/commitdiff
Submodules support
authorJames Moger <james.moger@gitblit.com>
Fri, 10 Aug 2012 21:46:11 +0000 (17:46 -0400)
committerJames Moger <james.moger@gitblit.com>
Fri, 10 Aug 2012 21:46:11 +0000 (17:46 -0400)
21 files changed:
distrib/gitblit.properties
docs/01_features.mkd
docs/04_releases.mkd
src/com/gitblit/GitBlit.java
src/com/gitblit/models/PathModel.java
src/com/gitblit/models/SubmoduleModel.java [new file with mode: 0644]
src/com/gitblit/utils/JGitUtils.java
src/com/gitblit/utils/MarkdownUtils.java
src/com/gitblit/utils/StringUtils.java
src/com/gitblit/wicket/GitBlitWebApp.properties
src/com/gitblit/wicket/GitBlitWebApp_es.properties
src/com/gitblit/wicket/GitBlitWebApp_ja.properties
src/com/gitblit/wicket/GitBlitWebApp_pl.properties
src/com/gitblit/wicket/pages/CommitDiffPage.java
src/com/gitblit/wicket/pages/CommitPage.java
src/com/gitblit/wicket/pages/MarkdownPage.java
src/com/gitblit/wicket/pages/RepositoryPage.java
src/com/gitblit/wicket/pages/SummaryPage.java
src/com/gitblit/wicket/pages/TreePage.html
src/com/gitblit/wicket/pages/TreePage.java
tests/com/gitblit/tests/StringUtilsTest.java

index a5a47b78ccb45b69a5451de34a7c142012944d6f..836ac05c638ae512897e8713d53d9c2f29f94e3b 100644 (file)
@@ -37,6 +37,18 @@ git.searchRecursionDepth = -1
 # SINCE 1.0.1\r
 git.searchExclusions =\r
 \r
+# List of regex url patterns for extracting a repository name when locating\r
+# submodules.\r
+#   e.g. git.submoduleUrlPatterns = .*?://github.com/(.*) will extract\r
+#   *gitblit/gitblit.git* from *git://github.com/gitblit/gitblit.git*\r
+# If no matches are found then the submodule repository name is assumed to be\r
+# whatever trails the last / character. (e.g. gitblit.git).\r
+#\r
+# SPACE-DELIMITED\r
+# CASE-SENSITIVE\r
+# SINCE 1.0.1\r
+git.submoduleUrlPatterns = .*?://github.com/(.*)\r
+\r
 # Allow push/pull over http/https with JGit servlet.\r
 # If you do NOT want to allow Git clients to clone/push to Gitblit set this\r
 # to false.  You might want to do this if you are only using ssh:// or git://.\r
index a4fc802b570b581f8f0770d9616f40e74232d7c2..fb964fa35c59030312f5d4a17e4825e6e814a75e 100644 (file)
@@ -23,6 +23,7 @@
 - LDAP authentication and optional LDAP-controlled Team memberships\r
 - Gravatar integration\r
 - Git-notes display support\r
+- Submodule support\r
 - gh-pages display support (Jekyll is not supported)\r
 - Branch metrics (uses Google Charts)\r
 - HEAD and Branch RSS feeds\r
index e94d8c1585588e2c9c03ed005359e98db4350bc4..ab81cb3e905e5dd32338b81302b277cfa7216fb7 100644 (file)
@@ -11,12 +11,11 @@ If you are updating from an earlier release AND you have indexed branches with t
 \r
 #### fixes\r
 \r
-- Support StartTLS in LdapUserService (Steffen Gebert, issue 122)\r
-- Do not index blobs in submodules (issue 119)\r
+- Do not index submodule links (issue 119)\r
 - Restore original user or team object on failure to update (issue 118)\r
 - Fixes to relative path determination in repository search algorithm for symlinks (issue 116)\r
 - Fix to GitServlet to allow pushing to symlinked repositories (issue 116)\r
-- Repository URL uses `X-Forwarded-Proto` and `X-Forwarded-Port`, if available, for reverse proxy configurations (issue 115)\r
+- Repository URL now uses `X-Forwarded-Proto` and `X-Forwarded-Port`, if available, for reverse proxy configurations (issue 115)\r
 - Output real RAW content, not simulated RAW content (issue 114)\r
 - Fixed Lucene charset encoding bug when reindexing a repository (issue 112)\r
 - Fixed search box linking to Lucene page for nested repository on Tomcat (issue 111)\r
@@ -25,9 +24,17 @@ If you are updating from an earlier release AND you have indexed branches with t
 \r
 #### additions\r
 \r
-- Added a repository setting to control authorization as AUTHENTICATED or NAMED.  \r
-NAMED is the original behavior for authorizing against a list of permitted users or permitted teams.\r
-AUTHENTICATED allows restricted access for any authenticated user.\r
+- Preliminary bare repository submodule support  \r
+    **New:** *git.submoduleUrlPatterns=*\r
+    - *git.submoduleUrlPatterns* is a space-delimited list of regular expressions for extracting a repository name from a submodule url.  \r
+    For example, `git.submoduleUrlPatterns = .*?://github.com/(.*)` would extract *gitblit/gitblit.git* from *git://github.git/gitblit/gitblit.git*  \r
+    **Note:** You may not need this control to work with submodules, but it is there if you do.\r
+    - If there are no matches from *git.submoduleUrlPatterns* then the repository name is assumed to be whatever comes after the last `/` character *(e.g. gitblit.git)*\r
+    - Gitblit will try to locate this repository relative to the current repository *(e.g. myfolder/myrepo.git, myfolder/mysubmodule.git)* and then at the root level *(mysubmodule.git)* if that fails.\r
+    - Submodule references in a working copy will be properly identified as gitlinks, but Gitblit will not traverse into the working copy submodule repository.\r
+- Added a repository setting to control authorization as AUTHENTICATED or NAMED. (issue 117)  \r
+NAMED is the original behavior for authorizing against a list of permitted users or permitted teams.  \r
+AUTHENTICATED allows restricted access for any authenticated user.  This is a looser authorization control.\r
 - Added default authorization control setting (AUTHENTICATED or NAMED)  \r
     **New:** *git.defaultAuthorizationControl=NAMED*  \r
 - Added setting to control how deep Gitblit will recurse into *git.repositoriesFolder* looking for repositories (issue 103)  \r
@@ -36,11 +43,18 @@ AUTHENTICATED allows restricted access for any authenticated user.
     **New:** *git.searchExclusions=*  \r
 - Blob page now supports displaying images (issue 6)\r
 - Non-image binary files can now be downloaded using the RAW link\r
+- Support StartTLS in LdapUserService (Steffen Gebert, issue 122)\r
 \r
 #### changes\r
 \r
+- Line breaks inserted for readability in raw Markdown content display in the event of a parsing/transformation error.  An error message is now displayed prepended to the raw content.\r
+- Improve UTF-8 reading for Markdown files\r
 - Updated Polish translation\r
 \r
+<hr/>\r
+\r
+### Older Releases\r
+\r
 **1.0.0** *released 2012-07-14*\r
 \r
 #### fixes\r
@@ -104,8 +118,6 @@ Make sure to properly set *web.blobEncodings* before starting Gitblit if you are
 \r
 <hr/>\r
 \r
-### Older Releases\r
-\r
 **0.9.3** *released 2012-04-11*\r
 \r
 #### fixes\r
index 26f30f915a1047f599daf9997b014e6f3466016a..f52f229fd5f98e1f922d69baea3e6fdc719ef16b 100644 (file)
@@ -910,6 +910,21 @@ public class GitBlit implements ServletContextListener {
                r.close();\r
                return model;\r
        }\r
+       \r
+       /**\r
+        * Determines if this server has the requested repository.\r
+        * \r
+        * @param name\r
+        * @return true if the repository exists\r
+        */\r
+       public boolean hasRepository(String repositoryName) {\r
+               Repository r = getRepository(repositoryName, false);\r
+               if (r == null) {\r
+                       return false;\r
+               }\r
+               r.close();\r
+               return true;\r
+       }\r
 \r
        /**\r
         * Returns the size in bytes of the repository. Gitblit caches the\r
index 9bb7eb736af7a4d766fc75c451a97b465e264e42..8692359c55a2df0b57fc635b15c62c0874bb1f5e 100644 (file)
@@ -35,14 +35,16 @@ public class PathModel implements Serializable, Comparable<PathModel> {
        public final String path;\r
        public final long size;\r
        public final int mode;\r
+       public final String objectId;\r
        public final String commitId;\r
        public boolean isParentPath;\r
 \r
-       public PathModel(String name, String path, long size, int mode, String commitId) {\r
+       public PathModel(String name, String path, long size, int mode, String objectId, String commitId) {\r
                this.name = name;\r
                this.path = path;\r
                this.size = size;\r
                this.mode = mode;\r
+               this.objectId = objectId;\r
                this.commitId = commitId;\r
        }\r
 \r
@@ -102,9 +104,9 @@ public class PathModel implements Serializable, Comparable<PathModel> {
 \r
                public final ChangeType changeType;\r
 \r
-               public PathChangeModel(String name, String path, long size, int mode, String commitId,\r
-                               ChangeType type) {\r
-                       super(name, path, size, mode, commitId);\r
+               public PathChangeModel(String name, String path, long size, int mode, String objectId,\r
+                               String commitId, ChangeType type) {\r
+                       super(name, path, size, mode, objectId, commitId);\r
                        this.changeType = type;\r
                }\r
 \r
diff --git a/src/com/gitblit/models/SubmoduleModel.java b/src/com/gitblit/models/SubmoduleModel.java
new file mode 100644 (file)
index 0000000..47f84b9
--- /dev/null
@@ -0,0 +1,47 @@
+/*\r
+ * Copyright 2012 gitblit.com.\r
+ *\r
+ * Licensed under the Apache License, Version 2.0 (the "License");\r
+ * you may not use this file except in compliance with the License.\r
+ * You may obtain a copy of the License at\r
+ *\r
+ *     http://www.apache.org/licenses/LICENSE-2.0\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ */\r
+package com.gitblit.models;\r
+\r
+import java.io.Serializable;\r
+\r
+/**\r
+ * SubmoduleModel is a serializable model class that represents a git submodule\r
+ * definition.\r
+ * \r
+ * @author James Moger\r
+ * \r
+ */\r
+public class SubmoduleModel implements Serializable {\r
+\r
+       private static final long serialVersionUID = 1L;\r
+\r
+       public final String name;\r
+       public final String path;\r
+       public final String url;\r
+\r
+       public boolean hasSubmodule;\r
+       public String gitblitPath;\r
+\r
+       public SubmoduleModel(String name, String path, String url) {\r
+               this.name = name;\r
+               this.path = path;\r
+               this.url = url;\r
+       }\r
+       \r
+       public String toString() {\r
+               return path + "=" + url;\r
+       }\r
+}
\ No newline at end of file
index 90e6a76b2edbcc854059f7367ca970d031c3daa0..5eb83edb545bd66c0f77353dcb0b58998e055ca7 100644 (file)
@@ -45,7 +45,9 @@ import org.eclipse.jgit.errors.ConfigInvalidException;
 import org.eclipse.jgit.errors.IncorrectObjectTypeException;\r
 import org.eclipse.jgit.errors.MissingObjectException;\r
 import org.eclipse.jgit.errors.StopWalkException;\r
+import org.eclipse.jgit.lib.BlobBasedConfig;\r
 import org.eclipse.jgit.lib.CommitBuilder;\r
+import org.eclipse.jgit.lib.Config;\r
 import org.eclipse.jgit.lib.Constants;\r
 import org.eclipse.jgit.lib.FileMode;\r
 import org.eclipse.jgit.lib.ObjectId;\r
@@ -87,6 +89,7 @@ import com.gitblit.models.GitNote;
 import com.gitblit.models.PathModel;\r
 import com.gitblit.models.PathModel.PathChangeModel;\r
 import com.gitblit.models.RefModel;\r
+import com.gitblit.models.SubmoduleModel;\r
 \r
 /**\r
  * Collection of static methods for retrieving information from a repository.\r
@@ -732,7 +735,8 @@ public class JGitUtils {
                                tw.addTree(commit.getTree());\r
                                while (tw.next()) {\r
                                        list.add(new PathChangeModel(tw.getPathString(), tw.getPathString(), 0, tw\r
-                                                       .getRawMode(0), commit.getId().getName(), ChangeType.ADD));\r
+                                                       .getRawMode(0), tw.getObjectId(0).getName(), commit.getId().getName(),\r
+                                                       ChangeType.ADD));\r
                                }\r
                                tw.release();\r
                        } else {\r
@@ -745,15 +749,15 @@ public class JGitUtils {
                                for (DiffEntry diff : diffs) {\r
                                        if (diff.getChangeType().equals(ChangeType.DELETE)) {\r
                                                list.add(new PathChangeModel(diff.getOldPath(), diff.getOldPath(), 0, diff\r
-                                                               .getNewMode().getBits(), commit.getId().getName(), diff\r
+                                                               .getNewMode().getBits(), null, commit.getId().getName(), diff\r
                                                                .getChangeType()));\r
                                        } else if (diff.getChangeType().equals(ChangeType.RENAME)) {\r
                                                list.add(new PathChangeModel(diff.getOldPath(), diff.getNewPath(), 0, diff\r
-                                                               .getNewMode().getBits(), commit.getId().getName(), diff\r
+                                                               .getNewMode().getBits(), null, commit.getId().getName(), diff\r
                                                                .getChangeType()));\r
                                        } else {\r
                                                list.add(new PathChangeModel(diff.getNewPath(), diff.getNewPath(), 0, diff\r
-                                                               .getNewMode().getBits(), commit.getId().getName(), diff\r
+                                                               .getNewMode().getBits(), null, commit.getId().getName(), diff\r
                                                                .getChangeType()));\r
                                        }\r
                                }\r
@@ -846,15 +850,16 @@ public class JGitUtils {
                } else {\r
                        name = tw.getPathString().substring(basePath.length() + 1);\r
                }\r
+               ObjectId objectId = tw.getObjectId(0);\r
                try {\r
-                       if (!tw.isSubtree()) {\r
-                               size = tw.getObjectReader().getObjectSize(tw.getObjectId(0), Constants.OBJ_BLOB);\r
+                       if (!tw.isSubtree() && (tw.getFileMode(0) != FileMode.GITLINK)) {\r
+                               size = tw.getObjectReader().getObjectSize(objectId, Constants.OBJ_BLOB);\r
                        }\r
                } catch (Throwable t) {\r
                        error(t, null, "failed to retrieve blob size for " + tw.getPathString());\r
                }\r
                return new PathModel(name, tw.getPathString(), size, tw.getFileMode(0).getBits(),\r
-                               commit.getName());\r
+                               objectId.getName(), commit.getName());\r
        }\r
 \r
        /**\r
@@ -871,13 +876,10 @@ public class JGitUtils {
                } else if (FileMode.EXECUTABLE_FILE.equals(mode)) {\r
                        return "-rwxr-xr-x";\r
                } else if (FileMode.SYMLINK.equals(mode)) {\r
-                       // FIXME symlink permissions\r
                        return "symlink";\r
                } else if (FileMode.GITLINK.equals(mode)) {\r
-                       // FIXME gitlink permissions\r
-                       return "gitlink";\r
+                       return "submodule";\r
                }\r
-               // FIXME missing permissions\r
                return "missing";\r
        }\r
 \r
@@ -1533,6 +1535,62 @@ public class JGitUtils {
                }\r
                return branch;\r
        }\r
+               \r
+       /**\r
+        * Returns the list of submodules for this repository.\r
+        * \r
+        * @param repository\r
+        * @param commit\r
+        * @return list of submodules\r
+        */\r
+       public static List<SubmoduleModel> getSubmodules(Repository repository, String commitId) {\r
+               RevCommit commit = getCommit(repository, commitId);\r
+               return getSubmodules(repository, commit.getTree());\r
+       }\r
+       \r
+       /**\r
+        * Returns the list of submodules for this repository.\r
+        * \r
+        * @param repository\r
+        * @param commit\r
+        * @return list of submodules\r
+        */\r
+       public static List<SubmoduleModel> getSubmodules(Repository repository, RevTree tree) {\r
+               List<SubmoduleModel> list = new ArrayList<SubmoduleModel>();\r
+               byte [] blob = getByteContent(repository, tree, ".gitmodules");\r
+               if (blob == null) {\r
+                       return list;\r
+               }\r
+               try {\r
+                       BlobBasedConfig config = new BlobBasedConfig(repository.getConfig(), blob);\r
+                       for (String module : config.getSubsections("submodule")) {\r
+                               String path = config.getString("submodule", module, "path");\r
+                               String url = config.getString("submodule", module, "url");\r
+                               list.add(new SubmoduleModel(module, path, url));\r
+                       }\r
+               } catch (ConfigInvalidException e) {\r
+                       LOGGER.error("Failed to load .gitmodules file for " + repository.getDirectory(), e);\r
+               }\r
+               return list;\r
+       }\r
+       \r
+       /**\r
+        * Returns the submodule definition for the specified path at the specified\r
+        * commit.  If no module is defined for the path, null is returned.\r
+        * \r
+        * @param repository\r
+        * @param commit\r
+        * @param path\r
+        * @return a submodule definition or null if there is no submodule\r
+        */\r
+       public static SubmoduleModel getSubmoduleModel(Repository repository, String commitId, String path) {\r
+               for (SubmoduleModel model : getSubmodules(repository, commitId)) {\r
+                       if (model.path.equals(path)) {\r
+                               return model;\r
+                       }\r
+               }\r
+               return null;\r
+       }\r
 \r
        /**\r
         * Returns the list of notes entered about the commit from the refs/notes\r
index a3a1859235899d0ab3f876abd6c09b6df7386dff..828225db06b8063a3d1ae6b3180c6e6eee740905 100644 (file)
@@ -20,6 +20,7 @@ import java.io.Reader;
 import java.io.StringReader;\r
 import java.io.StringWriter;\r
 \r
+import org.slf4j.LoggerFactory;\r
 import org.tautua.markdownpapers.Markdown;\r
 import org.tautua.markdownpapers.parser.ParseException;\r
 \r
@@ -44,6 +45,8 @@ public class MarkdownUtils {
                        String html = transformMarkdown(reader);\r
                        reader.close();\r
                        return html;\r
+               } catch (IllegalArgumentException e) {\r
+                       throw new java.text.ParseException(e.getMessage(), 0);\r
                } catch (NullPointerException p) {\r
                        throw new java.text.ParseException("Markdown string is null!", 0);\r
                }\r
@@ -65,6 +68,7 @@ public class MarkdownUtils {
                        md.transform(markdownReader, writer);\r
                        return writer.toString().trim();\r
                } catch (ParseException p) {\r
+                       LoggerFactory.getLogger(MarkdownUtils.class).error("MarkdownPapers failed to parse Markdown!", p);\r
                        throw new java.text.ParseException(p.getMessage(), 0);\r
                } finally {\r
                        try {\r
index 412a920fe54db492124d45300d774eff19ede060..3972f202aeb639cb1a0527792a5b0754287979b8 100644 (file)
@@ -33,6 +33,8 @@ import java.util.Comparator;
 import java.util.LinkedHashSet;\r
 import java.util.List;\r
 import java.util.Set;\r
+import java.util.regex.Matcher;\r
+import java.util.regex.Pattern;\r
 import java.util.regex.PatternSyntaxException;\r
 \r
 /**\r
@@ -599,4 +601,28 @@ public class StringUtils {
         }\r
                return value;\r
        }\r
+       \r
+       /**\r
+        * Attempt to extract a repository name from a given url using regular\r
+        * expressions.  If no match is made, then return whatever trails after\r
+        * the final / character.\r
+        * \r
+        * @param regexUrls\r
+        * @return a repository path\r
+        */\r
+       public static String extractRepositoryPath(String url, String... urlpatterns) {\r
+               for (String urlPattern : urlpatterns) {\r
+                       Pattern p = Pattern.compile(urlPattern);\r
+                       Matcher m = p.matcher(url);\r
+                       while (m.find()) {\r
+                               String repositoryPath = m.group(1);\r
+                               return repositoryPath;\r
+                       }\r
+               }\r
+               // last resort\r
+               if (url.lastIndexOf('/') > -1) {\r
+                       return url.substring(url.lastIndexOf('/') + 1);\r
+               }\r
+               return url;\r
+       }\r
 }
\ No newline at end of file
index bcd63370fcaf6cd13a6b21d2412fca7205df6afd..f9480a8f4fbe1f76c2c0a95cc47d927cd92be614 100644 (file)
@@ -312,4 +312,5 @@ gb.duration.oneYear = 1 year
 gb.duration.years = {0} years\r
 gb.authorizationControl = authorization control\r
 gb.allowAuthenticatedDescription = grant restricted access to all authenticated users\r
-gb.allowNamedDescription = grant restricted access to named users or teams
\ No newline at end of file
+gb.allowNamedDescription = grant restricted access to named users or teams\r
+gb.markdownFailure = Failed to parse Markdown content!
\ No newline at end of file
index fec51b8bf142357177b00a4de226a74fd3a56e17..2140f52544c9f102bcb78a5cbc57fcae556bc0ab 100644 (file)
@@ -312,4 +312,5 @@ gb.duration.oneYear = 1 a\u00F1o
 gb.duration.years = {0} a\u00F1os\r
 gb.authorizationControl = authorization control\r
 gb.allowAuthenticatedDescription = grant restricted access to all authenticated users\r
-gb.allowNamedDescription = grant restricted access to named users or teams
\ No newline at end of file
+gb.allowNamedDescription = grant restricted access to named users or teams\r
+gb.markdownFailure = Failed to parse Markdown content!
\ No newline at end of file
index f0fe3e846ad45a54e2986a80581cd68ea52ecc45..04e2bf3544a5bf41e1597142ce771a2d6b19b543 100755 (executable)
@@ -312,4 +312,5 @@ gb.duration.oneYear = 1 year
 gb.duration.years = {0} years\r
 gb.authorizationControl = authorization control\r
 gb.allowAuthenticatedDescription = grant restricted access to all authenticated users\r
-gb.allowNamedDescription = grant restricted access to named users or teams
\ No newline at end of file
+gb.allowNamedDescription = grant restricted access to named users or teams\r
+gb.markdownFailure = Failed to parse Markdown content!
\ No newline at end of file
index 9486595cf90d0fa3158b4ce3c5b3bcc2ad4c0a0b..7c35dabe131102b74d24662d1d94fe80345e65b0 100644 (file)
@@ -312,4 +312,5 @@ gb.duration.oneYear = rok
 gb.duration.years = {0} lat
 gb.authorizationControl = authorization control
 gb.allowAuthenticatedDescription = grant restricted access to all authenticated users
-gb.allowNamedDescription = grant restricted access to named users or teams
\ No newline at end of file
+gb.allowNamedDescription = grant restricted access to named users or teams
+gb.markdownFailure = Failed to parse Markdown content!
\ No newline at end of file
index cee065bb0fad6f35b13c1f253d57b2b7bf6a3191..dbf981b4e194b5c5372092232a22415630f42380 100644 (file)
@@ -31,6 +31,7 @@ import org.eclipse.jgit.revwalk.RevCommit;
 import com.gitblit.GitBlit;\r
 import com.gitblit.Keys;\r
 import com.gitblit.models.PathModel.PathChangeModel;\r
+import com.gitblit.models.SubmoduleModel;\r
 import com.gitblit.utils.DiffUtils;\r
 import com.gitblit.utils.DiffUtils.DiffOutputType;\r
 import com.gitblit.utils.JGitUtils;\r
@@ -86,26 +87,55 @@ public class CommitDiffPage extends RepositoryPage {
                                setChangeTypeTooltip(changeType, entry.changeType);\r
                                item.add(changeType);\r
 \r
+                               boolean hasSubmodule = false;\r
+                               String submodulePath = null;\r
                                if (entry.isTree()) {\r
+                                       // tree\r
                                        item.add(new LinkPanel("pathName", null, entry.path, TreePage.class,\r
                                                        WicketUtils\r
                                                                        .newPathParameter(repositoryName, entry.commitId, entry.path)));\r
+                               } else if (entry.isSubmodule()) {\r
+                                       // submodule\r
+                                       String submoduleId = entry.objectId;                                            \r
+                                       SubmoduleModel submodule = getSubmodule(entry.path);\r
+                                       submodulePath = submodule.gitblitPath;\r
+                                       hasSubmodule = submodule.hasSubmodule;\r
+                                       \r
+                                       item.add(new LinkPanel("pathName", "list", entry.path + " @ " +\r
+                                                       getShortObjectId(submoduleId), TreePage.class,\r
+                                                       WicketUtils\r
+                                                                       .newPathParameter(submodulePath, submoduleId, "")).setEnabled(hasSubmodule));\r
                                } else {\r
+                                       // blob\r
                                        item.add(new LinkPanel("pathName", "list", entry.path, BlobPage.class,\r
                                                        WicketUtils\r
                                                                        .newPathParameter(repositoryName, entry.commitId, entry.path)));\r
                                }\r
 \r
-                               item.add(new BookmarkablePageLink<Void>("patch", PatchPage.class, WicketUtils\r
-                                               .newPathParameter(repositoryName, entry.commitId, entry.path)));\r
-                               item.add(new BookmarkablePageLink<Void>("view", BlobPage.class, WicketUtils\r
-                                               .newPathParameter(repositoryName, entry.commitId, entry.path)));\r
-                               item.add(new BookmarkablePageLink<Void>("blame", BlamePage.class, WicketUtils\r
-                                               .newPathParameter(repositoryName, entry.commitId, entry.path)));\r
-                               item.add(new BookmarkablePageLink<Void>("history", HistoryPage.class, WicketUtils\r
-                                               .newPathParameter(repositoryName, entry.commitId, entry.path))\r
-                                               .setEnabled(!entry.changeType.equals(ChangeType.ADD)));\r
-\r
+                               // quick links\r
+                               if (entry.isSubmodule()) {\r
+                                       // submodule                                    \r
+                                       item.add(new BookmarkablePageLink<Void>("patch", PatchPage.class, WicketUtils\r
+                                                       .newPathParameter(submodulePath, entry.objectId, entry.path)).setEnabled(false));\r
+                                       item.add(new BookmarkablePageLink<Void>("view", CommitPage.class, WicketUtils\r
+                                                       .newObjectParameter(submodulePath, entry.objectId)).setEnabled(hasSubmodule));\r
+                                       item.add(new BookmarkablePageLink<Void>("blame", BlamePage.class, WicketUtils\r
+                                                       .newPathParameter(submodulePath, entry.objectId, entry.path)).setEnabled(false));\r
+                                       item.add(new BookmarkablePageLink<Void>("history", HistoryPage.class, WicketUtils\r
+                                                       .newPathParameter(submodulePath, entry.objectId, entry.path))\r
+                                                       .setEnabled(hasSubmodule));\r
+                               } else {\r
+                                       // tree or blob\r
+                                       item.add(new BookmarkablePageLink<Void>("patch", PatchPage.class, WicketUtils\r
+                                                       .newPathParameter(repositoryName, entry.commitId, entry.path)));\r
+                                       item.add(new BookmarkablePageLink<Void>("view", BlobPage.class, WicketUtils\r
+                                                       .newPathParameter(repositoryName, entry.commitId, entry.path)));\r
+                                       item.add(new BookmarkablePageLink<Void>("blame", BlamePage.class, WicketUtils\r
+                                                       .newPathParameter(repositoryName, entry.commitId, entry.path)));\r
+                                       item.add(new BookmarkablePageLink<Void>("history", HistoryPage.class, WicketUtils\r
+                                                       .newPathParameter(repositoryName, entry.commitId, entry.path))\r
+                                                       .setEnabled(!entry.changeType.equals(ChangeType.ADD)));\r
+                               }\r
                                WicketUtils.setAlternatingBackground(item, counter);\r
                                counter++;\r
                        }\r
index f3b32652b86fcd7a0056216c5d5b9b2b4c3a72be..7bc6b41d7c19b30a109e9fb7dbf4074831a4c24a 100644 (file)
@@ -36,6 +36,7 @@ import com.gitblit.DownloadZipServlet;
 import com.gitblit.GitBlit;\r
 import com.gitblit.Keys;\r
 import com.gitblit.models.GitNote;\r
+import com.gitblit.models.SubmoduleModel;\r
 import com.gitblit.models.PathModel.PathChangeModel;\r
 import com.gitblit.utils.JGitUtils;\r
 import com.gitblit.wicket.WicketUtils;\r
@@ -52,7 +53,7 @@ public class CommitPage extends RepositoryPage {
 \r
                Repository r = getRepository();\r
                RevCommit c = getCommit();\r
-\r
+               \r
                List<String> parents = new ArrayList<String>();\r
                if (c.getParentCount() > 0) {\r
                        for (RevCommit parent : c.getParents()) {\r
@@ -150,28 +151,60 @@ public class CommitPage extends RepositoryPage {
                                WicketUtils.setChangeTypeCssClass(changeType, entry.changeType);\r
                                setChangeTypeTooltip(changeType, entry.changeType);\r
                                item.add(changeType);\r
+                               \r
+                               boolean hasSubmodule = false;\r
+                               String submodulePath = null;\r
                                if (entry.isTree()) {\r
+                                       // tree\r
                                        item.add(new LinkPanel("pathName", null, entry.path, TreePage.class,\r
                                                        WicketUtils\r
                                                                        .newPathParameter(repositoryName, entry.commitId, entry.path)));\r
+                               } else if (entry.isSubmodule()) {\r
+                                       // submodule\r
+                                       String submoduleId = entry.objectId;\r
+                                       SubmoduleModel submodule = getSubmodule(entry.path);\r
+                                       submodulePath = submodule.gitblitPath;\r
+                                       hasSubmodule = submodule.hasSubmodule;\r
+                                       \r
+                                       item.add(new LinkPanel("pathName", "list", entry.path + " @ " +\r
+                                                       getShortObjectId(submoduleId), TreePage.class,\r
+                                                       WicketUtils.newPathParameter(submodulePath, submoduleId, "")).setEnabled(hasSubmodule));\r
                                } else {\r
+                                       // blob\r
                                        item.add(new LinkPanel("pathName", "list", entry.path, BlobPage.class,\r
                                                        WicketUtils\r
                                                                        .newPathParameter(repositoryName, entry.commitId, entry.path)));\r
                                }\r
-\r
-                               item.add(new BookmarkablePageLink<Void>("diff", BlobDiffPage.class, WicketUtils\r
-                                               .newPathParameter(repositoryName, entry.commitId, entry.path))\r
-                                               .setEnabled(!entry.changeType.equals(ChangeType.ADD)\r
-                                                               && !entry.changeType.equals(ChangeType.DELETE)));\r
-                               item.add(new BookmarkablePageLink<Void>("view", BlobPage.class, WicketUtils\r
-                                               .newPathParameter(repositoryName, entry.commitId, entry.path)));\r
-                               item.add(new BookmarkablePageLink<Void>("blame", BlamePage.class, WicketUtils\r
-                                               .newPathParameter(repositoryName, entry.commitId, entry.path))\r
-                                               .setEnabled(!entry.changeType.equals(ChangeType.ADD)));\r
-                               item.add(new BookmarkablePageLink<Void>("history", HistoryPage.class, WicketUtils\r
-                                               .newPathParameter(repositoryName, entry.commitId, entry.path))\r
-                                               .setEnabled(!entry.changeType.equals(ChangeType.ADD)));\r
+                               \r
+                               // quick links\r
+                               if (entry.isSubmodule()) {\r
+                                       // submodule                                    \r
+                                       item.add(new BookmarkablePageLink<Void>("diff", BlobDiffPage.class, WicketUtils\r
+                                                       .newPathParameter(submodulePath, entry.objectId, entry.path))\r
+                                                       .setEnabled(false));\r
+                                       item.add(new BookmarkablePageLink<Void>("view", CommitPage.class, WicketUtils\r
+                                                       .newObjectParameter(submodulePath, entry.objectId)).setEnabled(hasSubmodule));\r
+                                       item.add(new BookmarkablePageLink<Void>("blame", BlamePage.class, WicketUtils\r
+                                                       .newPathParameter(submodulePath, entry.objectId, entry.path))\r
+                                                       .setEnabled(false));\r
+                                       item.add(new BookmarkablePageLink<Void>("history", HistoryPage.class, WicketUtils\r
+                                                       .newPathParameter(submodulePath, entry.objectId, entry.path))\r
+                                                       .setEnabled(hasSubmodule));\r
+                               } else {\r
+                                       // tree or blob\r
+                                       item.add(new BookmarkablePageLink<Void>("diff", BlobDiffPage.class, WicketUtils\r
+                                                       .newPathParameter(repositoryName, entry.commitId, entry.path))\r
+                                                       .setEnabled(!entry.changeType.equals(ChangeType.ADD)\r
+                                                                       && !entry.changeType.equals(ChangeType.DELETE)));\r
+                                       item.add(new BookmarkablePageLink<Void>("view", BlobPage.class, WicketUtils\r
+                                                       .newPathParameter(repositoryName, entry.commitId, entry.path)));\r
+                                       item.add(new BookmarkablePageLink<Void>("blame", BlamePage.class, WicketUtils\r
+                                                       .newPathParameter(repositoryName, entry.commitId, entry.path))\r
+                                                       .setEnabled(!entry.changeType.equals(ChangeType.ADD)));\r
+                                       item.add(new BookmarkablePageLink<Void>("history", HistoryPage.class, WicketUtils\r
+                                                       .newPathParameter(repositoryName, entry.commitId, entry.path))\r
+                                                       .setEnabled(!entry.changeType.equals(ChangeType.ADD)));\r
+                               }\r
 \r
                                WicketUtils.setAlternatingBackground(item, counter);\r
                                counter++;\r
index 5764235ac3e2fb6653f739c3fe1f1d7a305076d1..e032cbf9d5989d9173b5c5a07cfe4f0fcde07b76 100644 (file)
@@ -15,6 +15,7 @@
  */\r
 package com.gitblit.wicket.pages;\r
 \r
+import java.text.MessageFormat;\r
 import java.text.ParseException;\r
 \r
 import org.apache.wicket.PageParameters;\r
@@ -27,6 +28,7 @@ import org.eclipse.jgit.revwalk.RevCommit;
 import com.gitblit.GitBlit;\r
 import com.gitblit.utils.JGitUtils;\r
 import com.gitblit.utils.MarkdownUtils;\r
+import com.gitblit.utils.StringUtils;\r
 import com.gitblit.wicket.WicketUtils;\r
 \r
 public class MarkdownPage extends RepositoryPage {\r
@@ -56,8 +58,8 @@ public class MarkdownPage extends RepositoryPage {
                try {\r
                        htmlText = MarkdownUtils.transformMarkdown(markdownText);\r
                } catch (ParseException p) {\r
-                       error(p.getMessage());\r
-                       htmlText = markdownText;\r
+                       markdownText = MessageFormat.format("<div class=\"alert alert-error\"><strong>{0}:</strong> {1}</div>{2}", getString("gb.error"), getString("gb.markdownFailure"), markdownText);\r
+                       htmlText = StringUtils.breakLinesForHtml(markdownText);\r
                }\r
 \r
                // Add the html to the page\r
index 4b5e28d433faf17d05060c98ec83b568bf58beac..6d33a147e7cd71c77b13e6dbe212a46e14d375a0 100644 (file)
@@ -19,9 +19,12 @@ import java.io.Serializable;
 import java.text.MessageFormat;\r
 import java.util.ArrayList;\r
 import java.util.Arrays;\r
+import java.util.HashMap;\r
 import java.util.LinkedHashMap;\r
+import java.util.LinkedHashSet;\r
 import java.util.List;\r
 import java.util.Map;\r
+import java.util.Set;\r
 \r
 import org.apache.wicket.Component;\r
 import org.apache.wicket.PageParameters;\r
@@ -45,6 +48,7 @@ import com.gitblit.Keys;
 import com.gitblit.PagesServlet;\r
 import com.gitblit.SyndicationServlet;\r
 import com.gitblit.models.RepositoryModel;\r
+import com.gitblit.models.SubmoduleModel;\r
 import com.gitblit.utils.ArrayUtils;\r
 import com.gitblit.utils.JGitUtils;\r
 import com.gitblit.utils.StringUtils;\r
@@ -62,18 +66,20 @@ public abstract class RepositoryPage extends BasePage {
 \r
        protected final String repositoryName;\r
        protected final String objectId;\r
-\r
+       \r
        private transient Repository r;\r
 \r
        private RepositoryModel m;\r
 \r
+       private Map<String, SubmoduleModel> submodules;\r
+       \r
        private final Map<String, PageRegistration> registeredPages;\r
 \r
        public RepositoryPage(PageParameters params) {\r
                super(params);\r
                repositoryName = WicketUtils.getRepositoryName(params);\r
                objectId = WicketUtils.getObject(params);\r
-\r
+               \r
                if (StringUtils.isEmpty(repositoryName)) {\r
                        error(MessageFormat.format(getString("gb.repositoryNotSpecifiedFor"), getPageName()), true);\r
                }\r
@@ -206,8 +212,85 @@ public abstract class RepositoryPage extends BasePage {
                        error(MessageFormat.format(getString("gb.failedToFindCommit"),\r
                                        objectId, repositoryName, getPageName()), true);\r
                }\r
+               getSubmodules(commit);\r
                return commit;\r
        }\r
+       \r
+       private Map<String, SubmoduleModel> getSubmodules(RevCommit commit) {   \r
+               if (submodules == null) {\r
+                       submodules = new HashMap<String, SubmoduleModel>();\r
+                       for (SubmoduleModel model : JGitUtils.getSubmodules(r, commit.getTree())) {\r
+                               submodules.put(model.path, model);\r
+                       }\r
+               }\r
+               return submodules;\r
+       }\r
+       \r
+       protected Map<String, SubmoduleModel> getSubmodules() {\r
+               return submodules;\r
+       }\r
+       \r
+       protected SubmoduleModel getSubmodule(String path) {\r
+               SubmoduleModel model = submodules.get(path);\r
+               if (model == null) {\r
+                       // undefined submodule?!\r
+                       model = new SubmoduleModel(path.substring(path.lastIndexOf('/') + 1), path, path);\r
+                       model.hasSubmodule = false;\r
+                       model.gitblitPath = model.name;\r
+                       return model;\r
+               } else {\r
+                       // extract the repository name from the clone url\r
+                       List<String> patterns = GitBlit.getStrings(Keys.git.submoduleUrlPatterns);\r
+                       String submoduleName = StringUtils.extractRepositoryPath(model.url, patterns.toArray(new String[0]));\r
+                       \r
+                       // determine the current path for constructing paths relative\r
+                       // to the current repository\r
+                       String currentPath = "";\r
+                       if (repositoryName.indexOf('/') > -1) {\r
+                               currentPath = repositoryName.substring(0, repositoryName.lastIndexOf('/') + 1);\r
+                       }\r
+\r
+                       // try to locate the submodule repository\r
+                       // prefer bare to non-bare names\r
+                       List<String> candidates = new ArrayList<String>();\r
+\r
+                       // relative\r
+                       candidates.add(currentPath + StringUtils.stripDotGit(submoduleName));\r
+                       candidates.add(candidates.get(candidates.size() - 1) + ".git");\r
+\r
+                       // relative, no subfolder\r
+                       if (submoduleName.lastIndexOf('/') > -1) {\r
+                               String name = submoduleName.substring(submoduleName.lastIndexOf('/') + 1);\r
+                               candidates.add(currentPath + StringUtils.stripDotGit(name));\r
+                               candidates.add(currentPath + candidates.get(candidates.size() - 1) + ".git");\r
+                       }\r
+\r
+                       // absolute\r
+                       candidates.add(StringUtils.stripDotGit(submoduleName));\r
+                       candidates.add(candidates.get(candidates.size() - 1) + ".git");\r
+\r
+                       // absolute, no subfolder\r
+                       if (submoduleName.lastIndexOf('/') > -1) {\r
+                               String name = submoduleName.substring(submoduleName.lastIndexOf('/') + 1);\r
+                               candidates.add(StringUtils.stripDotGit(name));\r
+                               candidates.add(candidates.get(candidates.size() - 1) + ".git");\r
+                       }\r
+\r
+                       // create a unique, ordered set of candidate paths\r
+                       Set<String> paths = new LinkedHashSet<String>(candidates);\r
+                       for (String candidate : paths) {\r
+                               if (GitBlit.self().hasRepository(candidate)) {\r
+                                       model.hasSubmodule = true;\r
+                                       model.gitblitPath = candidate;\r
+                                       return model;\r
+                               }\r
+                       }\r
+                       \r
+                       // we do not have a copy of the submodule, but we need a path\r
+                       model.gitblitPath = candidates.get(0);\r
+                       return model;\r
+               }               \r
+       }\r
 \r
        protected String getShortObjectId(String objectId) {\r
                return objectId.substring(0, 8);\r
index cb507d2371632fc5f7d74887a475d7376efba4ce..2a624c266d830059a345ba00336a09458dcdfe51 100644 (file)
@@ -136,6 +136,7 @@ public class SummaryPage extends RepositoryPage {
 \r
                if (getRepositoryModel().showReadme) {\r
                        String htmlText = null;\r
+                       String markdownText = null;\r
                        String readme = null;\r
                        try {\r
                                RevCommit head = JGitUtils.getCommit(r, null);\r
@@ -158,11 +159,12 @@ public class SummaryPage extends RepositoryPage {
                                }\r
                                if (!StringUtils.isEmpty(readme)) {\r
                                        String [] encodings = GitBlit.getEncodings();\r
-                                       String markdownText = JGitUtils.getStringContent(r, head.getTree(), readme, encodings);\r
+                                       markdownText = JGitUtils.getStringContent(r, head.getTree(), readme, encodings);\r
                                        htmlText = MarkdownUtils.transformMarkdown(markdownText);\r
                                }\r
                        } catch (ParseException p) {\r
-                               error(p.getMessage());\r
+                               markdownText = MessageFormat.format("<div class=\"alert alert-error\"><strong>{0}:</strong> {1}</div>{2}", getString("gb.error"), getString("gb.markdownFailure"), markdownText);\r
+                               htmlText = StringUtils.breakLinesForHtml(markdownText);\r
                        }\r
                        Fragment fragment = new Fragment("readme", "markdownPanel");\r
                        fragment.add(new Label("readmeFile", readme));\r
index 760294f54dd4a0e38ddfa1325ca1fb7f1504537c..0047ff0d162b3c0fc190adb5e36774076c678c5d 100644 (file)
                </tr>\r
        </table>\r
 \r
+       <!--  submodule links -->\r
+       <wicket:fragment wicket:id="submoduleLinks">\r
+               <span class="link">\r
+                       <a wicket:id="view"><wicket:message key="gb.view"></wicket:message></a> | <span class="hidden-phone"><a wicket:id="tree"><wicket:message key="gb.tree"></wicket:message></a> | </span><a wicket:id="history"><wicket:message key="gb.history"></wicket:message></a> | <a wicket:id="zip"><wicket:message key="gb.zip"></wicket:message></a>\r
+               </span>\r
+       </wicket:fragment>\r
+\r
        <!--  tree links -->\r
        <wicket:fragment wicket:id="treeLinks">\r
                <span class="link">\r
index 7fc91eeeb21a085a53ecd70b20a8765386a0e7dd..973634b7ad460042515a7fc6217741eea7b286a4 100644 (file)
@@ -34,6 +34,7 @@ import com.gitblit.DownloadZipServlet;
 import com.gitblit.GitBlit;\r
 import com.gitblit.Keys;\r
 import com.gitblit.models.PathModel;\r
+import com.gitblit.models.SubmoduleModel;\r
 import com.gitblit.utils.ByteFormat;\r
 import com.gitblit.utils.JGitUtils;\r
 import com.gitblit.wicket.WicketUtils;\r
@@ -71,7 +72,7 @@ public class TreePage extends RepositoryPage {
                        if (path.lastIndexOf('/') > -1) {\r
                                parentPath = path.substring(0, path.lastIndexOf('/'));\r
                        }\r
-                       PathModel model = new PathModel("..", parentPath, 0, FileMode.TREE.getBits(), objectId);\r
+                       PathModel model = new PathModel("..", parentPath, 0, FileMode.TREE.getBits(), null, objectId);\r
                        model.isParentPath = true;\r
                        paths.add(0, model);\r
                }\r
@@ -118,6 +119,34 @@ public class TreePage extends RepositoryPage {
                                                                repositoryName, objectId, entry.path)).setVisible(GitBlit\r
                                                                .getBoolean(Keys.web.allowZipDownloads, true)));\r
                                                item.add(links);\r
+                                       } else if (entry.isSubmodule()) {\r
+                                               // submodule\r
+                                               String submoduleId = entry.objectId;                                            \r
+                                               String submodulePath;\r
+                                               boolean hasSubmodule = false;\r
+                                               SubmoduleModel submodule = getSubmodule(entry.path);\r
+                                               submodulePath = submodule.gitblitPath;\r
+                                               hasSubmodule = submodule.hasSubmodule;\r
+                                               \r
+                                               item.add(WicketUtils.newImage("pathIcon", "git-orange-16x16.png"));\r
+                                               item.add(new Label("pathSize", ""));\r
+                                               item.add(new LinkPanel("pathName", "list", entry.name + " @ " + \r
+                                                               getShortObjectId(submoduleId), TreePage.class,\r
+                                                               WicketUtils.newPathParameter(submodulePath, submoduleId, "")).setEnabled(hasSubmodule));\r
+                                               \r
+                                               Fragment links = new Fragment("pathLinks", "submoduleLinks", this);\r
+                                               links.add(new BookmarkablePageLink<Void>("view", SummaryPage.class,\r
+                                                               WicketUtils.newRepositoryParameter(submodulePath)).setEnabled(hasSubmodule));\r
+                                               links.add(new BookmarkablePageLink<Void>("tree", TreePage.class,\r
+                                                               WicketUtils.newPathParameter(submodulePath, submoduleId,\r
+                                                                               "")).setEnabled(hasSubmodule));\r
+                                               links.add(new BookmarkablePageLink<Void>("history", HistoryPage.class,\r
+                                                               WicketUtils.newPathParameter(submodulePath, submoduleId,\r
+                                                                               "")).setEnabled(hasSubmodule));\r
+                                               links.add(new ExternalLink("zip", DownloadZipServlet.asLink(baseUrl,\r
+                                                               submodulePath, submoduleId, "")).setVisible(GitBlit\r
+                                                               .getBoolean(Keys.web.allowZipDownloads, true)).setEnabled(hasSubmodule));\r
+                                               item.add(links);                                                \r
                                        } else {\r
                                                // blob link\r
                                                item.add(WicketUtils.getFileImage("pathIcon", entry.name));\r
index 91bfa672b9989889b686566d79be1155006b33d6..bcf3a99c051f0971370363d2ac5fc91d0a790cc8 100644 (file)
@@ -150,4 +150,11 @@ public class StringUtilsTest {
                assertFalse(StringUtils.fuzzyMatch("123", "12345"));\r
                assertFalse(StringUtils.fuzzyMatch("AbCdEfHIJ", "abc*hhh"));\r
        }\r
+       \r
+       @Test\r
+       public void testGetRepositoryPath() throws Exception {\r
+               assertEquals("gitblit/gitblit.git", StringUtils.extractRepositoryPath("git://github.com/gitblit/gitblit.git", new String [] { ".*?://github.com/(.*)" }));\r
+               assertEquals("gitblit.git", StringUtils.extractRepositoryPath("git://github.com/gitblit/gitblit.git", new String [] { ".*?://github.com/[^/].*?/(.*)" }));\r
+               assertEquals("gitblit.git", StringUtils.extractRepositoryPath("git://github.com/gitblit/gitblit.git"));\r
+       }\r
 }\r