James Moger
2013-11-12 dc7c2f650de99c7f9ae8d6c049f419fcd00fb2a2
Refactor markup processing in preparation for supporting other formats

Change-Id: I0eb217064abc4f4b0f6bfbbc21302c470cc2f9c6
2 files added
1 files renamed
9 files modified
1 files deleted
635 ■■■■■ changed files
src/main/java/com/gitblit/PagesServlet.java 26 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/utils/StringUtils.java 14 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/GitBlitWebApp.java 6 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/MarkupProcessor.java 216 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/WicketUtils.java 10 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/BlobPage.java 8 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/DocPage.html 6 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/DocPage.java 92 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/DocsPage.java 25 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/MarkdownPage.java 105 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/RepositoryPage.java 45 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/SummaryPage.java 59 ●●●● patch | view | raw | blame | history
src/main/resources/gitblit.css 23 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/PagesServlet.java
@@ -38,6 +38,8 @@
import com.gitblit.utils.JGitUtils;
import com.gitblit.utils.MarkdownUtils;
import com.gitblit.utils.StringUtils;
import com.gitblit.wicket.MarkupProcessor;
import com.gitblit.wicket.MarkupProcessor.MarkupDocument;
/**
 * Serves the content of a gh-pages branch.
@@ -142,21 +144,20 @@
                return;
            }
            MarkupProcessor processor = new MarkupProcessor(GitBlit.getSettings());
            String [] encodings = GitBlit.getEncodings();
            RevTree tree = commit.getTree();
            byte[] content = null;
            if (StringUtils.isEmpty(resource)) {
                // find resource
                List<String> markdownExtensions = GitBlit.getStrings(Keys.web.markdownExtensions);
                List<String> extensions = new ArrayList<String>(markdownExtensions.size() + 2);
                List<String> extensions = new ArrayList<String>(processor.getMarkupExtensions());
                extensions.add("html");
                extensions.add("htm");
                extensions.addAll(markdownExtensions);
                for (String ext : extensions){
                for (String ext : extensions) {
                    String file = "index." + ext;
                    String stringContent = JGitUtils.getStringContent(r, tree, file, encodings);
                    if(stringContent == null){
                    if (stringContent == null) {
                        continue;
                    }
                    content = stringContent.getBytes(Constants.ENCODING);
@@ -213,14 +214,13 @@
                return;
            }
            // check to see if we should transform markdown files
            for (String ext : GitBlit.getStrings(Keys.web.markdownExtensions)) {
                if (resource.endsWith(ext)) {
                    String mkd = new String(content, Constants.ENCODING);
                    content = MarkdownUtils.transformMarkdown(mkd).getBytes(Constants.ENCODING);
                    response.setContentType("text/html; charset=" + Constants.ENCODING);
                    break;
                }
            // check to see if we should transform markup files
            String ext = StringUtils.getFileExtension(resource);
            if (processor.getMarkupExtensions().contains(ext)) {
                String markup = new String(content, Constants.ENCODING);
                MarkupDocument markupDoc = processor.parse(repository, commit.getName(), resource, markup);
                content = markupDoc.html.getBytes("UTF-8");
                response.setContentType("text/html; charset=" + Constants.ENCODING);
            }
            try {
src/main/java/com/gitblit/utils/StringUtils.java
@@ -556,6 +556,20 @@
    }
    /**
     * Returns the file extension of a path.
     *
     * @param path
     * @return a blank string or a file extension
     */
    public static String stripFileExtension(String path) {
        int lastDot = path.lastIndexOf('.');
        if (lastDot > -1) {
            return path.substring(0, lastDot);
        }
        return path;
    }
    /**
     * Replace all occurences of a substring within a string with
     * another string.
     *
src/main/java/com/gitblit/wicket/GitBlitWebApp.java
@@ -39,6 +39,7 @@
import com.gitblit.wicket.pages.CommitDiffPage;
import com.gitblit.wicket.pages.CommitPage;
import com.gitblit.wicket.pages.ComparePage;
import com.gitblit.wicket.pages.DocPage;
import com.gitblit.wicket.pages.DocsPage;
import com.gitblit.wicket.pages.FederationRegistrationPage;
import com.gitblit.wicket.pages.ForkPage;
@@ -49,7 +50,6 @@
import com.gitblit.wicket.pages.LogPage;
import com.gitblit.wicket.pages.LogoutPage;
import com.gitblit.wicket.pages.LuceneSearchPage;
import com.gitblit.wicket.pages.MarkdownPage;
import com.gitblit.wicket.pages.MetricsPage;
import com.gitblit.wicket.pages.MyDashboardPage;
import com.gitblit.wicket.pages.OverviewPage;
@@ -121,9 +121,9 @@
        mount("/users", UsersPage.class);
        mount("/logout", LogoutPage.class);
        // setup the markdown urls
        // setup the markup document urls
        mount("/docs", DocsPage.class, "r");
        mount("/markdown", MarkdownPage.class, "r", "h", "f");
        mount("/doc", DocPage.class, "r", "h", "f");
        // federation urls
        mount("/proposal", ReviewProposalPage.class, "t");
src/main/java/com/gitblit/wicket/MarkupProcessor.java
New file
@@ -0,0 +1,216 @@
/*
 * Copyright 2013 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.wicket;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
import org.apache.wicket.Page;
import org.apache.wicket.RequestCycle;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.pegdown.LinkRenderer;
import org.pegdown.ast.WikiLinkNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.gitblit.IStoredSettings;
import com.gitblit.Keys;
import com.gitblit.models.PathModel;
import com.gitblit.utils.JGitUtils;
import com.gitblit.utils.MarkdownUtils;
import com.gitblit.utils.StringUtils;
import com.gitblit.wicket.pages.DocPage;
/**
 * Processes markup content and generates html with repository-relative page and
 * image linking.
 *
 * @author James Moger
 *
 */
