commit | author | age
|
df0ba7
|
1 |
/* |
T |
2 |
* Copyright 2011 gitblit.com. |
|
3 |
* |
|
4 |
* Licensed under the Apache License, Version 2.0 (the "License"); |
|
5 |
* you may not use this file except in compliance with the License. |
|
6 |
* You may obtain a copy of the License at |
|
7 |
* |
|
8 |
* http://www.apache.org/licenses/LICENSE-2.0 |
|
9 |
* |
|
10 |
* Unless required by applicable law or agreed to in writing, software |
|
11 |
* distributed under the License is distributed on an "AS IS" BASIS, |
|
12 |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
13 |
* See the License for the specific language governing permissions and |
|
14 |
* limitations under the License. |
|
15 |
*/ |
|
16 |
package com.gitblit.utils; |
|
17 |
|
|
18 |
import static org.eclipse.jgit.lib.Constants.encode; |
|
19 |
import static org.eclipse.jgit.lib.Constants.encodeASCII; |
|
20 |
|
|
21 |
import java.io.IOException; |
7dd99f
|
22 |
import java.nio.charset.StandardCharsets; |
df0ba7
|
23 |
import java.text.MessageFormat; |
f0ebfe
|
24 |
import java.util.ArrayList; |
7dd99f
|
25 |
import java.util.Arrays; |
f0ebfe
|
26 |
import java.util.List; |
6235fa
|
27 |
import java.util.regex.Matcher; |
T |
28 |
import java.util.regex.Pattern; |
df0ba7
|
29 |
|
T |
30 |
import org.apache.wicket.Application; |
|
31 |
import org.apache.wicket.Localizer; |
|
32 |
import org.eclipse.jgit.diff.DiffEntry; |
|
33 |
import org.eclipse.jgit.diff.DiffEntry.ChangeType; |
|
34 |
import org.eclipse.jgit.diff.DiffFormatter; |
|
35 |
import org.eclipse.jgit.diff.RawText; |
|
36 |
import org.eclipse.jgit.util.RawParseUtils; |
|
37 |
|
|
38 |
import com.gitblit.models.PathModel.PathChangeModel; |
7dd99f
|
39 |
import com.gitblit.utils.DiffUtils.BinaryDiffHandler; |
df0ba7
|
40 |
import com.gitblit.utils.DiffUtils.DiffStat; |
T |
41 |
import com.gitblit.wicket.GitBlitWebApp; |
|
42 |
|
|
43 |
/** |
|
44 |
* Generates an html snippet of a diff in Gitblit's style, tracks changed paths, and calculates diff stats. |
6235fa
|
45 |
* |
df0ba7
|
46 |
* @author James Moger |
T |
47 |
* @author Tom <tw201207@gmail.com> |
6235fa
|
48 |
* |
df0ba7
|
49 |
*/ |
T |
50 |
public class GitBlitDiffFormatter extends DiffFormatter { |
6235fa
|
51 |
|
T |
52 |
/** Regex pattern identifying trailing whitespace. */ |
|
53 |
private static final Pattern trailingWhitespace = Pattern.compile("(\\s+?)\r?\n?$"); |
df0ba7
|
54 |
|
T |
55 |
/** |
|
56 |
* gitblit.properties key for the per-file limit on the number of diff lines. |
|
57 |
*/ |
|
58 |
private static final String DIFF_LIMIT_PER_FILE_KEY = "web.maxDiffLinesPerFile"; |
|
59 |
|
|
60 |
/** |
|
61 |
* gitblit.properties key for the global limit on the number of diff lines in a commitdiff. |
|
62 |
*/ |
|
63 |
private static final String GLOBAL_DIFF_LIMIT_KEY = "web.maxDiffLines"; |
|
64 |
|
|
65 |
/** |
|
66 |
* Diffs with more lines are not shown in commitdiffs. (Similar to what GitHub does.) Can be reduced |
|
67 |
* (but not increased) through gitblit.properties key {@link #DIFF_LIMIT_PER_FILE_KEY}. |
|
68 |
*/ |
|
69 |
private static final int DIFF_LIMIT_PER_FILE = 4000; |
|
70 |
|
|
71 |
/** |
|
72 |
* Global diff limit. Commitdiffs with more lines are truncated. Can be reduced (but not increased) |
|
73 |
* through gitblit.properties key {@link #GLOBAL_DIFF_LIMIT_KEY}. |
|
74 |
*/ |
|
75 |
private static final int GLOBAL_DIFF_LIMIT = 20000; |
|
76 |
|
8621be
|
77 |
private static final boolean CONVERT_TABS = true; |
JM |
78 |
|
7dd99f
|
79 |
private final DiffOutputStream os; |
df0ba7
|
80 |
|
T |
81 |
private final DiffStat diffStat; |
|
82 |
|
|
83 |
private PathChangeModel currentPath; |
|
84 |
|
|
85 |
private int left, right; |
|
86 |
|
|
87 |
/** |
|
88 |
* If a single file diff in a commitdiff produces more than this number of lines, we don't display |
|
89 |
* the diff. First, it's too taxing on the browser: it'll spend an awful lot of time applying the |
|
90 |
* CSS rules (despite my having optimized them). And second, no human can read a diff with thousands |
|
91 |
* of lines and make sense of it. |
|
92 |
* <p> |
|
93 |
* Set to {@link #DIFF_LIMIT_PER_FILE} for commitdiffs, and to -1 (switches off the limit) for |
|
94 |
* single-file diffs. |
|
95 |
* </p> |
|
96 |
*/ |
|
97 |
private final int maxDiffLinesPerFile; |
|
98 |
|
|
99 |
/** |
|
100 |
* Global limit on the number of diff lines. Set to {@link #GLOBAL_DIFF_LIMIT} for commitdiffs, and |
|
101 |
* to -1 (switched off the limit) for single-file diffs. |
|
102 |
*/ |
|
103 |
private final int globalDiffLimit; |
|
104 |
|
|
105 |
/** Number of lines for the current file diff. Set to zero when a new DiffEntry is started. */ |
|
106 |
private int nofLinesCurrent; |
|
107 |
/** |
|
108 |
* Position in the stream when we try to write the first line. Used to rewind when we detect that |
|
109 |
* the diff is too large. |
|
110 |
*/ |
|
111 |
private int startCurrent; |
|
112 |
/** Flag set to true when we rewind. Reset to false when we start a new DiffEntry. */ |
|
113 |
private boolean isOff; |
|
114 |
/** The current diff entry. */ |
|
115 |
private DiffEntry entry; |
|
116 |
|
|
117 |
// Global limit stuff. |
|
118 |
|
|
119 |
/** Total number of lines written before the current diff entry. */ |
|
120 |
private int totalNofLinesPrevious; |
|
121 |
/** Running total of the number of diff lines written. Updated until we exceed the global limit. */ |
|
122 |
private int totalNofLinesCurrent; |
|
123 |
/** Stream position to reset to if we decided to truncate the commitdiff. */ |
|
124 |
private int truncateTo; |
|
125 |
/** Whether we decided to truncate the commitdiff. */ |
|
126 |
private boolean truncated; |
f0ebfe
|
127 |
/** If {@link #truncated}, contains all entries skipped. */ |
T |
128 |
private final List<DiffEntry> skipped = new ArrayList<DiffEntry>(); |
df0ba7
|
129 |
|
310a80
|
130 |
private int tabLength; |
JM |
131 |
|
7dd99f
|
132 |
/** |
T |
133 |
* A {@link ResettableByteArrayOutputStream} that intercept the "Binary files differ" message produced |
|
134 |
* by the super implementation. Unfortunately the super implementation has far too many things private; |
|
135 |
* otherwise we'd just have re-implemented {@link GitBlitDiffFormatter#format(DiffEntry) format(DiffEntry)} |
|
136 |
* completely without ever calling the super implementation. |
|
137 |
*/ |
|
138 |
private static class DiffOutputStream extends ResettableByteArrayOutputStream { |
|
139 |
|
|
140 |
private static final String BINARY_DIFFERENCE = "Binary files differ\n"; |
|
141 |
|
|
142 |
private GitBlitDiffFormatter formatter; |
|
143 |
private BinaryDiffHandler binaryDiffHandler; |
|
144 |
|
|
145 |
public void setFormatter(GitBlitDiffFormatter formatter, BinaryDiffHandler handler) { |
|
146 |
this.formatter = formatter; |
|
147 |
this.binaryDiffHandler = handler; |
|
148 |
} |
|
149 |
|
|
150 |
@Override |
|
151 |
public void write(byte[] b, int offset, int length) { |
|
152 |
if (binaryDiffHandler != null |
|
153 |
&& RawParseUtils.decode(Arrays.copyOfRange(b, offset, offset + length)).contains(BINARY_DIFFERENCE)) |
|
154 |
{ |
|
155 |
String binaryDiff = binaryDiffHandler.renderBinaryDiff(formatter.entry); |
|
156 |
if (binaryDiff != null) { |
3e1336
|
157 |
byte[] bb = ("<tr><td colspan='4' align='center'>" + binaryDiff + "</td></tr>").getBytes(StandardCharsets.UTF_8); |
7dd99f
|
158 |
super.write(bb, 0, bb.length); |
T |
159 |
return; |
|
160 |
} |
|
161 |
} |
|
162 |
super.write(b, offset, length); |
|
163 |
} |
|
164 |
|
|
165 |
} |
|
166 |
|
310a80
|
167 |
public GitBlitDiffFormatter(String commitId, String path, BinaryDiffHandler handler, int tabLength) { |
7dd99f
|
168 |
super(new DiffOutputStream()); |
T |
169 |
this.os = (DiffOutputStream) getOutputStream(); |
|
170 |
this.os.setFormatter(this, handler); |
df0ba7
|
171 |
this.diffStat = new DiffStat(commitId); |
310a80
|
172 |
this.tabLength = tabLength; |
df0ba7
|
173 |
// If we have a full commitdiff, install maxima to avoid generating a super-long diff listing that |
T |
174 |
// will only tax the browser too much. |
|
175 |
maxDiffLinesPerFile = path != null ? -1 : getLimit(DIFF_LIMIT_PER_FILE_KEY, 500, DIFF_LIMIT_PER_FILE); |
|
176 |
globalDiffLimit = path != null ? -1 : getLimit(GLOBAL_DIFF_LIMIT_KEY, 1000, GLOBAL_DIFF_LIMIT); |
|
177 |
} |
|
178 |
|
|
179 |
/** |
|
180 |
* Determines a limit to use for HTML diff output. |
6235fa
|
181 |
* |
df0ba7
|
182 |
* @param key |
T |
183 |
* to use to read the value from the GitBlit settings, if available. |
|
184 |
* @param minimum |
|
185 |
* minimum value to enforce |
|
186 |
* @param maximum |
|
187 |
* maximum (and default) value to enforce |
|
188 |
* @return the limit |
|
189 |
*/ |
|
190 |
private int getLimit(String key, int minimum, int maximum) { |
|
191 |
if (Application.exists()) { |
|
192 |
Application application = Application.get(); |
|
193 |
if (application instanceof GitBlitWebApp) { |
|
194 |
GitBlitWebApp webApp = (GitBlitWebApp) application; |
|
195 |
int configValue = webApp.settings().getInteger(key, maximum); |
|
196 |
if (configValue < minimum) { |
|
197 |
return minimum; |
|
198 |
} else if (configValue < maximum) { |
|
199 |
return configValue; |
|
200 |
} |
|
201 |
} |
|
202 |
} |
|
203 |
return maximum; |
|
204 |
} |
|
205 |
|
|
206 |
/** |
|
207 |
* Returns a localized message string, if there is a localization; otherwise the given default value. |
6235fa
|
208 |
* |
df0ba7
|
209 |
* @param key |
T |
210 |
* message key for the message |
|
211 |
* @param defaultValue |
|
212 |
* to use if no localization for the message can be found |
|
213 |
* @return the possibly localized message |
|
214 |
*/ |
|
215 |
private String getMsg(String key, String defaultValue) { |
|
216 |
if (Application.exists()) { |
|
217 |
Localizer localizer = Application.get().getResourceSettings().getLocalizer(); |
|
218 |
if (localizer != null) { |
|
219 |
// Use getStringIgnoreSettings because we don't want exceptions here if the key is missing! |
|
220 |
return localizer.getStringIgnoreSettings(key, null, null, defaultValue); |
|
221 |
} |
|
222 |
} |
|
223 |
return defaultValue; |
|
224 |
} |
|
225 |
|
|
226 |
@Override |
|
227 |
public void format(DiffEntry ent) throws IOException { |
|
228 |
currentPath = diffStat.addPath(ent); |
|
229 |
nofLinesCurrent = 0; |
|
230 |
isOff = false; |
|
231 |
entry = ent; |
|
232 |
if (!truncated) { |
|
233 |
totalNofLinesPrevious = totalNofLinesCurrent; |
|
234 |
if (globalDiffLimit > 0 && totalNofLinesPrevious > globalDiffLimit) { |
|
235 |
truncated = true; |
|
236 |
isOff = true; |
|
237 |
} |
|
238 |
truncateTo = os.size(); |
|
239 |
} else { |
|
240 |
isOff = true; |
|
241 |
} |
f0ebfe
|
242 |
if (truncated) { |
T |
243 |
skipped.add(ent); |
|
244 |
} else { |
|
245 |
// Produce a header here and now |
|
246 |
String path; |
|
247 |
String id; |
|
248 |
if (ChangeType.DELETE.equals(ent.getChangeType())) { |
|
249 |
path = ent.getOldPath(); |
|
250 |
id = ent.getOldId().name(); |
df0ba7
|
251 |
} else { |
f0ebfe
|
252 |
path = ent.getNewPath(); |
T |
253 |
id = ent.getNewId().name(); |
df0ba7
|
254 |
} |
f0ebfe
|
255 |
StringBuilder sb = new StringBuilder(MessageFormat.format("<div class='header'><div class=\"diffHeader\" id=\"n{0}\"><i class=\"icon-file\"></i> ", id)); |
T |
256 |
sb.append(StringUtils.escapeForHtml(path, false)).append("</div></div>"); |
599827
|
257 |
sb.append("<div class=\"diff\"><table cellpadding='0'><tbody>\n"); |
f0ebfe
|
258 |
os.write(sb.toString().getBytes()); |
df0ba7
|
259 |
} |
T |
260 |
// Keep formatting, but if off, don't produce anything anymore. We just keep on counting. |
|
261 |
super.format(ent); |
f0ebfe
|
262 |
if (!truncated) { |
T |
263 |
// Close the table |
7dd99f
|
264 |
os.write("</tbody></table></div>\n".getBytes()); |
f0ebfe
|
265 |
} |
df0ba7
|
266 |
} |
T |
267 |
|
|
268 |
@Override |
|
269 |
public void flush() throws IOException { |
|
270 |
if (truncated) { |
|
271 |
os.resetTo(truncateTo); |
|
272 |
} |
|
273 |
super.flush(); |
|
274 |
} |
|
275 |
|
|
276 |
/** |
|
277 |
* Rewind and issue a message that the diff is too large. |
|
278 |
*/ |
|
279 |
private void reset() { |
|
280 |
if (!isOff) { |
|
281 |
os.resetTo(startCurrent); |
7dd99f
|
282 |
writeFullWidthLine(getMsg("gb.diffFileDiffTooLarge", "Diff too large")); |
df0ba7
|
283 |
totalNofLinesCurrent = totalNofLinesPrevious; |
T |
284 |
isOff = true; |
|
285 |
} |
|
286 |
} |
|
287 |
|
|
288 |
/** |
|
289 |
* Writes an initial table row containing information about added/removed/renamed/copied files. In case |
|
290 |
* of a deletion, we also suppress generating the diff; it's not interesting. (All lines removed.) |
|
291 |
*/ |
|
292 |
private void handleChange() { |
|
293 |
// XXX Would be nice if we could generate blob links for the cases handled here. Alas, we lack the repo |
|
294 |
// name, and cannot reliably determine it here. We could get the .git directory of a Repository, if we |
|
295 |
// passed in the repo, and then take the name of the parent directory, but that'd fail for repos nested |
|
296 |
// in GitBlit projects. And we don't know if the repo is inside a project or is a top-level repo. |
|
297 |
// |
|
298 |
// That's certainly solvable (just pass along more information), but would require a larger rewrite than |
|
299 |
// I'm prepared to do now. |
|
300 |
String message; |
|
301 |
switch (entry.getChangeType()) { |
|
302 |
case ADD: |
|
303 |
message = getMsg("gb.diffNewFile", "New file"); |
|
304 |
break; |
|
305 |
case DELETE: |
|
306 |
message = getMsg("gb.diffDeletedFile", "File was deleted"); |
|
307 |
isOff = true; |
|
308 |
break; |
|
309 |
case RENAME: |
|
310 |
message = MessageFormat.format(getMsg("gb.diffRenamedFile", "File was renamed from {0}"), entry.getOldPath()); |
|
311 |
break; |
|
312 |
case COPY: |
|
313 |
message = MessageFormat.format(getMsg("gb.diffCopiedFile", "File was copied from {0}"), entry.getOldPath()); |
|
314 |
break; |
|
315 |
default: |
|
316 |
return; |
|
317 |
} |
7dd99f
|
318 |
writeFullWidthLine(message); |
df0ba7
|
319 |
} |
T |
320 |
|
|
321 |
/** |
|
322 |
* Output a hunk header |
6235fa
|
323 |
* |
df0ba7
|
324 |
* @param aStartLine |
T |
325 |
* within first source |
|
326 |
* @param aEndLine |
|
327 |
* within first source |
|
328 |
* @param bStartLine |
|
329 |
* within second source |
|
330 |
* @param bEndLine |
|
331 |
* within second source |
|
332 |
* @throws IOException |
|
333 |
*/ |
|
334 |
@Override |
|
335 |
protected void writeHunkHeader(int aStartLine, int aEndLine, int bStartLine, int bEndLine) throws IOException { |
|
336 |
if (nofLinesCurrent++ == 0) { |
|
337 |
handleChange(); |
|
338 |
startCurrent = os.size(); |
|
339 |
} |
|
340 |
if (!isOff) { |
|
341 |
totalNofLinesCurrent++; |
|
342 |
if (nofLinesCurrent > maxDiffLinesPerFile && maxDiffLinesPerFile > 0) { |
|
343 |
reset(); |
|
344 |
} else { |
|
345 |
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'>" |
|
346 |
.getBytes()); |
|
347 |
os.write('@'); |
|
348 |
os.write('@'); |
|
349 |
writeRange('-', aStartLine + 1, aEndLine - aStartLine); |
|
350 |
writeRange('+', bStartLine + 1, bEndLine - bStartLine); |
|
351 |
os.write(' '); |
|
352 |
os.write('@'); |
|
353 |
os.write('@'); |
|
354 |
os.write("</td></tr>\n".getBytes()); |
|
355 |
} |
|
356 |
} |
|
357 |
left = aStartLine + 1; |
|
358 |
right = bStartLine + 1; |
|
359 |
} |
|
360 |
|
|
361 |
protected void writeRange(final char prefix, final int begin, final int cnt) throws IOException { |
|
362 |
os.write(' '); |
|
363 |
os.write(prefix); |
|
364 |
switch (cnt) { |
|
365 |
case 0: |
|
366 |
// If the range is empty, its beginning number must be the |
|
367 |
// line just before the range, or 0 if the range is at the |
|
368 |
// start of the file stream. Here, begin is always 1 based, |
|
369 |
// so an empty file would produce "0,0". |
|
370 |
// |
|
371 |
os.write(encodeASCII(begin - 1)); |
|
372 |
os.write(','); |
|
373 |
os.write('0'); |
|
374 |
break; |
|
375 |
|
|
376 |
case 1: |
|
377 |
// If the range is exactly one line, produce only the number. |
|
378 |
// |
|
379 |
os.write(encodeASCII(begin)); |
|
380 |
break; |
|
381 |
|
|
382 |
default: |
|
383 |
os.write(encodeASCII(begin)); |
|
384 |
os.write(','); |
|
385 |
os.write(encodeASCII(cnt)); |
|
386 |
break; |
7dd99f
|
387 |
} |
T |
388 |
} |
|
389 |
|
|
390 |
/** |
|
391 |
* Writes a line spanning the full width of the code view, including the gutter. |
|
392 |
* |
|
393 |
* @param text |
|
394 |
* to put on that line; will be HTML-escaped. |
|
395 |
*/ |
|
396 |
private void writeFullWidthLine(String text) { |
|
397 |
try { |
|
398 |
os.write("<tr><td class='diff-cell' colspan='4'>".getBytes()); |
|
399 |
os.write(StringUtils.escapeForHtml(text, false).getBytes()); |
|
400 |
os.write("</td></tr>\n".getBytes()); |
|
401 |
} catch (IOException ex) { |
|
402 |
// Cannot happen with a ByteArrayOutputStream |
df0ba7
|
403 |
} |
T |
404 |
} |
|
405 |
|
|
406 |
@Override |
|
407 |
protected void writeLine(final char prefix, final RawText text, final int cur) throws IOException { |
|
408 |
if (nofLinesCurrent++ == 0) { |
|
409 |
handleChange(); |
|
410 |
startCurrent = os.size(); |
|
411 |
} |
|
412 |
// update entry diffstat |
|
413 |
currentPath.update(prefix); |
|
414 |
if (isOff) { |
|
415 |
return; |
|
416 |
} |
|
417 |
totalNofLinesCurrent++; |
|
418 |
if (nofLinesCurrent > maxDiffLinesPerFile && maxDiffLinesPerFile > 0) { |
|
419 |
reset(); |
|
420 |
} else { |
|
421 |
// output diff |
|
422 |
os.write("<tr>".getBytes()); |
|
423 |
switch (prefix) { |
|
424 |
case '+': |
|
425 |
os.write(("<th class='diff-line'></th><th class='diff-line' data-lineno='" + (right++) + "'></th>").getBytes()); |
|
426 |
os.write("<th class='diff-state diff-state-add'></th>".getBytes()); |
|
427 |
os.write("<td class='diff-cell add2'>".getBytes()); |
|
428 |
break; |
|
429 |
case '-': |
|
430 |
os.write(("<th class='diff-line' data-lineno='" + (left++) + "'></th><th class='diff-line'></th>").getBytes()); |
|
431 |
os.write("<th class='diff-state diff-state-sub'></th>".getBytes()); |
|
432 |
os.write("<td class='diff-cell remove2'>".getBytes()); |
|
433 |
break; |
|
434 |
default: |
|
435 |
os.write(("<th class='diff-line' data-lineno='" + (left++) + "'></th><th class='diff-line' data-lineno='" + (right++) + "'></th>").getBytes()); |
|
436 |
os.write("<th class='diff-state'></th>".getBytes()); |
|
437 |
os.write("<td class='diff-cell context2'>".getBytes()); |
|
438 |
break; |
|
439 |
} |
6235fa
|
440 |
os.write(encode(codeLineToHtml(prefix, text.getString(cur)))); |
df0ba7
|
441 |
os.write("</td></tr>\n".getBytes()); |
T |
442 |
} |
|
443 |
} |
|
444 |
|
|
445 |
/** |
6235fa
|
446 |
* Convert the given code line to HTML. |
T |
447 |
* |
|
448 |
* @param prefix |
|
449 |
* the diff prefix (+/-) indicating whether the line was added or removed. |
|
450 |
* @param line |
|
451 |
* the line to format as HTML |
|
452 |
* @return the HTML-formatted line, safe for inserting as is into HTML. |
|
453 |
*/ |
|
454 |
private String codeLineToHtml(final char prefix, final String line) { |
|
455 |
if ((prefix == '+' || prefix == '-')) { |
|
456 |
// Highlight trailing whitespace on deleted/added lines. |
|
457 |
Matcher matcher = trailingWhitespace.matcher(line); |
|
458 |
if (matcher.find()) { |
310a80
|
459 |
StringBuilder result = new StringBuilder(StringUtils.escapeForHtml(line.substring(0, matcher.start()), CONVERT_TABS, tabLength)); |
6235fa
|
460 |
result.append("<span class='trailingws-").append(prefix == '+' ? "add" : "sub").append("'>"); |
T |
461 |
result.append(StringUtils.escapeForHtml(matcher.group(1), false)); |
|
462 |
result.append("</span>"); |
|
463 |
return result.toString(); |
|
464 |
} |
|
465 |
} |
310a80
|
466 |
return StringUtils.escapeForHtml(line, CONVERT_TABS, tabLength); |
6235fa
|
467 |
} |
T |
468 |
|
|
469 |
/** |
df0ba7
|
470 |
* Workaround function for complex private methods in DiffFormatter. This sets the html for the diff headers. |
6235fa
|
471 |
* |
df0ba7
|
472 |
* @return |
T |
473 |
*/ |
|
474 |
public String getHtml() { |
|
475 |
String html = RawParseUtils.decode(os.toByteArray()); |
|
476 |
String[] lines = html.split("\n"); |
|
477 |
StringBuilder sb = new StringBuilder(); |
|
478 |
for (String line : lines) { |
864428
|
479 |
if (line.startsWith("index") || line.startsWith("similarity") |
JM |
480 |
|| line.startsWith("rename from ") || line.startsWith("rename to ")) { |
df0ba7
|
481 |
// skip index lines |
T |
482 |
} else if (line.startsWith("new file") || line.startsWith("deleted file")) { |
|
483 |
// skip new file lines |
|
484 |
} else if (line.startsWith("\\ No newline")) { |
|
485 |
// skip no new line |
|
486 |
} else if (line.startsWith("---") || line.startsWith("+++")) { |
|
487 |
// skip --- +++ lines |
|
488 |
} else if (line.startsWith("diff")) { |
f0ebfe
|
489 |
// skip diff lines |
df0ba7
|
490 |
} else { |
T |
491 |
boolean gitLinkDiff = line.length() > 0 && line.substring(1).startsWith("Subproject commit"); |
|
492 |
if (gitLinkDiff) { |
|
493 |
sb.append("<tr><th class='diff-line'></th><th class='diff-line'></th>"); |
|
494 |
if (line.charAt(0) == '+') { |
|
495 |
sb.append("<th class='diff-state diff-state-add'></th><td class=\"diff-cell add2\">"); |
|
496 |
} else { |
|
497 |
sb.append("<th class='diff-state diff-state-sub'></th><td class=\"diff-cell remove2\">"); |
|
498 |
} |
310a80
|
499 |
line = StringUtils.escapeForHtml(line.substring(1), CONVERT_TABS, tabLength); |
df0ba7
|
500 |
} |
T |
501 |
sb.append(line); |
|
502 |
if (gitLinkDiff) { |
|
503 |
sb.append("</td></tr>"); |
|
504 |
} |
7dd99f
|
505 |
sb.append('\n'); |
df0ba7
|
506 |
} |
T |
507 |
} |
|
508 |
if (truncated) { |
|
509 |
sb.append(MessageFormat.format("<div class='header'><div class='diffHeader'>{0}</div></div>", |
|
510 |
StringUtils.escapeForHtml(getMsg("gb.diffTruncated", "Diff truncated after the above file"), false))); |
|
511 |
// List all files not shown. We can be sure we do have at least one path in skipped. |
599827
|
512 |
sb.append("<div class='diff'><table cellpadding='0'><tbody><tr><td class='diff-cell' colspan='4'>"); |
f0ebfe
|
513 |
String deletedSuffix = StringUtils.escapeForHtml(getMsg("gb.diffDeletedFileSkipped", "(deleted)"), false); |
df0ba7
|
514 |
boolean first = true; |
f0ebfe
|
515 |
for (DiffEntry entry : skipped) { |
df0ba7
|
516 |
if (!first) { |
T |
517 |
sb.append('\n'); |
|
518 |
} |
f0ebfe
|
519 |
if (ChangeType.DELETE.equals(entry.getChangeType())) { |
T |
520 |
sb.append("<span id=\"n" + entry.getOldId().name() + "\">" + StringUtils.escapeForHtml(entry.getOldPath(), false) + ' ' + deletedSuffix + "</span>"); |
df0ba7
|
521 |
} else { |
f0ebfe
|
522 |
sb.append("<span id=\"n" + entry.getNewId().name() + "\">" + StringUtils.escapeForHtml(entry.getNewPath(), false) + "</span>"); |
df0ba7
|
523 |
} |
T |
524 |
first = false; |
|
525 |
} |
f0ebfe
|
526 |
skipped.clear(); |
df0ba7
|
527 |
sb.append("</td></tr></tbody></table></div>"); |
T |
528 |
} |
|
529 |
return sb.toString(); |
|
530 |
} |
|
531 |
|
|
532 |
public DiffStat getDiffStat() { |
|
533 |
return diffStat; |
|
534 |
} |
|
535 |
} |