Markdown: show README.md at bottom of trees
doc.css needs to be adjusted to "cancel out" the styles applied by
go.css and gitiles.css. Within documentation blocks h1/etc. should
have the same styles as when rendered in documentation pages.
Change-Id: I4bd91ad76a801f27abc1e885d8bef18191ef128a
diff --git a/gitiles-servlet/src/main/java/com/google/gitiles/PathServlet.java b/gitiles-servlet/src/main/java/com/google/gitiles/PathServlet.java
index fa76cb5..6d54991 100644
--- a/gitiles-servlet/src/main/java/com/google/gitiles/PathServlet.java
+++ b/gitiles-servlet/src/main/java/com/google/gitiles/PathServlet.java
@@ -36,6 +36,7 @@
import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.errors.StopWalkException;
import org.eclipse.jgit.http.server.ServletUtils;
+import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.FileMode;
import org.eclipse.jgit.lib.ObjectId;
@@ -417,6 +418,7 @@
private void showTree(HttpServletRequest req, HttpServletResponse res, WalkResult wr)
throws IOException {
GitilesView view = ViewFilter.getView(req);
+ Config cfg = getAccess(req).getConfig();
List<String> autodive = view.getParameters().get(AUTODIVE_PARAM);
if (autodive.size() != 1 || !NO_AUTODIVE_VALUE.equals(autodive.get(0))) {
byte[] path = Constants.encode(view.getPathPart());
@@ -444,7 +446,7 @@
"title", !view.getPathPart().isEmpty() ? view.getPathPart() : "/",
"breadcrumbs", view.getBreadcrumbs(wr.hasSingleTree),
"type", FileType.TREE.toString(),
- "data", new TreeSoyData(wr.getObjectReader(), view)
+ "data", new TreeSoyData(wr.getObjectReader(), view, cfg, wr.root)
.setArchiveFormat(getArchiveFormat(getAccess(req)))
.toSoyData(wr.id, wr.tw)));
}
diff --git a/gitiles-servlet/src/main/java/com/google/gitiles/RevisionServlet.java b/gitiles-servlet/src/main/java/com/google/gitiles/RevisionServlet.java
index 9ff0fbd..c8336c7 100644
--- a/gitiles-servlet/src/main/java/com/google/gitiles/RevisionServlet.java
+++ b/gitiles-servlet/src/main/java/com/google/gitiles/RevisionServlet.java
@@ -32,6 +32,7 @@
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.http.server.ServletUtils;
+import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectLoader;
@@ -40,6 +41,7 @@
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevObject;
import org.eclipse.jgit.revwalk.RevTag;
+import org.eclipse.jgit.revwalk.RevTree;
import org.eclipse.jgit.revwalk.RevWalk;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -76,6 +78,7 @@
GitilesView view = ViewFilter.getView(req);
Repository repo = ServletUtils.getRepository(req);
GitilesAccess access = getAccess(req);
+ Config cfg = getAccess(req).getConfig();
RevWalk walk = new RevWalk(repo);
try {
@@ -83,6 +86,7 @@
List<RevObject> objects = listObjects(walk, view.getRevision());
List<Map<String, ?>> soyObjects = Lists.newArrayListWithCapacity(objects.size());
boolean hasBlob = false;
+ boolean hasReadme = false;
// TODO(sop): Allow caching commits by SHA-1 when no S cookie is sent.
for (RevObject obj : objects) {
@@ -98,9 +102,13 @@
.toSoyData(req, (RevCommit) obj, COMMIT_SOY_FIELDS, df)));
break;
case OBJ_TREE:
+ Map<String, Object> tree =
+ new TreeSoyData(walk.getObjectReader(), view, cfg, (RevTree) obj)
+ .toSoyData(obj);
soyObjects.add(ImmutableMap.of(
"type", Constants.TYPE_TREE,
- "data", new TreeSoyData(walk.getObjectReader(), view).toSoyData(obj)));
+ "data", tree));
+ hasReadme = tree.containsKey("readmeHtml");
break;
case OBJ_BLOB:
soyObjects.add(ImmutableMap.of(
@@ -132,7 +140,8 @@
renderHtml(req, res, "gitiles.revisionDetail", ImmutableMap.of(
"title", view.getRevision().getName(),
"objects", soyObjects,
- "hasBlob", hasBlob));
+ "hasBlob", hasBlob,
+ "hasReadme", hasReadme));
} finally {
walk.release();
}
diff --git a/gitiles-servlet/src/main/java/com/google/gitiles/TreeSoyData.java b/gitiles-servlet/src/main/java/com/google/gitiles/TreeSoyData.java
index 70dfcde..0b275e0 100644
--- a/gitiles-servlet/src/main/java/com/google/gitiles/TreeSoyData.java
+++ b/gitiles-servlet/src/main/java/com/google/gitiles/TreeSoyData.java
@@ -22,11 +22,23 @@
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.gitiles.PathServlet.FileType;
+import com.google.gitiles.doc.GitilesMarkdown;
+import com.google.gitiles.doc.ImageLoader;
+import com.google.gitiles.doc.MarkdownToHtml;
+import com.google.template.soy.data.SanitizedContent;
+import org.eclipse.jgit.errors.LargeObjectException;
import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectReader;
+import org.eclipse.jgit.revwalk.RevTree;
import org.eclipse.jgit.treewalk.TreeWalk;
+import org.eclipse.jgit.util.RawParseUtils;
+import org.pegdown.ast.RootNode;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.List;
@@ -34,6 +46,8 @@
/** Soy data converter for git trees. */
public class TreeSoyData {
+ private static final Logger log = LoggerFactory.getLogger(TreeSoyData.class);
+
/**
* Number of characters to display for a symlink target. Targets longer than
* this are abbreviated for display in a tree listing.
@@ -71,11 +85,16 @@
private final ObjectReader reader;
private final GitilesView view;
+ private final Config cfg;
+ private final RevTree rootTree;
private ArchiveFormat archiveFormat;
- public TreeSoyData(ObjectReader reader, GitilesView view) {
+ public TreeSoyData(ObjectReader reader, GitilesView view, Config cfg,
+ RevTree rootTree) {
this.reader = reader;
this.view = view;
+ this.cfg = cfg;
+ this.rootTree = rootTree;
}
public TreeSoyData setArchiveFormat(ArchiveFormat archiveFormat) {
@@ -85,6 +104,9 @@
public Map<String, Object> toSoyData(ObjectId treeId, TreeWalk tw) throws MissingObjectException,
IOException {
+ String readmePath = null;
+ ObjectId readmeId = null;
+
List<Object> entries = Lists.newArrayList();
GitilesView.Builder urlBuilder = GitilesView.path().copyFrom(view);
while (tw.next()) {
@@ -122,6 +144,9 @@
if (targetUrl != null) {
entry.put("targetUrl", targetUrl);
}
+ } else if (isReadmeFile(name) && type == FileType.REGULAR_FILE) {
+ readmePath = tw.getPathString();
+ readmeId = tw.getObjectId(0);
}
entries.add(entry);
}
@@ -141,13 +166,49 @@
data.put("archiveType", archiveFormat.getShortName());
}
+ if (readmeId != null && cfg.getBoolean("markdown", "render", true)) {
+ data.put("readmePath", readmePath);
+ data.put("readmeHtml", render(readmePath, readmeId));
+ }
+
return data;
}
+ /** True if the file is the default markdown file to render in tree view. */
+ private static boolean isReadmeFile(String name) {
+ return name.equalsIgnoreCase("README.md");
+ }
+
public Map<String, Object> toSoyData(ObjectId treeId) throws MissingObjectException, IOException {
TreeWalk tw = new TreeWalk(reader);
tw.addTree(treeId);
tw.setRecursive(false);
return toSoyData(treeId, tw);
}
+
+ private SanitizedContent render(String path, ObjectId id) {
+ try {
+ int inputLimit = cfg.getInt("markdown", "inputLimit", 5 << 20);
+ byte[] raw = reader.open(id, Constants.OBJ_BLOB).getCachedBytes(inputLimit);
+ String md = RawParseUtils.decode(raw);
+ RootNode root = GitilesMarkdown.parseFile(view, path, md);
+ if (root == null) {
+ return null;
+ }
+
+ int imageLimit = cfg.getInt("markdown", "imageLimit", 256 << 10);
+ ImageLoader img = null;
+ if (imageLimit > 0) {
+ img = new ImageLoader(reader, view, rootTree, path, imageLimit);
+ }
+
+ return new MarkdownToHtml(view, cfg)
+ .setImageLoader(img)
+ .toSoyHtml(root);
+ } catch (LargeObjectException | IOException e) {
+ log.error(String.format("error rendering %s/%s/%s",
+ view.getRepositoryName(), view.getPathPart(), path), e);
+ return null;
+ }
+ }
}
diff --git a/gitiles-servlet/src/main/java/com/google/gitiles/doc/GitilesMarkdown.java b/gitiles-servlet/src/main/java/com/google/gitiles/doc/GitilesMarkdown.java
index 9557c54..f077688 100644
--- a/gitiles-servlet/src/main/java/com/google/gitiles/doc/GitilesMarkdown.java
+++ b/gitiles-servlet/src/main/java/com/google/gitiles/doc/GitilesMarkdown.java
@@ -32,7 +32,7 @@
import java.util.List;
/** Parses Gitiles extensions to markdown. */
-class GitilesMarkdown extends Parser implements BlockPluginParser {
+public class GitilesMarkdown extends Parser implements BlockPluginParser {
private static final Logger log = LoggerFactory.getLogger(MarkdownUtil.class);
// SUPPRESS_ALL_HTML is enabled to permit hosting arbitrary user content
@@ -43,7 +43,7 @@
// this impacting the rendered formatting.
private static final int MD_OPTIONS = (ALL | SUPPRESS_ALL_HTML) & ~(HARDWRAPS);
- static RootNode parseFile(GitilesView view, String path, String md) {
+ public static RootNode parseFile(GitilesView view, String path, String md) {
if (md == null) {
return null;
}
diff --git a/gitiles-servlet/src/main/resources/com/google/gitiles/static/doc.css b/gitiles-servlet/src/main/resources/com/google/gitiles/static/doc.css
index 9272763..24b9b79 100644
--- a/gitiles-servlet/src/main/resources/com/google/gitiles/static/doc.css
+++ b/gitiles-servlet/src/main/resources/com/google/gitiles/static/doc.css
@@ -127,17 +127,42 @@
.doc {
color: #444;
font-size: 13px;
+ line-height: normal;
}
.doc h1, .doc h2, .doc h3, .doc h4, .doc h5, .doc h6 {
font-family: "open sans",arial,sans-serif;
+ font-weight: bold;
+ color: #444;
+ height: auto;
+ white-space: normal;
+ overflow: visible;
+ margin: 0.67em 0 0.67em 0;
}
-.doc h1, .doc h2, .doc h3, .doc h4 { font-weight: bold; }
-.doc h5, .doc h6 { font-weight: normal; }
-.doc h1 { font-size: 20px; }
-.doc h2 { font-size: 16px; }
-.doc h3 { font-size: 14px; }
-.doc h4, .doc h5, .doc h6 { font-size: 13px; }
+.doc h1 {
+ font-size: 20px;
+ margin: 0.67em 0 0.67em 0;
+}
+.doc h2 {
+ font-size: 16px;
+ margin: 0.67em 0 0.67em 0;
+}
+.doc h3 {
+ font-size: 14px;
+ margin: 0.67em 0 0.67em 0;
+}
+.doc h4 {
+ font-size: 13px;
+ margin: 1em 0 1em 0;
+}
+.doc h5 {
+ font-size: 13px;
+ margin: 1.3em 0 1.3em 0;
+}
+.doc h6 {
+ font-size: 13px;
+ margin: 1.6em 0 1.6em 0;
+}
.doc a { text-decoration: none; }
.doc a:link { color: #245dc1; }
@@ -161,6 +186,15 @@
border: 0;
}
+.doc em {
+ font-weight: normal;
+ font-style: italic;
+}
+.doc strong {
+ font-weight: bold;
+ color: inherit;
+}
+
.doc pre {
border: 1px solid silver;
background: #fafafa;
@@ -180,6 +214,9 @@
border-collapse: collapse;
border-spacing: 0;
}
+.doc th {
+ text-align: center;
+}
.doc th, .doc td {
border: 1px solid #eee;
padding: 4px 12px;
diff --git a/gitiles-servlet/src/main/resources/com/google/gitiles/static/gitiles.css b/gitiles-servlet/src/main/resources/com/google/gitiles/static/gitiles.css
index e2adfc2..13c187f 100644
--- a/gitiles-servlet/src/main/resources/com/google/gitiles/static/gitiles.css
+++ b/gitiles-servlet/src/main/resources/com/google/gitiles/static/gitiles.css
@@ -418,3 +418,18 @@
font-size: 8pt;
white-space: pre !important;
}
+
+/* Styles for README.md in tree view. */
+
+.readme-path {
+ border-top: #ddd solid 1px; /* BORDER */
+ color: #666;
+ font-size: 9pt;
+ padding-top: 5px; /* VPADDING */
+}
+.doc {
+ border-bottom: #ddd solid 1px; /* BORDER */
+}
+.doc h1 {
+ position: static;
+}
diff --git a/gitiles-servlet/src/main/resources/com/google/gitiles/templates/ObjectDetail.soy b/gitiles-servlet/src/main/resources/com/google/gitiles/templates/ObjectDetail.soy
index d78187f..6c87498 100644
--- a/gitiles-servlet/src/main/resources/com/google/gitiles/templates/ObjectDetail.soy
+++ b/gitiles-servlet/src/main/resources/com/google/gitiles/templates/ObjectDetail.soy
@@ -156,6 +156,8 @@
* targetName: name of a symlink target, required only if type == 'SYMLINK'.
* targetUrl: optional url of a symlink target, required only if
* type == 'SYMLINK'.
+ * @param? readmePath optional path of the selected README.md file.
+ * @param? readmeHtml optional rendered README.md contents.
*/
{template .treeDetail}
<div class="sha1">
@@ -206,6 +208,11 @@
{else}
<p>{msg desc="Informational text for when a tree is empty"}This tree is empty.{/msg}</p>
{/if}
+
+{if $readmeHtml}
+ <div class="readme-path">{$readmePath}</div>
+ <div class="doc">{$readmeHtml}</div>
+{/if}
{/template}
/**
diff --git a/gitiles-servlet/src/main/resources/com/google/gitiles/templates/PathDetail.soy b/gitiles-servlet/src/main/resources/com/google/gitiles/templates/PathDetail.soy
index d2f8b3a..e42ee97 100644
--- a/gitiles-servlet/src/main/resources/com/google/gitiles/templates/PathDetail.soy
+++ b/gitiles-servlet/src/main/resources/com/google/gitiles/templates/PathDetail.soy
@@ -31,6 +31,10 @@
{call .header data="all"}
{param css: [gitiles.PRETTIFY_CSS_URL] /}
{/call}
+{elseif $data.readmeHtml}
+ {call .header data="all"}
+ {param css: [gitiles.DOC_CSS_URL] /}
+ {/call}
{else}
{call .header data="all" /}
{/if}
diff --git a/gitiles-servlet/src/main/resources/com/google/gitiles/templates/RevisionDetail.soy b/gitiles-servlet/src/main/resources/com/google/gitiles/templates/RevisionDetail.soy
index 2bc335d..3b2fcc2 100644
--- a/gitiles-servlet/src/main/resources/com/google/gitiles/templates/RevisionDetail.soy
+++ b/gitiles-servlet/src/main/resources/com/google/gitiles/templates/RevisionDetail.soy
@@ -22,6 +22,7 @@
* @param? headerVariant variant name for custom header.
* @param breadcrumbs breadcrumbs for this page.
* @param? hasBlob set to true if the revision or its peeled value is a blob.
+ * @param? hasReadme set to true if the treeDetail has readmeHtml.
* @param objects list of objects encountered when peeling this object. Each
* object has a "type" key with one of the
* org.eclipse.jgit.lib.Contants.TYPE_* constant strings, and a "data" key
@@ -33,6 +34,10 @@
{call .header data="all"}
{param css: [gitiles.PRETTIFY_CSS_URL] /}
{/call}
+{elseif $hasReadme}
+ {call .header data="all"}
+ {param css: [gitiles.DOC_CSS_URL] /}
+ {/call}
{else}
{call .header data="all" /}
{/if}