public class MarkupProcessor {
    public enum MarkupSyntax {
        PLAIN, MARKDOWN
    }
    private Logger logger = LoggerFactory.getLogger(getClass());
    private final IStoredSettings settings;
    public MarkupProcessor(IStoredSettings settings) {
        this.settings = settings;
    }
    public List<String> getMarkupExtensions() {
        List<String> list = new ArrayList<String>();
        list.addAll(settings.getStrings(Keys.web.markdownExtensions));
        return list;
    }
    private MarkupSyntax determineSyntax(String documentPath) {
        String ext = StringUtils.getFileExtension(documentPath).toLowerCase();
        if (StringUtils.isEmpty(ext)) {
            return MarkupSyntax.PLAIN;
        }
        if (settings.getStrings(Keys.web.markdownExtensions).contains(ext)) {
            return MarkupSyntax.MARKDOWN;
        }
        return MarkupSyntax.PLAIN;
    }
    public MarkupDocument parseReadme(Repository r, String repositoryName, String commitId) {
        String readme = null;
        RevCommit commit = JGitUtils.getCommit(r, commitId);
        List<PathModel> paths = JGitUtils.getFilesInPath(r, null, commit);
        for (PathModel path : paths) {
            if (!path.isTree()) {
                String name = path.name.toLowerCase();
                if (name.equals("readme") || name.equals("readme.txt")) {
                    readme = path.name;
                    break;
                } else if (name.startsWith("readme.")) {
                    String ext = StringUtils.getFileExtension(name).toLowerCase();
                    if (getMarkupExtensions().contains(ext)) {
                        readme = path.name;
                        break;
                    }
                }
            }
        }
        if (!StringUtils.isEmpty(readme)) {
            String [] encodings = settings.getStrings(Keys.web.blobEncodings).toArray(new String[0]);
            String markup = JGitUtils.getStringContent(r, commit.getTree(), readme, encodings);
            return parse(repositoryName, commitId, readme, markup);
        }
        return null;
    }
    public MarkupDocument parse(String repositoryName, String commitId, String documentPath, String markupText) {
        final MarkupSyntax syntax = determineSyntax(documentPath);
        final MarkupDocument doc = new MarkupDocument(documentPath, markupText, syntax);
        if (markupText != null) {
            try {
                switch (syntax){
                case MARKDOWN:
                    parse(doc, repositoryName, commitId);
                    break;
                default:
                    doc.html = MarkdownUtils.transformPlainText(markupText);
                    break;
                }
            } catch (Exception e) {
                logger.error("failed to transform " + syntax, e);
            }
        }
        if (doc.html == null) {
            // failed to transform markup
            if (markupText == null) {
                markupText = String.format("Document <b>%1$s</b> not found in <em>%2$s</em>", documentPath, repositoryName);
            }
            markupText = MessageFormat.format("<div class=\"alert alert-error\"><strong>{0}:</strong> {1}</div>{2}", "Error", "failed to parse markup", markupText);
            doc.html = StringUtils.breakLinesForHtml(markupText);
        }
        return doc;
    }
    /**
     * Parses the document as Markdown using Pegdown.
     *
     * @param doc
     * @param repositoryName
     * @param commitId
     */
    private void parse(final MarkupDocument doc, final String repositoryName, final String commitId) {
        LinkRenderer renderer = new LinkRenderer() {
            @Override
            public Rendering render(WikiLinkNode node) {
                String path = doc.getRelativePath(node.getText());
                String name = getDocumentName(path);
                String url = getWicketUrl(DocPage.class, repositoryName, commitId, path);
                return new Rendering(url, name);
            }
        };
        doc.html = MarkdownUtils.transformMarkdown(doc.markup, renderer);
    }
    private String getWicketUrl(Class<? extends Page> pageClass, final String repositoryName, final String commitId, final String document) {
        String fsc = settings.getString(Keys.web.forwardSlashCharacter, "/");
        String encodedPath = document.replace(' ', '-');
        try {
            encodedPath = URLEncoder.encode(encodedPath, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            logger.error(null, e);
        }
        encodedPath = encodedPath.replace("/", fsc).replace("%2F", fsc);
        String url = RequestCycle.get().urlFor(pageClass, WicketUtils.newPathParameter(repositoryName, commitId, encodedPath)).toString();
        return url;
    }
    private String getDocumentName(final String document) {
        // extract document name
        String name = StringUtils.stripFileExtension(document);
        name = name.replace('_', ' ');
        if (name.indexOf('/') > -1) {
            name = name.substring(name.lastIndexOf('/') + 1);
        }
        return name;
    }
    public static class MarkupDocument implements Serializable {
        private static final long serialVersionUID = 1L;
        public final String documentPath;
        public final String markup;
        public final MarkupSyntax syntax;
        public String html;
        MarkupDocument(String documentPath, String markup, MarkupSyntax syntax) {
            this.documentPath = documentPath;
            this.markup = markup;
            this.syntax = syntax;
        }
        String getCurrentPath() {
            String basePath = "";
            if (documentPath.indexOf('/') > -1) {
                basePath = documentPath.substring(0, documentPath.lastIndexOf('/') + 1);
                if (basePath.charAt(0) == '/') {
                    return basePath.substring(1);
                }
            }
            return basePath;
        }
        String getRelativePath(String ref) {
            return ref.charAt(0) == '/' ? ref.substring(1) : (getCurrentPath() + ref);
        }
    }
}
src/main/java/com/gitblit/wicket/WicketUtils.java
@@ -21,7 +21,6 @@
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TimeZone;
@@ -190,11 +189,10 @@
            return newImage(wicketId, "file_settings_16x16.png");
        }
        List<String> mdExtensions = GitBlit.getStrings(Keys.web.markdownExtensions);
        for (String ext : mdExtensions) {
            if (filename.endsWith('.' + ext.toLowerCase())) {
                return newImage(wicketId, "file_world_16x16.png");
            }
        MarkupProcessor processor = new MarkupProcessor(GitBlit.getSettings());
        String ext = StringUtils.getFileExtension(filename).toLowerCase();
        if (processor.getMarkupExtensions().contains(ext)) {
            return newImage(wicketId, "file_world_16x16.png");
        }
        return newImage(wicketId, "file_16x16.png");
    }
