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}