From df0ba7f7ff02ed02c0ba7714ae928a79d932baef Mon Sep 17 00:00:00 2001 From: Tom <tw201207@gmail.com> Date: Thu, 06 Nov 2014 12:05:33 -0500 Subject: [PATCH] Improve the commitdiff. --- src/main/java/com/gitblit/utils/ResettableByteArrayOutputStream.java | 42 ++ src/main/resources/gitblit.css | 89 ++--- src/main/java/com/gitblit/utils/GitBlitDiffFormatter.java | 709 +++++++++++++++++++++++++++++++--------------- src/main/java/com/gitblit/wicket/GitBlitWebApp_fr.properties | 7 src/main/java/com/gitblit/wicket/GitBlitWebApp.properties | 9 src/main/java/com/gitblit/utils/DiffUtils.java | 7 src/main/java/com/gitblit/wicket/GitBlitWebApp_de.properties | 7 7 files changed, 585 insertions(+), 285 deletions(-) diff --git a/src/main/java/com/gitblit/utils/DiffUtils.java b/src/main/java/com/gitblit/utils/DiffUtils.java index dd2a780..f597b94 100644 --- a/src/main/java/com/gitblit/utils/DiffUtils.java +++ b/src/main/java/com/gitblit/utils/DiffUtils.java @@ -228,15 +228,16 @@ DiffStat stat = null; String diff = null; try { - final ByteArrayOutputStream os = new ByteArrayOutputStream(); + ByteArrayOutputStream os = null; RawTextComparator cmp = RawTextComparator.DEFAULT; DiffFormatter df; switch (outputType) { case HTML: - df = new GitBlitDiffFormatter(os, commit.getName()); + df = new GitBlitDiffFormatter(commit.getName(), path); break; case PLAIN: default: + os = new ByteArrayOutputStream(); df = new DiffFormatter(os); break; } @@ -271,6 +272,7 @@ } else { df.format(diffEntries); } + df.flush(); if (df instanceof GitBlitDiffFormatter) { // workaround for complex private methods in DiffFormatter diff = ((GitBlitDiffFormatter) df).getHtml(); @@ -278,7 +280,6 @@ } else { diff = os.toString(); } - df.flush(); } catch (Throwable t) { LOGGER.error("failed to generate commit diff!", t); } diff --git a/src/main/java/com/gitblit/utils/GitBlitDiffFormatter.java b/src/main/java/com/gitblit/utils/GitBlitDiffFormatter.java index 47ff143..0b16c37 100644 --- a/src/main/java/com/gitblit/utils/GitBlitDiffFormatter.java +++ b/src/main/java/com/gitblit/utils/GitBlitDiffFormatter.java @@ -1,236 +1,473 @@ -/* - * 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; - } -} +/* + * 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.IOException; +import java.text.MessageFormat; +import java.util.HashMap; +import java.util.Map; + +import org.apache.wicket.Application; +import org.apache.wicket.Localizer; +import org.eclipse.jgit.diff.DiffEntry; +import org.eclipse.jgit.diff.DiffEntry.ChangeType; +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; +import com.gitblit.wicket.GitBlitWebApp; + +/** + * Generates an html snippet of a diff in Gitblit's style, tracks changed paths, and calculates diff stats. + * + * @author James Moger + * @author Tom <tw201207@gmail.com> + * + */ +public class GitBlitDiffFormatter extends DiffFormatter { + + /** + * gitblit.properties key for the per-file limit on the number of diff lines. + */ + private static final String DIFF_LIMIT_PER_FILE_KEY = "web.maxDiffLinesPerFile"; + + /** + * gitblit.properties key for the global limit on the number of diff lines in a commitdiff. + */ + private static final String GLOBAL_DIFF_LIMIT_KEY = "web.maxDiffLines"; + + /** + * Diffs with more lines are not shown in commitdiffs. (Similar to what GitHub does.) Can be reduced + * (but not increased) through gitblit.properties key {@link #DIFF_LIMIT_PER_FILE_KEY}. + */ + private static final int DIFF_LIMIT_PER_FILE = 4000; + + /** + * Global diff limit. Commitdiffs with more lines are truncated. Can be reduced (but not increased) + * through gitblit.properties key {@link #GLOBAL_DIFF_LIMIT_KEY}. + */ + private static final int GLOBAL_DIFF_LIMIT = 20000; + + private final ResettableByteArrayOutputStream os; + + private final DiffStat diffStat; + + private PathChangeModel currentPath; + + private int left, right; + + /** + * If a single file diff in a commitdiff produces more than this number of lines, we don't display + * the diff. First, it's too taxing on the browser: it'll spend an awful lot of time applying the + * CSS rules (despite my having optimized them). And second, no human can read a diff with thousands + * of lines and make sense of it. + * <p> + * Set to {@link #DIFF_LIMIT_PER_FILE} for commitdiffs, and to -1 (switches off the limit) for + * single-file diffs. + * </p> + */ + private final int maxDiffLinesPerFile; + + /** + * Global limit on the number of diff lines. Set to {@link #GLOBAL_DIFF_LIMIT} for commitdiffs, and + * to -1 (switched off the limit) for single-file diffs. + */ + private final int globalDiffLimit; + + /** Number of lines for the current file diff. Set to zero when a new DiffEntry is started. */ + private int nofLinesCurrent; + /** + * Position in the stream when we try to write the first line. Used to rewind when we detect that + * the diff is too large. + */ + private int startCurrent; + /** Flag set to true when we rewind. Reset to false when we start a new DiffEntry. */ + private boolean isOff; + /** The current diff entry. */ + private DiffEntry entry; + + // Global limit stuff. + + /** Total number of lines written before the current diff entry. */ + private int totalNofLinesPrevious; + /** Running total of the number of diff lines written. Updated until we exceed the global limit. */ + private int totalNofLinesCurrent; + /** Stream position to reset to if we decided to truncate the commitdiff. */ + private int truncateTo; + /** Whether we decided to truncate the commitdiff. */ + private boolean truncated; + /** If {@link #truncated}, contains all files skipped,possibly with a suffix message as value to be displayed. */ + private final Map<String, String> skipped = new HashMap<String, String>(); + + public GitBlitDiffFormatter(String commitId, String path) { + super(new ResettableByteArrayOutputStream()); + this.os = (ResettableByteArrayOutputStream) getOutputStream(); + this.diffStat = new DiffStat(commitId); + // If we have a full commitdiff, install maxima to avoid generating a super-long diff listing that + // will only tax the browser too much. + maxDiffLinesPerFile = path != null ? -1 : getLimit(DIFF_LIMIT_PER_FILE_KEY, 500, DIFF_LIMIT_PER_FILE); + globalDiffLimit = path != null ? -1 : getLimit(GLOBAL_DIFF_LIMIT_KEY, 1000, GLOBAL_DIFF_LIMIT); + } + + /** + * Determines a limit to use for HTML diff output. + * + * @param key + * to use to read the value from the GitBlit settings, if available. + * @param minimum + * minimum value to enforce + * @param maximum + * maximum (and default) value to enforce + * @return the limit + */ + private int getLimit(String key, int minimum, int maximum) { + if (Application.exists()) { + Application application = Application.get(); + if (application instanceof GitBlitWebApp) { + GitBlitWebApp webApp = (GitBlitWebApp) application; + int configValue = webApp.settings().getInteger(key, maximum); + if (configValue < minimum) { + return minimum; + } else if (configValue < maximum) { + return configValue; + } + } + } + return maximum; + } + + /** + * Returns a localized message string, if there is a localization; otherwise the given default value. + * + * @param key + * message key for the message + * @param defaultValue + * to use if no localization for the message can be found + * @return the possibly localized message + */ + private String getMsg(String key, String defaultValue) { + if (Application.exists()) { + Localizer localizer = Application.get().getResourceSettings().getLocalizer(); + if (localizer != null) { + // Use getStringIgnoreSettings because we don't want exceptions here if the key is missing! + return localizer.getStringIgnoreSettings(key, null, null, defaultValue); + } + } + return defaultValue; + } + + @Override + public void format(DiffEntry ent) throws IOException { + currentPath = diffStat.addPath(ent); + nofLinesCurrent = 0; + isOff = false; + entry = ent; + if (!truncated) { + totalNofLinesPrevious = totalNofLinesCurrent; + if (globalDiffLimit > 0 && totalNofLinesPrevious > globalDiffLimit) { + truncated = true; + isOff = true; + } + truncateTo = os.size(); + } else { + isOff = true; + } + if (isOff) { + if (ent.getChangeType().equals(ChangeType.DELETE)) { + skipped.put(ent.getOldPath(), getMsg("gb.diffDeletedFileSkipped", "(deleted file)")); + } else { + skipped.put(ent.getNewPath(), null); + } + } + // Keep formatting, but if off, don't produce anything anymore. We just keep on counting. + super.format(ent); + } + + @Override + public void flush() throws IOException { + if (truncated) { + os.resetTo(truncateTo); + } + super.flush(); + } + + /** + * Rewind and issue a message that the diff is too large. + */ + private void reset() { + if (!isOff) { + os.resetTo(startCurrent); + try { + os.write("<tr><td class='diff-cell' colspan='4'>".getBytes()); + os.write(StringUtils.escapeForHtml(getMsg("gb.diffFileDiffTooLarge", "Diff too large"), false).getBytes()); + os.write("</td></tr>\n".getBytes()); + } catch (IOException ex) { + // Cannot happen with a ByteArrayOutputStream + } + totalNofLinesCurrent = totalNofLinesPrevious; + isOff = true; + } + } + + /** + * Writes an initial table row containing information about added/removed/renamed/copied files. In case + * of a deletion, we also suppress generating the diff; it's not interesting. (All lines removed.) + */ + private void handleChange() { + // XXX Would be nice if we could generate blob links for the cases handled here. Alas, we lack the repo + // name, and cannot reliably determine it here. We could get the .git directory of a Repository, if we + // passed in the repo, and then take the name of the parent directory, but that'd fail for repos nested + // in GitBlit projects. And we don't know if the repo is inside a project or is a top-level repo. + // + // That's certainly solvable (just pass along more information), but would require a larger rewrite than + // I'm prepared to do now. + String message; + switch (entry.getChangeType()) { + case ADD: + message = getMsg("gb.diffNewFile", "New file"); + break; + case DELETE: + message = getMsg("gb.diffDeletedFile", "File was deleted"); + isOff = true; + break; + case RENAME: + message = MessageFormat.format(getMsg("gb.diffRenamedFile", "File was renamed from {0}"), entry.getOldPath()); + break; + case COPY: + message = MessageFormat.format(getMsg("gb.diffCopiedFile", "File was copied from {0}"), entry.getOldPath()); + break; + default: + return; + } + try { + os.write("<tr><td class='diff-cell' colspan='4'>".getBytes()); + os.write(StringUtils.escapeForHtml(message, false).getBytes()); + os.write("</td></tr>\n".getBytes()); + } catch (IOException ex) { + // Cannot happen with a ByteArrayOutputStream + } + } + + /** + * 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 { + if (nofLinesCurrent++ == 0) { + handleChange(); + startCurrent = os.size(); + } + if (!isOff) { + totalNofLinesCurrent++; + if (nofLinesCurrent > maxDiffLinesPerFile && maxDiffLinesPerFile > 0) { + reset(); + } else { + os.write("<tr><th class='diff-line' data-lineno='..'></th><th class='diff-line' data-lineno='..'></th><th class='diff-state'></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 { + if (nofLinesCurrent++ == 0) { + handleChange(); + startCurrent = os.size(); + } + // update entry diffstat + currentPath.update(prefix); + if (isOff) { + return; + } + totalNofLinesCurrent++; + if (nofLinesCurrent > maxDiffLinesPerFile && maxDiffLinesPerFile > 0) { + reset(); + } else { + // output diff + os.write("<tr>".getBytes()); + switch (prefix) { + case '+': + os.write(("<th class='diff-line'></th><th class='diff-line' data-lineno='" + (right++) + "'></th>").getBytes()); + os.write("<th class='diff-state diff-state-add'></th>".getBytes()); + os.write("<td class='diff-cell add2'>".getBytes()); + break; + case '-': + os.write(("<th class='diff-line' data-lineno='" + (left++) + "'></th><th class='diff-line'></th>").getBytes()); + os.write("<th class='diff-state diff-state-sub'></th>".getBytes()); + os.write("<td class='diff-cell remove2'>".getBytes()); + break; + default: + os.write(("<th class='diff-line' data-lineno='" + (left++) + "'></th><th class='diff-line' data-lineno='" + (right++) + "'></th>").getBytes()); + os.write("<th class='diff-state'></th>".getBytes()); + os.write("<td class='diff-cell context2'>".getBytes()); + break; + } + String line = text.getString(cur); + line = StringUtils.escapeForHtml(line, false); + os.write(encode(line)); + os.write("</td></tr>\n".getBytes()); + } + } + + /** + * Workaround function for complex private methods in DiffFormatter. This sets the html for the diff headers. + * + * @return + */ + public String getHtml() { + String html = RawParseUtils.decode(os.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") || line.startsWith("deleted 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 class='diff-line'></th><th class='diff-line'></th>"); + if (line.charAt(0) == '+') { + sb.append("<th class='diff-state diff-state-add'></th><td class=\"diff-cell add2\">"); + } else { + sb.append("<th class='diff-state diff-state-sub'></th><td class=\"diff-cell remove2\">"); + } + } + sb.append(line); + if (gitLinkDiff) { + sb.append("</td></tr>"); + } + } + } + sb.append("</tbody></table></div>"); + if (truncated) { + sb.append(MessageFormat.format("<div class='header'><div class='diffHeader'>{0}</div></div>", + StringUtils.escapeForHtml(getMsg("gb.diffTruncated", "Diff truncated after the above file"), false))); + // List all files not shown. We can be sure we do have at least one path in skipped. + sb.append("<div class='diff'><table><tbody><tr><td class='diff-cell' colspan='4'>"); + boolean first = true; + for (Map.Entry<String, String> s : skipped.entrySet()) { + if (!first) { + sb.append('\n'); + } + String path = StringUtils.escapeForHtml(s.getKey(), false); + String comment = s.getValue(); + if (comment != null) { + sb.append("<span id='" + path + "'>" + path + ' ' + StringUtils.escapeForHtml(comment, false) + "</span>"); + } else { + sb.append("<span id='" + path + "'>" + path + "</span>"); + } + first = false; + } + sb.append("</td></tr></tbody></table></div>"); + } + return sb.toString(); + } + + public DiffStat getDiffStat() { + return diffStat; + } +} diff --git a/src/main/java/com/gitblit/utils/ResettableByteArrayOutputStream.java b/src/main/java/com/gitblit/utils/ResettableByteArrayOutputStream.java new file mode 100644 index 0000000..3a2553f --- /dev/null +++ b/src/main/java/com/gitblit/utils/ResettableByteArrayOutputStream.java @@ -0,0 +1,42 @@ +// Copyright (C) 2014 Tom <tw201207@gmail.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; +package com.gitblit.utils; + +import java.io.ByteArrayOutputStream; + +/** + * A {@link ByteArrayOutputStream} that can be reset to a specified position. + * + * @author Tom <tw201207@gmail.com> + */ +public class ResettableByteArrayOutputStream extends ByteArrayOutputStream { + + /** + * Reset the stream to the given position. If {@code mark} is <= 0, see {@link #reset()}. + * A no-op if the stream contains less than {@code mark} bytes. Otherwise, resets the + * current writing position to {@code mark}. Previously allocated buffer space will be + * reused in subsequent writes. + * + * @param mark + * to set the current writing position to. + */ + public synchronized void resetTo(int mark) { + if (mark <= 0) { + reset(); + } else if (mark < count) { + count = mark; + } + } + +} diff --git a/src/main/java/com/gitblit/wicket/GitBlitWebApp.properties b/src/main/java/com/gitblit/wicket/GitBlitWebApp.properties index ce7b522..cfec39e 100644 --- a/src/main/java/com/gitblit/wicket/GitBlitWebApp.properties +++ b/src/main/java/com/gitblit/wicket/GitBlitWebApp.properties @@ -749,4 +749,11 @@ gb.sortLowestPriority = lowest priority gb.sortHighestSeverity = highest severity gb.sortLowestSeverity = lowest severity -gb.missingIntegrationBranchMore = The target integration branch does not exist in the repository! \ No newline at end of file +gb.missingIntegrationBranchMore = The target integration branch does not exist in the repository! +gb.diffDeletedFileSkipped = (deleted file) +gb.diffFileDiffTooLarge = Diff too large +gb.diffNewFile = New file +gb.diffDeletedFile = File was deleted +gb.diffRenamedFile = File was renamed from {0} +gb.diffCopiedFile = File was copied from {0} +gb.diffTruncated = Diff truncated after the above file diff --git a/src/main/java/com/gitblit/wicket/GitBlitWebApp_de.properties b/src/main/java/com/gitblit/wicket/GitBlitWebApp_de.properties index 3ec330b..da437c7 100644 --- a/src/main/java/com/gitblit/wicket/GitBlitWebApp_de.properties +++ b/src/main/java/com/gitblit/wicket/GitBlitWebApp_de.properties @@ -743,3 +743,10 @@ gb.sshKeyPermissionDescription = Geben Sie die Zugriffberechtigung f\u00fcr den SSH Key an gb.transportPreference = \u00dcbertragungseinstellungen gb.transportPreferenceDescription = Geben Sie die \u00dcbertragungsart an, die Sie f\u00fcr das Klonen bevorzugen +gb.diffDeletedFileSkipped = (gel\u00f6schte Datei) +gb.diffFileDiffTooLarge = Zu viele \u00c4nderungen; Diff wird nicht angezeigt +gb.diffNewFile = Neue Datei +gb.diffDeletedFile = Datei wurde gel\u00f6scht +gb.diffRenamedFile = Datei umbenannt von {0} +gb.diffCopiedFile = Datei kopiert von {0} +gb.diffTruncated = Diff nach obiger Datei abgeschnitten diff --git a/src/main/java/com/gitblit/wicket/GitBlitWebApp_fr.properties b/src/main/java/com/gitblit/wicket/GitBlitWebApp_fr.properties index b888bee..67855a0 100644 --- a/src/main/java/com/gitblit/wicket/GitBlitWebApp_fr.properties +++ b/src/main/java/com/gitblit/wicket/GitBlitWebApp_fr.properties @@ -672,3 +672,10 @@ gb.mergeToDescription = branche d'int\u00e9gration par d\u00e9faut pour fusionner les correctifs li\u00e9s aux tickets gb.myTickets = mes tickets gb.yourAssignedTickets = dont vous \u00eates responsable +gb.diffDeletedFileSkipped = (fichier effac\u00e9) +gb.diffFileDiffTooLarge = Trop de diff\u00e9rences, affichage supprim\u00e9e +gb.diffNewFile = Nouveau fichier +gb.diffDeletedFile = Fichier a \u00e9t\u00e9 effac\u00e9 +gb.diffRenamedFile = Fichier renomm\u00e9 de {0} +gb.diffCopiedFile = Fichier copi\u00e9 de {0} +gb.diffTruncated = Affichage de diff\u00e9rences supprim\u00e9e apr\u00e8s le fichier ci-dessus diff --git a/src/main/resources/gitblit.css b/src/main/resources/gitblit.css index 2484b0c..43b1be8 100644 --- a/src/main/resources/gitblit.css +++ b/src/main/resources/gitblit.css @@ -1350,19 +1350,6 @@ 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; @@ -1374,60 +1361,72 @@ font-family: inherit; } -div.diff.add2 { - background-color: #DDFFDD; - font-family: inherit; +.diff-cell { + margin: 0px; + padding: 0px; + border: 0; + border-left: 1px solid #bbb; } -div.diff.remove2 { +.add2 { + background-color: #DDFFDD; +} + +.remove2 { background-color: #FFDDDD; - font-family: inherit; } -div.diff table { +.context2 { + background-color: #fbfbfb; +} + +div.diff > table { border-radius: 0; 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; - border: 0; -} - -div.diff table th { +.diff-line { background-color: #f0f0f0; text-align: center; color: #999; - padding-left: 5px; - padding-right: 5px; - width: 30px; + padding-left: 2px; + padding-right: 2px; + width: 3em; /* Font-size relative! */ } -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; +.diff-line:before { + content: attr(data-lineno); } -div.diff table td.hunk_header { +.diff-state { + background-color: #f0f0f0; + text-align: center; + color: #999; + padding-left: 2px; + padding-right: 2px; + width: 0.5em; /* Font-size relative! */ +} + +.diff-state-add:before { + color: green; + font-weight: bold; + content: '+'; +} + +.diff-state-sub:before { + color: red; + font-weight: bold; + content: '-'; +} + +.hunk_header { background-color: #dAe2e5 !important; + border-left: 1px solid #bbb; border-top: 1px solid #bac2c5; border-bottom: 1px solid #bac2c5; color: #555; -} - -div.diff table td { - border-left: 1px solid #bbb; - background-color: #fbfbfb; } td.changeType { -- Gitblit v1.9.1