src/main/java/com/gitblit/wicket/pages/BlobPage.java
@@ -35,6 +35,7 @@
import com.gitblit.wicket.CacheControl;
import com.gitblit.wicket.CacheControl.LastModified;
import com.gitblit.wicket.ExternalImage;
import com.gitblit.wicket.MarkupProcessor;
import com.gitblit.wicket.WicketUtils;
import com.gitblit.wicket.panels.CommitHeaderPanel;
import com.gitblit.wicket.panels.PathBreadcrumbsPanel;
@@ -73,10 +74,11 @@
                extension = blobPath.substring(blobPath.lastIndexOf('.') + 1).toLowerCase();
            }
            // see if we should redirect to the markdown page
            for (String ext : GitBlit.getStrings(Keys.web.markdownExtensions)) {
            // see if we should redirect to the doc page
            MarkupProcessor processor = new MarkupProcessor(GitBlit.getSettings());
            for (String ext : processor.getMarkupExtensions()) {
                if (ext.equals(extension)) {
                    setResponsePage(MarkdownPage.class, params);
                    setResponsePage(DocPage.class, params);
                    return;
                }
            }
src/main/java/com/gitblit/wicket/pages/DocPage.html
File was renamed from src/main/java/com/gitblit/wicket/pages/MarkdownPage.html
@@ -6,13 +6,13 @@
<body>
<wicket:extend>
        <!-- markdown nav links -->
        <!-- doc nav links -->
        <div class="page_nav2">
            <a wicket:id="blameLink"><wicket:message key="gb.blame"></wicket:message></a>  | <a wicket:id="historyLink"><wicket:message key="gb.history"></wicket:message></a> | <a wicket:id="rawLink"><wicket:message key="gb.raw"></wicket:message></a> | <a wicket:id="headLink"><wicket:message key="gb.head"></wicket:message></a>
        </div>    
    
        <!--  markdown content -->
        <div class="markdown" style="padding-bottom:5px;" wicket:id="markdownText">[markdown content]</div>
        <!--  document content -->
        <div class="markdown" style="padding-bottom:5px;" wicket:id="content">[content]</div>
</wicket:extend>
</body>
</html>
src/main/java/com/gitblit/wicket/pages/DocPage.java
New file
@@ -0,0 +1,92 @@
/*
 * Copyright 2011 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.wicket.pages;
import java.util.List;
import org.apache.wicket.PageParameters;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.link.BookmarkablePageLink;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import com.gitblit.GitBlit;
import com.gitblit.utils.JGitUtils;
import com.gitblit.utils.StringUtils;
import com.gitblit.wicket.CacheControl;
import com.gitblit.wicket.CacheControl.LastModified;
import com.gitblit.wicket.MarkupProcessor;
import com.gitblit.wicket.MarkupProcessor.MarkupDocument;
import com.gitblit.wicket.WicketUtils;
@CacheControl(LastModified.BOOT)
public class DocPage extends RepositoryPage {
    public DocPage(PageParameters params) {
        super(params);
        final String path = WicketUtils.getPath(params).replace("%2f", "/").replace("%2F", "/");
        MarkupProcessor processor = new MarkupProcessor(GitBlit.getSettings());
        Repository r = getRepository();
        RevCommit commit = JGitUtils.getCommit(r, objectId);
        String [] encodings = GitBlit.getEncodings();
        // Read raw markup content and transform it to html
        String documentPath = path;
        String markupText = JGitUtils.getStringContent(r, commit.getTree(), path, encodings);
        // Hunt for document
        if (StringUtils.isEmpty(markupText)) {
            String name = StringUtils.stripFileExtension(path);
            List<String> docExtensions = processor.getMarkupExtensions();
            for (String ext : docExtensions) {
                String checkName = name + "." + ext;
                markupText = JGitUtils.getStringContent(r, commit.getTree(), checkName, encodings);
                if (!StringUtils.isEmpty(markupText)) {
                    // found it
                    documentPath = path;
                    break;
                }
            }
        }
        // document page links
        add(new BookmarkablePageLink<Void>("blameLink", BlamePage.class,
                WicketUtils.newPathParameter(repositoryName, objectId, documentPath)));
        add(new BookmarkablePageLink<Void>("historyLink", HistoryPage.class,
                WicketUtils.newPathParameter(repositoryName, objectId, documentPath)));
        add(new BookmarkablePageLink<Void>("rawLink", RawPage.class, WicketUtils.newPathParameter(
                repositoryName, objectId, documentPath)));
        add(new BookmarkablePageLink<Void>("headLink", DocPage.class,
                WicketUtils.newPathParameter(repositoryName, Constants.HEAD, documentPath)));
        MarkupDocument markupDoc = processor.parse(repositoryName, getBestCommitId(commit), documentPath, markupText);
        add(new Label("content", markupDoc.html).setEscapeModelStrings(false));
    }
    @Override
    protected String getPageName() {
        return getString("gb.docs");
    }
    @Override
    protected Class<? extends BasePage> getRepoNavPageClass() {
        return DocsPage.class;
    }
}
src/main/java/com/gitblit/wicket/pages/DocsPage.java
@@ -30,14 +30,14 @@
import org.eclipse.jgit.revwalk.RevCommit;
import com.gitblit.GitBlit;
import com.gitblit.Keys;
import com.gitblit.models.PathModel;
import com.gitblit.utils.ByteFormat;
import com.gitblit.utils.JGitUtils;
import com.gitblit.utils.MarkdownUtils;
import com.gitblit.utils.StringUtils;
import com.gitblit.wicket.CacheControl;
import com.gitblit.wicket.CacheControl.LastModified;
import com.gitblit.wicket.MarkupProcessor;
import com.gitblit.wicket.MarkupProcessor.MarkupDocument;
import com.gitblit.wicket.WicketUtils;
import com.gitblit.wicket.panels.LinkPanel;
@@ -47,13 +47,15 @@
    public DocsPage(PageParameters params) {
        super(params);
        MarkupProcessor processor = new MarkupProcessor(GitBlit.getSettings());
        Repository r = getRepository();
        RevCommit head = JGitUtils.getCommit(r, null);
        List<String> extensions = GitBlit.getStrings(Keys.web.markdownExtensions);
        List<String> extensions = processor.getMarkupExtensions();
        List<PathModel> paths = JGitUtils.getDocuments(r, extensions);
        String doc = null;
        String markdown = null;
        String markup = null;
        String html = null;
        List<String> roots = Arrays.asList("home");
@@ -61,9 +63,7 @@
        // try to find a custom index/root page
        for (PathModel path : paths) {
            String name = path.name.toLowerCase();
            if (name.indexOf('.') > -1) {
                name = name.substring(0, name.lastIndexOf('.'));
            }
            name = StringUtils.stripFileExtension(name);
            if (roots.contains(name)) {
                doc = path.name;
                break;
@@ -73,8 +73,11 @@
        if (!StringUtils.isEmpty(doc)) {
            // load the document
            String [] encodings = GitBlit.getEncodings();
            markdown = JGitUtils.getStringContent(r, head.getTree(), doc, encodings);
            html = MarkdownUtils.transformMarkdown(markdown, getMarkdownLinkRenderer());
            markup = JGitUtils.getStringContent(r, head.getTree(), doc, encodings);
            // parse document
            MarkupDocument markupDoc = processor.parse(repositoryName, getBestCommitId(head), doc, markup);
            html = markupDoc.html;
        }
        Fragment fragment = null;
@@ -103,11 +106,11 @@
                PathModel entry = item.getModelObject();
                item.add(WicketUtils.newImage("docIcon", "file_world_16x16.png"));
                item.add(new Label("docSize", byteFormat.format(entry.size)));
                item.add(new LinkPanel("docName", "list", entry.name, MarkdownPage.class, WicketUtils
                item.add(new LinkPanel("docName", "list", entry.name, DocPage.class, WicketUtils
                        .newPathParameter(repositoryName, id, entry.path)));
                // links
                item.add(new BookmarkablePageLink<Void>("view", MarkdownPage.class, WicketUtils
                item.add(new BookmarkablePageLink<Void>("view", DocPage.class, WicketUtils
                        .newPathParameter(repositoryName, id, entry.path)));
                item.add(new BookmarkablePageLink<Void>("raw", RawPage.class, WicketUtils
                        .newPathParameter(repositoryName, id, entry.path)));
src/main/java/com/gitblit/wicket/pages/MarkdownPage.java
File was deleted
src/main/java/com/gitblit/wicket/pages/RepositoryPage.java
@@ -16,8 +16,6 @@
package com.gitblit.wicket.pages;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
@@ -44,8 +42,6 @@
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.pegdown.LinkRenderer;
import org.pegdown.ast.WikiLinkNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -645,47 +641,6 @@
    public boolean isOwner() {
        return isOwner;
    }
    /**
     * Returns a Pegdown/Markdown link renderer which renders WikiLinks.
     *
     * @return a link renderer
     */
    protected LinkRenderer getMarkdownLinkRenderer() {
        RevCommit head = JGitUtils.getCommit(r, "HEAD");
        final String id = getBestCommitId(head);
        LinkRenderer renderer = new LinkRenderer() {
            @Override
            public Rendering render(WikiLinkNode node) {
                try {
                    String fsc = GitBlit.getString(Keys.web.forwardSlashCharacter, "/");
                    // adjust the request path
                    String path = node.getText().charAt(0) == '/' ? node.getText().substring(1) : node.getText();
                    path = URLEncoder.encode(path.replace(' ', '-'), "UTF-8").replace("%2F", fsc);
                    // extract document name
                    String name = node.getText().replace('_', ' ');
                    if (name.indexOf('/') > -1) {
                        name = name.substring(name.lastIndexOf('/') + 1);
                    }
                    // strip Markdown extension
                    for (String ext : GitBlit.getStrings(Keys.web.markdownExtensions)) {
                        String x = "." + ext;
                        if (name.endsWith(x)) {
                            name = name.substring(0, name.length() - x.length());
                            break;
                        }
                    }
                    String url = urlFor(MarkdownPage.class, WicketUtils.newPathParameter(repositoryName, id, path)).toString();
                    return new Rendering(url, name);
                } catch (UnsupportedEncodingException e) {
                    throw new IllegalStateException();
                }
            }
        };
        return renderer;
    }
    private class SearchForm extends SessionlessForm<Void> implements Serializable {
src/main/java/com/gitblit/wicket/pages/SummaryPage.java
@@ -43,15 +43,16 @@
import com.gitblit.GitBlit;
import com.gitblit.Keys;
import com.gitblit.models.Metric;
import com.gitblit.models.PathModel;
import com.gitblit.models.RepositoryModel;
import com.gitblit.models.UserModel;
import com.gitblit.utils.JGitUtils;
import com.gitblit.utils.MarkdownUtils;
import com.gitblit.utils.StringUtils;
import com.gitblit.wicket.CacheControl;
import com.gitblit.wicket.CacheControl.LastModified;
import com.gitblit.wicket.GitBlitWebSession;
import com.gitblit.wicket.MarkupProcessor;
import com.gitblit.wicket.MarkupProcessor.MarkupDocument;
import com.gitblit.wicket.MarkupProcessor.MarkupSyntax;
import com.gitblit.wicket.WicketUtils;
import com.gitblit.wicket.charting.SecureChart;
import com.gitblit.wicket.panels.BranchesPanel;
@@ -137,55 +138,17 @@
        add(new TagsPanel("tagsPanel", repositoryName, r, numberRefs).hideIfEmpty());
        add(new BranchesPanel("branchesPanel", getRepositoryModel(), r, numberRefs, false).hideIfEmpty());
        String htmlText = null;
        String markdownText = null;
        String readme = null;
        boolean isMarkdown = false;
        try {
            RevCommit head = JGitUtils.getCommit(r, null);
            List<String> markdownExtensions = GitBlit.getStrings(Keys.web.markdownExtensions);
            List<PathModel> paths = JGitUtils.getFilesInPath(r, null, head);
            for (PathModel path : paths) {
                if (!path.isTree()) {
                    String name = path.name.toLowerCase();
                    if (name.equals("readme") || name.equals("readme.txt")) {
                        readme = path.name;
                        isMarkdown = false;
                    } else if (name.startsWith("readme")) {
                        if (name.indexOf('.') > -1) {
                            String ext = name.substring(name.lastIndexOf('.') + 1);
                            if (markdownExtensions.contains(ext)) {
                                readme = path.name;
                                isMarkdown = true;
                                break;
                            }
                        }
                    }
                }
            }
            if (!StringUtils.isEmpty(readme)) {
                String [] encodings = GitBlit.getEncodings();
                markdownText = JGitUtils.getStringContent(r, head.getTree(), readme, encodings);
                if (isMarkdown) {
                    htmlText = MarkdownUtils.transformMarkdown(markdownText, getMarkdownLinkRenderer());
                } else {
                    htmlText = MarkdownUtils.transformPlainText(markdownText);
                }
            }
        } catch (Exception e) {
            logger.error("failed to transform markdown", e);
            markdownText = MessageFormat.format("<div class=\"alert alert-error\"><strong>{0}:</strong> {1}</div>{2}", getString("gb.error"), getString("gb.markdownFailure"), markdownText);
            htmlText = MarkdownUtils.transformPlainText(markdownText);
        }
        if (StringUtils.isEmpty(htmlText)) {
        RevCommit head = JGitUtils.getCommit(r, null);
        MarkupProcessor processor = new MarkupProcessor(GitBlit.getSettings());
        MarkupDocument markupDoc = processor.parseReadme(r, repositoryName, getBestCommitId(head));
        if (markupDoc.markup == null) {
            add(new Label("readme").setVisible(false));
        } else {
            Fragment fragment = new Fragment("readme", isMarkdown ? "markdownPanel" : "plaintextPanel", this);
            fragment.add(new Label("readmeFile", readme));
            Fragment fragment = new Fragment("readme", MarkupSyntax.PLAIN.equals(markupDoc.syntax) ? "plaintextPanel" : "markdownPanel", this);
            fragment.add(new Label("readmeFile", markupDoc.documentPath));
            // Add the html to the page
            Component content = new Label("readmeContent", htmlText).setEscapeModelStrings(false);
            fragment.add(content.setVisible(!StringUtils.isEmpty(htmlText)));
            Component content = new Label("readmeContent", markupDoc.html).setEscapeModelStrings(false);
            fragment.add(content.setVisible(!StringUtils.isEmpty(markupDoc.html)));
            add(fragment);
        }
src/main/resources/gitblit.css
@@ -1540,6 +1540,29 @@
    text-decoration: underline;    
}
div.markdown table {
    max-width: 100%;
    background-color: transparent;
    border-collapse: collapse;
    border-spacing: 0px;
    font-size: inherit;
    border-width: 0px 1px 1px 0px;
    border-style: solid solid solid none;
    border-color: rgb(221, 221, 221);
    border-image: none;
    border-collapse: separate;
    margin: 10px 0px 20px;
}
div.markdown table td, div.markdown table th {
    padding: 8px;
    line-height: 20px;
    text-align: left;
    vertical-align: top;
    border-top: 1px solid rgb(221, 221, 221);
    border-left: 1px solid rgb(221, 221, 221);
}
div.markdown table.text th, div.markdown table.text td {
    vertical-align: top;
    border-top: 1px solid #ccc;