/* | |
* 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.utils; | |
import static org.eclipse.jgit.lib.Constants.encode; | |
import static org.eclipse.jgit.lib.Constants.encodeASCII; | |
import java.io.ByteArrayOutputStream; | |
import java.io.IOException; | |
import java.io.OutputStream; | |
import java.text.MessageFormat; | |
import org.eclipse.jgit.diff.DiffEntry; | |
import org.eclipse.jgit.diff.DiffFormatter; | |
import org.eclipse.jgit.diff.RawText; | |
import org.eclipse.jgit.util.RawParseUtils; | |
import com.gitblit.models.PathModel.PathChangeModel; | |
import com.gitblit.utils.DiffUtils.DiffStat; | |
/** | |
* Generates an html snippet of a diff in Gitblit's style, tracks changed paths, | |
* and calculates diff stats. | |
* | |
* @author James Moger | |
* | |
*/ | |
public class GitBlitDiffFormatter extends DiffFormatter { | |
private final OutputStream os; | |
private final DiffStat diffStat; | |
private PathChangeModel currentPath; | |
private int left, right; | |
public GitBlitDiffFormatter(OutputStream os, String commitId) { | |
super(os); | |
this.os = os; | |
this.diffStat = new DiffStat(commitId); | |
} | |
@Override | |
public void format(DiffEntry ent) throws IOException { | |
currentPath = diffStat.addPath(ent); | |
super.format(ent); | |
} | |
/** | |
* 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; | |
} | |
protected 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 { | |
// update entry diffstat | |
currentPath.update(prefix); | |
// output diff | |
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); | |
String line = text.getString(cur); | |
line = StringUtils.escapeForHtml(line, false); | |
os.write(encode(line)); | |
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 | |
*/ | |
public String getHtml() { | |
ByteArrayOutputStream bos = (ByteArrayOutputStream) os; | |
String html = RawParseUtils.decode(bos.toByteArray()); | |
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("\\ No newline")) { | |
// skip no new line | |
} else if (line.startsWith("---") || line.startsWith("+++")) { | |
// skip --- +++ lines | |
} else if (line.startsWith("diff")) { | |
line = StringUtils.convertOctal(line); | |
if (line.indexOf(oldnull) > -1) { | |
// a is null, use b | |
line = line.substring(("diff --git " + oldnull).length()).trim(); | |
// trim b/ | |
line = line.substring(2).trim(); | |
} else { | |
// use a | |
line = line.substring("diff --git ".length()).trim(); | |
line = line.substring(line.startsWith("\"a/") ? 3 : 2); | |
line = line.substring(0, line.indexOf(" b/") > -1 ? line.indexOf(" b/") : line.indexOf("\"b/")).trim(); | |
} | |
if (line.charAt(0) == '"') { | |
line = line.substring(1); | |
} | |
if (line.charAt(line.length() - 1) == '"') { | |
line = line.substring(0, line.length() - 1); | |
} | |
if (inFile) { | |
sb.append("</tbody></table></div>\n"); | |
inFile = false; | |
} | |
sb.append(MessageFormat.format("<div class='header'><div class=\"diffHeader\" id=\"{0}\"><i class=\"icon-file\"></i> ", line)).append(line).append("</div></div>"); | |
sb.append("<div class=\"diff\">"); | |
sb.append("<table><tbody>"); | |
inFile = true; | |
} else { | |
boolean gitLinkDiff = line.length() > 0 && line.substring(1).startsWith("Subproject commit"); | |
if (gitLinkDiff) { | |
sb.append("<tr><th></th><th></th>"); | |
if (line.charAt(0) == '+') { | |
sb.append("<td><div class=\"diff add2\">"); | |
} else { | |
sb.append("<td><div class=\"diff remove2\">"); | |
} | |
} | |
sb.append(line); | |
if (gitLinkDiff) { | |
sb.append("</div></td></tr>"); | |
} | |
} | |
} | |
sb.append("</table></div>"); | |
return sb.toString(); | |
} | |
public DiffStat getDiffStat() { | |
return diffStat; | |
} | |
} |