James Moger
2011-04-06 87c3d714693b216cee9c31cdc5170715c56f541d
Mostly working Diff presentation.
3 files added
7 files modified
420 ■■■■■ changed files
resources/gitblit.css 67 ●●●●● patch | view | raw | blame | history
src/com/gitblit/tests/JGitUtilsTest.java 42 ●●●●● patch | view | raw | blame | history
src/com/gitblit/utils/HtmlDiffFormatter.java 128 ●●●●● patch | view | raw | blame | history
src/com/gitblit/utils/JGitUtils.java 95 ●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/GitBlitWebApp.java 2 ●●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/pages/CommitPage.java 2 ●●● patch | view | raw | blame | history
src/com/gitblit/wicket/pages/DiffPage.html 31 ●●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/pages/DiffPage.java 46 ●●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/panels/PathLinksPanel.java 3 ●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/panels/ShortLogLinksPanel.java 4 ●●●● patch | view | raw | blame | history
resources/gitblit.css
@@ -18,8 +18,9 @@
    padding: 0px;
}
pre.prettyprint, pre.plainprint {
pre, pre.prettyprint, pre.plainprint {
    color: black;
    font-family: monospace;
    font-size:12px;
    border:0px;
}
@@ -201,6 +202,70 @@
    text-align: center;
}
div.diff {
    font-family: monospace;
}
div.diff.header {
    -moz-border-bottom-colors: none;
    -moz-border-image: none;
    -moz-border-left-colors: none;
    -moz-border-right-colors: none;
    -moz-border-top-colors: none;
    background-color: #EDECE6;
    border-color: #D9D8D1;
    border-style: solid;
    border-width: 1px 0;
    font-weight: bold;
    margin-top: 4px;
    padding: 4px 0 2px;
}
div.diff.extended_header {
    background-color: #F6F5EE;
    padding: 2px 0;
    font-family: inherit;
}
div.diff.add {
    color: #008800;
    font-family: inherit;
}
div.diff.remove {
    color: #cc0000;
    font-family: inherit;
}
div.diff.unchanged {
    color: inherit;
    font-family: inherit;
}
div.diff.hunk_header {
    -moz-border-bottom-colors: none;
    -moz-border-image: none;
    -moz-border-left-colors: none;
    -moz-border-right-colors: none;
    -moz-border-top-colors: none;
    border-color: #FFE0FF;
    border-style: dotted;
    border-width: 1px 0 0;
    margin-top: 2px;
    font-family: inherit;
}
span.diff.hunk_info {
    background-color: #FFEEFF;
    color: #990099;
    font-family: inherit;
}
span.diff.hunk_section {
    color: #AA22AA;
    font-family: inherit;
}
a.list {
    text-decoration: none;
    color: #000000;
src/com/gitblit/tests/JGitUtilsTest.java
@@ -16,50 +16,50 @@
import com.gitblit.utils.JGitUtils;
import com.gitblit.utils.TicGitTicket;
import com.gitblit.wicket.models.PathModel;
import com.gitblit.wicket.models.RefModel;
public class JGitUtilsTest extends TestCase {
    private File repositoriesFolder = new File("c:/projects/git");
    private boolean exportAll = true;
    private boolean readNested = true;
    private List<String> getRepositories() {
        return JGitUtils.getRepositoryList(repositoriesFolder, exportAll, readNested);
    }
    private Repository getRepository() throws Exception {
        return new FileRepository(new File(repositoriesFolder, getRepositories().get(0)) + "/" + Constants.DOT_GIT);
    }
    public void testFindRepositories() {
        List<String> list = getRepositories();
        assertTrue("No repositories found in " + repositoriesFolder, list.size() > 0);
    }
    public void testOpenRepository() throws Exception {
    public void testOpenRepository() throws Exception {
        Repository r = getRepository();
        r.close();
        assertTrue("Could not find repository!", r != null);
    }
    public void testLastChangeRepository() throws Exception {
    public void testLastChangeRepository() throws Exception {
        Repository r = getRepository();
        Date date = JGitUtils.getLastChange(r);
        r.close();
        assertTrue("Could not get last repository change date!", date != null);
    }
    public void testRetrieveRevObject() throws Exception {
        Repository r = getRepository();
        RevCommit commit = JGitUtils.getCommit(r, Constants.HEAD);
        RevTree tree = commit.getTree();
        RevObject object = JGitUtils.getRevObject(r, tree, "AUTHORS");
        RevObject object = JGitUtils.getRevObject(r, tree, "AUTHORS");
        r.close();
        assertTrue("Object is null!", object != null);
    }
    public void testRetrieveStringContent() throws Exception {
        Repository r = getRepository();
        RevCommit commit = JGitUtils.getCommit(r, Constants.HEAD);
@@ -69,7 +69,7 @@
        r.close();
        assertTrue("Content is null!", content != null);
    }
    public void testTicGit() throws Exception {
        Repository r = new FileRepository(new File(repositoriesFolder, "ticgit") + "/" + Constants.DOT_GIT);
        RefModel ticgit = JGitUtils.getTicGitBranch(r);
@@ -79,4 +79,20 @@
        r.close();
    }
    public void testFilesInCommit() throws Exception {
        Repository r = getRepository();
        RevCommit commit = JGitUtils.getCommit(r, Constants.HEAD);
        List<PathModel> paths = JGitUtils.getFilesInCommit(r, commit);
        r.close();
        assertTrue("No changed paths found!", paths.size() > 0);
    }
    public void testCommitDiff() throws Exception {
        Repository r = getRepository();
        RevCommit commit = JGitUtils.getCommit(r, Constants.HEAD);
        String diff = JGitUtils.getCommitDiff(r, commit, false);
        r.close();
        System.out.println(diff);
    }
}
src/com/gitblit/utils/HtmlDiffFormatter.java
New file
@@ -0,0 +1,128 @@
package com.gitblit.utils;
import static org.eclipse.jgit.lib.Constants.encodeASCII;
import java.io.IOException;
import java.io.OutputStream;
import org.eclipse.jgit.diff.DiffFormatter;
import org.eclipse.jgit.diff.RawText;
public class HtmlDiffFormatter extends DiffFormatter {
    private final OutputStream os;
    public HtmlDiffFormatter(OutputStream os) {
        super(os);
        this.os = os;
    }
    /**
     * Output a hunk header
     *
     * @param aStartLine
     *            within first source
     * @param aEndLine
     *            within first source
     * @param bStartLine
     *            within second source
     * @param bEndLine
     *            within second source
     * @throws IOException
     */
    @Override
    protected void writeHunkHeader(int aStartLine, int aEndLine, int bStartLine, int bEndLine) throws IOException {
        os.write("<div class=\"diff hunk_header\"><span class=\"diff hunk_info\">".getBytes());
        os.write('@');
        os.write('@');
        writeRange('-', aStartLine + 1, aEndLine - aStartLine);
        writeRange('+', bStartLine + 1, bEndLine - bStartLine);
        os.write(' ');
        os.write('@');
        os.write('@');
        // TODO not sure if JGit can determine hunk section
        //os.write("<span class=\"diff hunk_section\">".getBytes());
        //os.write("</span>".getBytes());
        os.write("</span></div>".getBytes());
    }
    private void writeRange(final char prefix, final int begin, final int cnt) throws IOException {
        os.write(' ');
        os.write(prefix);
        switch (cnt) {
        case 0:
            // If the range is empty, its beginning number must
            // be the
            // line just before the range, or 0 if the range is
            // at the
            // start of the file stream. Here, begin is always 1
            // based,
            // so an empty file would produce "0,0".
            //
            os.write(encodeASCII(begin - 1));
            os.write(',');
            os.write('0');
            break;
        case 1:
            // If the range is exactly one line, produce only
            // the number.
            //
            os.write(encodeASCII(begin));
            break;
        default:
            os.write(encodeASCII(begin));
            os.write(',');
            os.write(encodeASCII(cnt));
            break;
        }
    }
    @Override
    protected void writeLine(final char prefix, final RawText text, final int cur) throws IOException {
        switch (prefix) {
        case '+':
            os.write("<div class=\"diff add\">".getBytes());
            break;
        case '-':
            os.write("<div class=\"diff remove\">".getBytes());
            break;
        }
        os.write(prefix);
        text.writeLine(os, cur);
        switch (prefix) {
        case '+':
        case '-':
            os.write("</div>".getBytes());
            break;
        default:
            os.write('\n');
        }
    }
    /**
     * Workaround function for complex private methods in DiffFormatter. This
     * sets the html for the diff headers.
     *
     * @return
     */
    public String getHtml() {
        String html = os.toString();
        String[] lines = html.split("\n");
        StringBuilder sb = new StringBuilder();
        sb.append("<div class=\"diff\">");
        for (String line : lines) {
            if (line.startsWith("diff")) {
                sb.append("<div class=\"diff header\">").append(line).append("</div>");
            } else if (line.startsWith("---")) {
                sb.append("<div class=\"diff remove\">").append(line).append("</div>");
            } else if (line.startsWith("+++")) {
                sb.append("<div class=\"diff add\">").append(line).append("</div>");
            } else {
                sb.append(line).append('\n');
            }
        }
        sb.append("</div>");
        return sb.toString();
    }
}
src/com/gitblit/utils/JGitUtils.java
@@ -17,6 +17,9 @@
import java.util.Set;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.diff.DiffEntry;
import org.eclipse.jgit.diff.DiffFormatter;
import org.eclipse.jgit.diff.RawTextComparator;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.Constants;
@@ -35,6 +38,8 @@
import org.eclipse.jgit.treewalk.TreeWalk;
import org.eclipse.jgit.treewalk.filter.PathFilter;
import org.eclipse.jgit.treewalk.filter.PathFilterGroup;
import org.eclipse.jgit.treewalk.filter.TreeFilter;
import org.eclipse.jgit.util.io.DisabledOutputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -243,29 +248,89 @@
        return list;
    }
    public static List<PathModel> getCommitChangedPaths(Repository r, String commitId) {
    public static List<PathModel> getFilesInCommit(Repository r, String commitId) {
        RevCommit commit = getCommit(r, commitId);
        return getCommitChangedPaths(r, commit);
        return getFilesInCommit(r, commit);
    }
    public static List<PathModel> getCommitChangedPaths(Repository r, RevCommit commit) {
    public static List<PathModel> getFilesInCommit(Repository r, RevCommit commit) {
        List<PathModel> list = new ArrayList<PathModel>();
        final TreeWalk walk = new TreeWalk(r);
        walk.setRecursive(false);
        try {
            walk.addTree(commit.getTree());
            while (walk.next()) {
                list.add(getPathModel(walk, null, commit));
            }
            final RevWalk rw = new RevWalk(r);
            RevCommit parent = rw.parseCommit(commit.getParent(0).getId());
            RevTree parentTree = parent.getTree();
            RevTree commitTree = commit.getTree();
        } catch (IOException e) {
            LOGGER.error("Failed to get files for commit " + commit.getName(), e);
        } finally {
            if (walk != null) {
                walk.release();
            final TreeWalk walk = new TreeWalk(r);
            walk.reset();
            walk.setRecursive(true);
            walk.addTree(parentTree);
            walk.addTree(commitTree);
            walk.setFilter(TreeFilter.ANY_DIFF);
            RawTextComparator cmp = RawTextComparator.DEFAULT;
            DiffFormatter df = new DiffFormatter(DisabledOutputStream.INSTANCE);
            df.setRepository(r);
            df.setDiffComparator(cmp);
            df.setDetectRenames(true);
            List<DiffEntry> diffs = df.scan(parentTree, commitTree);
            for (DiffEntry diff : diffs) {
                list.add(new PathModel(diff.getNewPath(), diff.getNewPath(), 0, diff.getNewMode().getBits(), commit.getId().getName()));
            }
        } catch (Throwable t) {
            LOGGER.error("failed to determine files in commit!", t);
        }
        return list;
    }
    public static String getCommitDiff(Repository r, RevCommit commit, boolean outputHtml) {
        return getCommitDiff(r, commit, null, outputHtml);
    }
    public static String getCommitDiff(Repository r, RevCommit commit, String path, boolean outputHtml) {
        try {
            final RevWalk rw = new RevWalk(r);
            RevCommit parent = rw.parseCommit(commit.getParent(0).getId());
            RevTree parentTree = parent.getTree();
            RevTree commitTree = commit.getTree();
            final TreeWalk walk = new TreeWalk(r);
            walk.reset();
            walk.setRecursive(true);
            walk.addTree(parentTree);
            walk.addTree(commitTree);
            if (path != null && path.trim().length() > 0) {
                walk.setFilter(PathFilter.create(path));
            } else {
                walk.setFilter(TreeFilter.ANY_DIFF);
            }
            final ByteArrayOutputStream os = new ByteArrayOutputStream();
            RawTextComparator cmp = RawTextComparator.DEFAULT;
            DiffFormatter df;
            if (outputHtml) {
                df = new HtmlDiffFormatter(os);
            } else {
                df = new DiffFormatter(os);
            }
            df.setRepository(r);
            df.setDiffComparator(cmp);
            df.setDetectRenames(true);
            List<DiffEntry> diffs = df.scan(parentTree, commitTree);
            df.format(diffs);
            String diff;
            if (outputHtml) {
                // workaround for complex private methods in DiffFormatter
                diff = ((HtmlDiffFormatter) df).getHtml();
            } else {
                diff = os.toString();
            }
            df.flush();
            return diff;
        } catch (Throwable t) {
            LOGGER.error("failed to generate commit diff!", t);
        }
        return null;
    }
    private static PathModel getPathModel(TreeWalk walk, String basePath, RevCommit commit) {
@@ -519,7 +584,7 @@
        }
        return null;
    }
    private static void readTicketContents(Repository r, RefModel ticgitBranch, TicGitTicket ticket) {
        List<PathModel> ticketFiles = getFilesInPath(r, ticket.name, ticgitBranch.getCommit());
        for (PathModel file : ticketFiles) {
src/com/gitblit/wicket/GitBlitWebApp.java
@@ -31,6 +31,7 @@
import com.gitblit.wicket.pages.BlobPage;
import com.gitblit.wicket.pages.BranchesPage;
import com.gitblit.wicket.pages.CommitPage;
import com.gitblit.wicket.pages.DiffPage;
import com.gitblit.wicket.pages.RepositoriesPage;
import com.gitblit.wicket.pages.ShortLogPage;
import com.gitblit.wicket.pages.SummaryPage;
@@ -69,6 +70,7 @@
        mount(new MixedParamUrlCodingStrategy("/tag", TagPage.class, new String[] { "p", "h" }));
        mount(new MixedParamUrlCodingStrategy("/tree", TreePage.class, new String[] { "p", "h", "f" }));
        mount(new MixedParamUrlCodingStrategy("/blob", BlobPage.class, new String[] { "p", "h", "f" }));
        mount(new MixedParamUrlCodingStrategy("/diff", DiffPage.class, new String[] { "p", "h", "f" }));
        
        // setup extended urls
        mount(new MixedParamUrlCodingStrategy("/ticgit", TicGitPage.class, new String[] { "p" }));
src/com/gitblit/wicket/pages/CommitPage.java
@@ -76,7 +76,7 @@
        addFullText("fullMessage", c.getFullMessage(), true);
        // changed paths list
        List<PathModel> paths  = JGitUtils.getCommitChangedPaths(r, c);
        List<PathModel> paths  = JGitUtils.getFilesInCommit(r, c);
        ListDataProvider<PathModel> pathsDp = new ListDataProvider<PathModel>(paths);
        DataView<PathModel> pathsView = new DataView<PathModel>("changedPath", pathsDp) {
            private static final long serialVersionUID = 1L;
src/com/gitblit/wicket/pages/DiffPage.html
New file
@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head>
       <link href="prettify/prettify.css" type="text/css" rel="stylesheet" />
    <script type="text/javascript" src="prettify/prettify.js"></script>
</head>
<body onload="prettyPrint()">
    <!-- page header -->
    <div wicket:id="pageHeader"></div>
    <!-- page nav links -->
    <div wicket:id="pageLinks"></div>
    <!-- blob nav links -->
    <div class="page_nav2">
        <span wicket:id="historyLink"></span> | <span wicket:id="rawLink"></span> | <span wicket:id="headLink"></span>
    </div>
    <!-- shortlog header -->
    <div class="header" wicket:id="shortlog"></div>
    <!-- breadcrumbs -->
    <div wicket:id="breadcrumbs"></div>
    <!--  diff content -->
    <pre wicket:id="diffText"></pre>
    <!-- footer -->
    <div wicket:id="pageFooter"></div>
</body>
</html>
src/com/gitblit/wicket/pages/DiffPage.java
New file
@@ -0,0 +1,46 @@
package com.gitblit.wicket.pages;
import org.apache.wicket.PageParameters;
import org.apache.wicket.markup.html.basic.Label;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import com.gitblit.utils.JGitUtils;
import com.gitblit.wicket.LinkPanel;
import com.gitblit.wicket.RepositoryPage;
import com.gitblit.wicket.panels.PathBreadcrumbsPanel;
public class DiffPage extends RepositoryPage {
    public DiffPage(PageParameters params) {
        super(params, "diff");
        final String blobPath = params.getString("f", null);
        Repository r = getRepository();
        RevCommit commit = JGitUtils.getCommit(r, commitId);
        String diff;
        if (blobPath != null && blobPath.length() > 0) {
            // blob diff
            diff = JGitUtils.getCommitDiff(r, commit, blobPath, true);
        } else {
            // commit diff
            diff = JGitUtils.getCommitDiff(r, commit, true);
        }
        r.close();
        // diff page links
        add(new Label("historyLink", "history"));
        add(new Label("rawLink", "raw"));
        add(new Label("headLink", "HEAD"));
        add(new LinkPanel("shortlog", "title", commit.getShortMessage(), CommitPage.class, newCommitParameter()));
        add(new PathBreadcrumbsPanel("breadcrumbs", repositoryName, blobPath, commitId));
        add(new Label("diffText", diff).setEscapeModelStrings(false));
        // footer
        addFooter();
    }
}
src/com/gitblit/wicket/panels/PathLinksPanel.java
@@ -7,6 +7,7 @@
import com.gitblit.wicket.LinkPanel;
import com.gitblit.wicket.models.PathModel;
import com.gitblit.wicket.pages.BlobPage;
import com.gitblit.wicket.pages.DiffPage;
public class PathLinksPanel extends Panel {
@@ -15,7 +16,7 @@
    public PathLinksPanel(String id, String repositoryName, PathModel path) {
        super(id);
        add(new Label("diff", "diff"));
        add(new LinkPanel("diff", null, "diff", DiffPage.class, new PageParameters("p=" + repositoryName + ",h=" + path.commitId + ",f=" + path.path)));
        add(new LinkPanel("blob", null, "view", BlobPage.class, new PageParameters("p=" + repositoryName + ",h=" + path.commitId + ",f=" + path.path)));
        add(new Label("history", "history"));
    }
src/com/gitblit/wicket/panels/ShortLogLinksPanel.java
@@ -1,11 +1,11 @@
package com.gitblit.wicket.panels;
import org.apache.wicket.PageParameters;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.panel.Panel;
import com.gitblit.wicket.LinkPanel;
import com.gitblit.wicket.pages.CommitPage;
import com.gitblit.wicket.pages.DiffPage;
import com.gitblit.wicket.pages.TreePage;
@@ -17,7 +17,7 @@
        super(id);
        add(new LinkPanel("commit", null, "commit", CommitPage.class, new PageParameters("p=" + repositoryName + ",h=" + commitId)));
        add(new Label("commitdiff", "commitdiff"));
        add(new LinkPanel("commitdiff", null, "commitdiff", DiffPage.class, new PageParameters("p=" + repositoryName + ",h=" + commitId)));
        add(new LinkPanel("tree", null, "tree", TreePage.class, new PageParameters("p=" + repositoryName + ",h=" + commitId)));
    }
}