From 3df3496185d229e6f1cdeb6f182f7933884ca29c Mon Sep 17 00:00:00 2001
From: James Moger <james.moger@gitblit.com>
Date: Wed, 20 Apr 2011 22:14:37 -0400
Subject: [PATCH] Selectable diff presentation: gitblit, gitweb, or plain.

---
 gitblit.properties                               |    3 
 src/com/gitblit/utils/GitBlitDiffFormatter.java  |  127 +++++++++++++++++++++++++
 src/com/gitblit/wicket/pages/CommitDiffPage.java |    6 +
 src/com/gitblit/tests/JGitUtilsTest.java         |    3 
 src/com/gitblit/wicket/pages/BlobDiffPage.java   |    9 +
 src/com/gitblit/wicket/resources/gitblit.css     |   54 ++++++++++
 src/com/gitblit/utils/GitWebDiffFormatter.java   |    6 
 src/com/gitblit/utils/JGitUtils.java             |   50 +++++++---
 8 files changed, 236 insertions(+), 22 deletions(-)

diff --git a/gitblit.properties b/gitblit.properties
index ae3866d..48515a1 100644
--- a/gitblit.properties
+++ b/gitblit.properties
@@ -59,6 +59,9 @@
 web.datestampShortFormat = yyyy-MM-dd
 web.datetimestampLongFormat = EEEE, MMMM d, yyyy h:mm a z
 
+# Choose the diff presentation style: gitblt, gitweb, or plain
+web.diffStyle = gitblit
+
 # Generates a line graph of repository activity over time on the Summary page.
 # This is a real-time graph so generation may be expensive. 
 web.generateActivityGraph = true
diff --git a/src/com/gitblit/tests/JGitUtilsTest.java b/src/com/gitblit/tests/JGitUtilsTest.java
index f3c798f..196058c 100644
--- a/src/com/gitblit/tests/JGitUtilsTest.java
+++ b/src/com/gitblit/tests/JGitUtilsTest.java
@@ -15,6 +15,7 @@
 import org.eclipse.jgit.storage.file.FileRepository;
 
 import com.gitblit.utils.JGitUtils;
+import com.gitblit.utils.JGitUtils.DiffOutputType;
 import com.gitblit.wicket.models.PathModel.PathChangeModel;
 import com.gitblit.wicket.models.RefModel;
 import com.gitblit.wicket.models.TicketModel;
@@ -98,7 +99,7 @@
 	public void testCommitDiff() throws Exception {
 		Repository r = getRepository();
 		RevCommit commit = JGitUtils.getCommit(r, Constants.HEAD);
-		String diff = JGitUtils.getCommitDiff(r, commit, false);
+		String diff = JGitUtils.getCommitDiff(r, commit, DiffOutputType.PLAIN);
 		r.close();
 		System.out.println(diff);
 	}
