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