# 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
- 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
\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
\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
**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
\r
<hr/>\r
\r
-### Older Releases\r
-\r
**0.9.3** *released 2012-04-11*\r
\r
#### fixes\r
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
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
\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
--- /dev/null
+/*\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
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
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
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
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
} 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
} 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
}\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
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
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
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
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
}\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
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
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
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
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
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
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
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
\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
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
*/\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
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
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
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
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
\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
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
\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
}\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
</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
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
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
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
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