diff --git a/src/com/gitblit/utils/GitBlitDiffFormatter.java b/src/com/gitblit/utils/GitBlitDiffFormatter.java
new file mode 100644
index 0000000..54cb6c2
--- /dev/null
+++ b/src/com/gitblit/utils/GitBlitDiffFormatter.java
@@ -0,0 +1,127 @@
+package com.gitblit.utils;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+import org.eclipse.jgit.diff.RawText;
+
+public class GitBlitDiffFormatter extends GitWebDiffFormatter {
+
+	private final OutputStream os;
+
+	private int left = 0, right = 0;
+
+	public GitBlitDiffFormatter(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("<tr><th>..</th><th>..</th><td class='hunk_header'>".getBytes());
+		os.write('@');
+		os.write('@');
+		writeRange('-', aStartLine + 1, aEndLine - aStartLine);
+		writeRange('+', bStartLine + 1, bEndLine - bStartLine);
+		os.write(' ');
+		os.write('@');
+		os.write('@');
+		os.write("</td></tr>\n".getBytes());
+		left = aStartLine + 1;
+		right = bStartLine + 1;
+	}
+
+	@Override
+	protected void writeLine(final char prefix, final RawText text, final int cur) throws IOException {
+		os.write("<tr>".getBytes());
+		switch (prefix) {
+		case '+':
+			os.write(("<th></th><th>" + (right++) + "</th>").getBytes());
+			os.write("<td><div class=\"diff add2\">".getBytes());
+			break;
+		case '-':
+			os.write(("<th>" + (left++) + "</th><th></th>").getBytes());
+			os.write("<td><div class=\"diff remove2\">".getBytes());
+			break;
+		default:
+			os.write(("<th>" + (left++) + "</th><th>" + (right++) + "</th>").getBytes());
+			os.write("<td>".getBytes());
+			break;
+		}
+		os.write(prefix);
+		ByteArrayOutputStream bos = new ByteArrayOutputStream();
+		text.writeLine(bos, cur);
+		String line = bos.toString();
+		line = StringUtils.escapeForHtml(line, false);
+		os.write(line.getBytes());
+		switch (prefix) {
+		case '+':
+		case '-':
+			os.write("</div>".getBytes());
+			break;
+		default:
+			os.write("</td>".getBytes());
+		}
+		os.write("</tr>\n".getBytes());
+	}
+
+	/**
+	 * Workaround function for complex private methods in DiffFormatter. This
+	 * sets the html for the diff headers.
+	 * 
+	 * @return
+	 */
+	@Override
+	public String getHtml() {
+		String html = os.toString();
+		String[] lines = html.split("\n");
+		StringBuilder sb = new StringBuilder();		
+		boolean inFile = false;
+		String oldnull = "a/dev/null";
+		for (String line : lines) {
+			if (line.startsWith("index")) {
+				// skip index lines
+			} else if (line.startsWith("new file")) {
+				// skip new file lines
+			} else if (line.startsWith("diff")) {
+				if (line.indexOf(oldnull) > -1) {
+					// a is null, use b
+					line = line.substring(("diff --git " + oldnull).length()).trim();
+					line = line.substring(2); // trim b/
+				} else {
+					// use a
+					line = line.substring("diff --git a/".length()).trim();
+					line = line.substring(0, line.indexOf(" b/")).trim();
+				}
+				if (inFile) {
+					sb.append("</tbody></table></div>\n");
+					inFile = false;
+				}
+				sb.append("<div class='header'>").append(line).append("</div>");
+				sb.append("<div class=\"diff\">");	
+				sb.append("<table><tbody>");
+				inFile = true;
+			} else if (line.startsWith("---") || line.startsWith("+++")) {
+				// skip --- +++ lines
+			} else {
+				sb.append(line).append('\n');
+			}
+		}
+		sb.append("</table></div>");
+		return sb.toString();
+	}
+}
diff --git a/src/com/gitblit/utils/HtmlDiffFormatter.java b/src/com/gitblit/utils/GitWebDiffFormatter.java
similarity index 93%
rename from src/com/gitblit/utils/HtmlDiffFormatter.java
rename to src/com/gitblit/utils/GitWebDiffFormatter.java
index 24cdd8a..2ac0a56 100644
--- a/src/com/gitblit/utils/HtmlDiffFormatter.java
+++ b/src/com/gitblit/utils/GitWebDiffFormatter.java
@@ -9,11 +9,11 @@
 import org.eclipse.jgit.diff.DiffFormatter;
 import org.eclipse.jgit.diff.RawText;
 
-public class HtmlDiffFormatter extends DiffFormatter {
+public class GitWebDiffFormatter extends DiffFormatter {
 
 	private final OutputStream os;
 
-	public HtmlDiffFormatter(OutputStream os) {
+	public GitWebDiffFormatter(OutputStream os) {
 		super(os);
 		this.os = os;
 	}
@@ -44,7 +44,7 @@
 		os.write("</span></div>".getBytes());
 	}
 
-	private void writeRange(final char prefix, final int begin, final int cnt) throws IOException {
+	protected void writeRange(final char prefix, final int begin, final int cnt) throws IOException {
 		os.write(' ');
 		os.write(prefix);
 		switch (cnt) {
diff --git a/src/com/gitblit/utils/JGitUtils.java b/src/com/gitblit/utils/JGitUtils.java
index daeb638..5118425 100644
--- a/src/com/gitblit/utils/JGitUtils.java
+++ b/src/com/gitblit/utils/JGitUtils.java
@@ -326,19 +326,32 @@
 		return list;
 	}
 
-	public static String getCommitDiff(Repository r, RevCommit commit, boolean outputHtml) {
-		return getCommitDiff(r, null, commit, null, outputHtml);
+	public static enum DiffOutputType {
+		PLAIN, GITWEB, GITBLIT;
+
+		public static DiffOutputType forName(String name) {
+			for (DiffOutputType type : values()) {
+				if (type.name().equalsIgnoreCase(name)) {
+					return type;
+				}
+			}
+			return null;
+		}
 	}
 
-	public static String getCommitDiff(Repository r, RevCommit commit, String path, boolean outputHtml) {
-		return getCommitDiff(r, null, commit, path, outputHtml);
+	public static String getCommitDiff(Repository r, RevCommit commit, DiffOutputType outputType) {
+		return getCommitDiff(r, null, commit, null, outputType);
 	}
 
-	public static String getCommitDiff(Repository r, RevCommit baseCommit, RevCommit commit, boolean outputHtml) {
-		return getCommitDiff(r, baseCommit, commit, null, outputHtml);
+	public static String getCommitDiff(Repository r, RevCommit commit, String path, DiffOutputType outputType) {
+		return getCommitDiff(r, null, commit, path, outputType);
 	}
 
-	public static String getCommitDiff(Repository r, RevCommit baseCommit, RevCommit commit, String path, boolean outputHtml) {
+	public static String getCommitDiff(Repository r, RevCommit baseCommit, RevCommit commit, DiffOutputType outputType) {
+		return getCommitDiff(r, baseCommit, commit, null, outputType);
+	}
+
+	public static String getCommitDiff(Repository r, RevCommit baseCommit, RevCommit commit, String path, DiffOutputType outputType) {
 		try {
 			RevTree baseTree;
 			if (baseCommit == null) {
@@ -362,10 +375,17 @@
 			final ByteArrayOutputStream os = new ByteArrayOutputStream();
 			RawTextComparator cmp = RawTextComparator.DEFAULT;
 			DiffFormatter df;
-			if (outputHtml) {
-				df = new HtmlDiffFormatter(os);
-			} else {
+			switch (outputType) {
+			case GITWEB:
+				df = new GitWebDiffFormatter(os);
+				break;
+			case GITBLIT:
+				df = new GitBlitDiffFormatter(os);
+				break;
+			case PLAIN:
+			default:
 				df = new DiffFormatter(os);
+				break;
 			}
 			df.setRepository(r);
 			df.setDiffComparator(cmp);
@@ -382,9 +402,9 @@
 				df.format(diffs);
 			}
 			String diff;
-			if (outputHtml) {
+			if (df instanceof GitWebDiffFormatter) {
 				// workaround for complex private methods in DiffFormatter
-				diff = ((HtmlDiffFormatter) df).getHtml();
+				diff = ((GitWebDiffFormatter) df).getHtml();
 			} else {
 				diff = os.toString();
 			}
@@ -750,21 +770,21 @@
 				df = new SimpleDateFormat("yyyy-MM");
 			}
 			walk.markStart(lastCommit);
-			
+
 			Iterable<RevCommit> revlog = walk;
 			for (RevCommit rev : revlog) {
 				Date d = getCommitDate(rev);
 				String p = df.format(d);
 				if (!metricMap.containsKey(p))
 					metricMap.put(p, new Metric(p));
-				Metric m = metricMap.get(p); 
+				Metric m = metricMap.get(p);
 				m.count++;
 				total.count++;
 				if (tagMap.containsKey(rev.getId())) {
 					m.tag++;
 					total.tag++;
 				}
-			}			
+			}
 		} catch (Throwable t) {
 			LOGGER.error("Failed to mine log history for metrics", t);
 		}
diff --git a/src/com/gitblit/wicket/pages/BlobDiffPage.java b/src/com/gitblit/wicket/pages/BlobDiffPage.java
index bb47171..99f44db 100644
--- a/src/com/gitblit/wicket/pages/BlobDiffPage.java
+++ b/src/com/gitblit/wicket/pages/BlobDiffPage.java
@@ -6,8 +6,11 @@
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.revwalk.RevCommit;
 
+import com.gitblit.GitBlit;
+import com.gitblit.Keys;
 import com.gitblit.utils.JGitUtils;
 import com.gitblit.utils.StringUtils;
+import com.gitblit.utils.JGitUtils.DiffOutputType;
 import com.gitblit.wicket.LinkPanel;
 import com.gitblit.wicket.RepositoryPage;
 import com.gitblit.wicket.WicketUtils;
@@ -24,15 +27,17 @@
 		Repository r = getRepository();
 		RevCommit commit = JGitUtils.getCommit(r, objectId);
 		
+		DiffOutputType diffType = DiffOutputType.forName(GitBlit.self().settings().getString(Keys.web.diffStyle, DiffOutputType.GITBLIT.name()));
+
 		String diff;
 		if (StringUtils.isEmpty(baseObjectId)) {
 			// use first parent
-			diff = JGitUtils.getCommitDiff(r, commit, blobPath, true);
+			diff = JGitUtils.getCommitDiff(r, commit, blobPath, diffType);
 			add(new BookmarkablePageLink<Void>("patchLink", PatchPage.class, WicketUtils.newPathParameter(repositoryName, objectId, blobPath)));
 		} else {
 			// base commit specified
 			RevCommit baseCommit = JGitUtils.getCommit(r, baseObjectId);
-			diff = JGitUtils.getCommitDiff(r, baseCommit, commit, blobPath, true);
+			diff = JGitUtils.getCommitDiff(r, baseCommit, commit, blobPath, diffType);
 			add(new BookmarkablePageLink<Void>("patchLink", PatchPage.class, WicketUtils.newBlobDiffParameter(repositoryName, baseObjectId, objectId, blobPath)));
 		}
 		
diff --git a/src/com/gitblit/wicket/pages/CommitDiffPage.java b/src/com/gitblit/wicket/pages/CommitDiffPage.java
index d52b7a3..c6f1f7d 100644
--- a/src/com/gitblit/wicket/pages/CommitDiffPage.java
+++ b/src/com/gitblit/wicket/pages/CommitDiffPage.java
@@ -12,7 +12,10 @@
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.revwalk.RevCommit;
 
+import com.gitblit.GitBlit;
+import com.gitblit.Keys;
 import com.gitblit.utils.JGitUtils;
+import com.gitblit.utils.JGitUtils.DiffOutputType;
 import com.gitblit.wicket.LinkPanel;
 import com.gitblit.wicket.RepositoryPage;
 import com.gitblit.wicket.WicketUtils;
@@ -25,7 +28,8 @@
 
 		Repository r = getRepository();
 		RevCommit commit = JGitUtils.getCommit(r, objectId);
-		String diff = JGitUtils.getCommitDiff(r, commit, true);
+		DiffOutputType diffType = DiffOutputType.forName(GitBlit.self().settings().getString(Keys.web.diffStyle, DiffOutputType.GITBLIT.name()));
+		String diff = JGitUtils.getCommitDiff(r, commit, diffType);
 
 		List<String> parents = new ArrayList<String>();
 		if (commit.getParentCount() > 0) {
diff --git a/src/com/gitblit/wicket/resources/gitblit.css b/src/com/gitblit/wicket/resources/gitblit.css
index 3c5aca8..eff6f2a 100644
--- a/src/com/gitblit/wicket/resources/gitblit.css
+++ b/src/com/gitblit/wicket/resources/gitblit.css
@@ -221,6 +221,7 @@
 
 div.diff {
 	font-family: monospace;
+	overflow: auto;
 }
 
 div.diff.header {
@@ -283,6 +284,59 @@
 	font-family: inherit;
 }
 
+div.diff.add2 {
+	background-color: #DDFFDD;
+    font-family: inherit;
+}
+
+div.diff.remove2 {
+	background-color: #FFDDDD;
+    font-family: inherit;
+}
+
+div.diff table {
+	border-right: 1px solid #bbb;
+	border-bottom: 1px solid #bbb;
+	width: 100%;
+}
+
+div.diff table th, div.diff table td {
+	margin: 0px;
+	padding: 0px;
+	font-family: monospace;	
+}
+
+div.diff table th {
+	background-color: #faf8dc;
+	border-left: 1px solid #bbb;	
+	text-align: center;
+	color: #999;
+	padding-left: 5px;
+	padding-right: 5px;
+}
+
+div.diff table th.header {
+	background-color: #D2C3AF;
+	border-right: 0px;
+	border-bottom: 1px solid #808080;
+	font-family: inherit;
+	font-size:0.9em;
+	color: black;
+	padding: 2px;
+	text-align: left;
+}
+
+div.diff table td.hunk_header {
+	background-color: #dAe2e5 !important;	
+	border-bottom: 1px solid #bac2c5;
+	color: #555;
+}
+
+div.diff table td {
+	border-left: 1px solid #bbb;
+	background-color: #fbfbfb;
+}
+
 span.addition, span.modification, span.deletion, span.rename {
 	border: 1px solid #888;
 	float: left;

--
Gitblit v1.9.1