author | Bernhard Reutner-Fischer <rep.dot.nop@gmail.com> | 2010-12-23 11:47:55 (UTC) |
---|---|---|
committer | Lars Hjemli <hjemli@gmail.com> | 2011-02-19 13:41:39 (UTC) |
commit | e52040bf90b034aebbfa32756638a24d6b1b3712 (patch) (side-by-side diff) | |
tree | ba2db3037cb486fb40a8c85a62b5a393d5035924 | |
parent | 808c685ebb7cd2d24d3881b74e3be2439bd1393b (diff) | |
download | cgit-e52040bf90b034aebbfa32756638a24d6b1b3712.zip cgit-e52040bf90b034aebbfa32756638a24d6b1b3712.tar.gz cgit-e52040bf90b034aebbfa32756638a24d6b1b3712.tar.bz2 |
ssdiff: anchors for ssdiff
Emit anchors to the respective revisions in side-by-side diff view
Signed-off-by: Bernhard Reutner-Fischer <rep.dot.nop@gmail.com>
Signed-off-by: Lars Hjemli <hjemli@gmail.com>
-rw-r--r-- | cgit.css | 2 | ||||
-rw-r--r-- | ui-diff.c | 12 | ||||
-rw-r--r-- | ui-diff.h | 6 | ||||
-rw-r--r-- | ui-ssdiff.c | 34 |
4 files changed, 43 insertions, 11 deletions
@@ -1,759 +1,759 @@ body, table, form { padding: 0em; margin: 0em; } body { font-family: sans-serif; font-size: 10pt; color: #333; background: white; padding: 4px; } a { color: blue; text-decoration: none; } a:hover { text-decoration: underline; } table { border-collapse: collapse; } table#header { width: 100%; margin-bottom: 1em; } table#header td.logo { width: 96px; } table#header td.main { font-size: 250%; padding-left: 10px; white-space: nowrap; } table#header td.main a { color: #000; } table#header td.form { text-align: right; vertical-align: bottom; padding-right: 1em; padding-bottom: 2px; white-space: nowrap; } table#header td.form form, table#header td.form input, table#header td.form select { font-size: 90%; } table#header td.sub { color: #777; border-top: solid 1px #ccc; padding-left: 10px; } table.tabs { border-bottom: solid 3px #ccc; border-collapse: collapse; margin-top: 2em; margin-bottom: 0px; width: 100%; } table.tabs td { padding: 0px 1em; vertical-align: bottom; } table.tabs td a { padding: 2px 0.75em; color: #777; font-size: 110%; } table.tabs td a.active { color: #000; background-color: #ccc; } table.tabs td.form { text-align: right; } table.tabs td.form form { padding-bottom: 2px; font-size: 90%; white-space: nowrap; } table.tabs td.form input, table.tabs td.form select { font-size: 90%; } div.path { margin: 0px; padding: 5px 2em 2px 2em; color: #000; background-color: #eee; } div.content { margin: 0px; padding: 2em; border-bottom: solid 3px #ccc; } table.list { width: 100%; border: none; border-collapse: collapse; } table.list tr { background: white; } table.list tr.logheader { background: #eee; } table.list tr:hover { background: #eee; } table.list tr.nohover:hover { background: white; } table.list th { font-weight: bold; /* color: #888; border-top: dashed 1px #888; border-bottom: dashed 1px #888; */ padding: 0.1em 0.5em 0.05em 0.5em; vertical-align: baseline; } table.list td { border: none; padding: 0.1em 0.5em 0.1em 0.5em; } table.list td.logsubject { font-family: monospace; font-weight: bold; } table.list td.logmsg { font-family: monospace; white-space: pre; padding: 1em 0.5em 2em 0.5em; } table.list td.lognotes-label { text-align:right; vertical-align:top; } table.list td.lognotes { font-family: monospace; white-space: pre; padding: 0em 0.5em 2em 0.5em; } table.list td a { color: black; } table.list td a.ls-dir { font-weight: bold; color: #00f; } table.list td a:hover { color: #00f; } img { border: none; } input#switch-btn { margin: 2px 0px 0px 0px; } td#sidebar input.txt { width: 100%; margin: 2px 0px 0px 0px; } table#grid { margin: 0px; } td#content { vertical-align: top; padding: 1em 2em 1em 1em; border: none; } div#summary { vertical-align: top; margin-bottom: 1em; } table#downloads { float: right; border-collapse: collapse; border: solid 1px #777; margin-left: 0.5em; margin-bottom: 0.5em; } table#downloads th { background-color: #ccc; } div#blob { border: solid 1px black; } div.error { color: red; font-weight: bold; margin: 1em 2em; } a.ls-blob, a.ls-dir, a.ls-mod { font-family: monospace; } td.ls-size { text-align: right; font-family: monospace; width: 10em; } td.ls-mode { font-family: monospace; width: 10em; } table.blob { margin-top: 0.5em; border-top: solid 1px black; } table.blob td.lines { margin: 0; padding: 0 0 0 0.5em; vertical-align: top; color: black; } table.blob td.linenumbers { margin: 0; padding: 0 0.5em 0 0.5em; vertical-align: top; text-align: right; border-right: 1px solid gray; } table.blob pre { padding: 0; margin: 0; } -table.blob a.no { +table.blob a.no, table.ssdiff a.no { color: gray; text-align: right; text-decoration: none; } table.blob a.no a:hover { color: black; } table.bin-blob { margin-top: 0.5em; border: solid 1px black; } table.bin-blob th { font-family: monospace; white-space: pre; border: solid 1px #777; padding: 0.5em 1em; } table.bin-blob td { font-family: monospace; white-space: pre; border-left: solid 1px #777; padding: 0em 1em; } table.nowrap td { white-space: nowrap; } table.commit-info { border-collapse: collapse; margin-top: 1.5em; } table.commit-info th { text-align: left; font-weight: normal; padding: 0.1em 1em 0.1em 0.1em; vertical-align: top; } table.commit-info td { font-weight: normal; padding: 0.1em 1em 0.1em 0.1em; } div.commit-subject { font-weight: bold; font-size: 125%; margin: 1.5em 0em 0.5em 0em; padding: 0em; } div.commit-msg { white-space: pre; font-family: monospace; } div.notes-header { font-weight: bold; padding-top: 1.5em; } div.notes { white-space: pre; font-family: monospace; border: solid 1px #ee9; background-color: #ffd; padding: 0.3em 2em 0.3em 1em; float: left; } div.notes-footer { clear: left; } div.diffstat-header { font-weight: bold; padding-top: 1.5em; } table.diffstat { border-collapse: collapse; border: solid 1px #aaa; background-color: #eee; } table.diffstat th { font-weight: normal; text-align: left; text-decoration: underline; padding: 0.1em 1em 0.1em 0.1em; font-size: 100%; } table.diffstat td { padding: 0.2em 0.2em 0.1em 0.1em; font-size: 100%; border: none; } table.diffstat td.mode { white-space: nowrap; } table.diffstat td span.modechange { padding-left: 1em; color: red; } table.diffstat td.add a { color: green; } table.diffstat td.del a { color: red; } table.diffstat td.upd a { color: blue; } table.diffstat td.graph { width: 500px; vertical-align: middle; } table.diffstat td.graph table { border: none; } table.diffstat td.graph td { padding: 0px; border: 0px; height: 7pt; } table.diffstat td.graph td.add { background-color: #5c5; } table.diffstat td.graph td.rem { background-color: #c55; } div.diffstat-summary { color: #888; padding-top: 0.5em; } table.diff { width: 100%; } table.diff td { font-family: monospace; white-space: pre; } table.diff td div.head { font-weight: bold; margin-top: 1em; color: black; } table.diff td div.hunk { color: #009; } table.diff td div.add { color: green; } table.diff td div.del { color: red; } .sha1 { font-family: monospace; font-size: 90%; } .left { text-align: left; } .right { text-align: right; } table.list td.reposection { font-style: italic; color: #888; } a.button { font-size: 80%; padding: 0em 0.5em; } a.primary { font-size: 100%; } a.secondary { font-size: 90%; } td.toplevel-repo { } table.list td.sublevel-repo { padding-left: 1.5em; } div.pager { text-align: center; margin: 1em 0em 0em 0em; } div.pager a { color: #777; margin: 0em 0.5em; } span.age-mins { font-weight: bold; color: #080; } span.age-hours { color: #080; } span.age-days { color: #040; } span.age-weeks { color: #444; } span.age-months { color: #888; } span.age-years { color: #bbb; } div.footer { margin-top: 0.5em; text-align: center; font-size: 80%; color: #ccc; } a.branch-deco { margin: 0px 0.5em; padding: 0px 0.25em; background-color: #88ff88; border: solid 1px #007700; } a.tag-deco { margin: 0px 0.5em; padding: 0px 0.25em; background-color: #ffff88; border: solid 1px #777700; } a.remote-deco { margin: 0px 0.5em; padding: 0px 0.25em; background-color: #ccccff; border: solid 1px #000077; } a.deco { margin: 0px 0.5em; padding: 0px 0.25em; background-color: #ff8888; border: solid 1px #770000; } div.commit-subject a.branch-deco, div.commit-subject a.tag-deco, div.commit-subject a.remote-deco, div.commit-subject a.deco { margin-left: 1em; font-size: 75%; } table.stats { border: solid 1px black; border-collapse: collapse; } table.stats th { text-align: left; padding: 1px 0.5em; background-color: #eee; border: solid 1px black; } table.stats td { text-align: right; padding: 1px 0.5em; border: solid 1px black; } table.stats td.total { font-weight: bold; text-align: left; } table.stats td.sum { color: #c00; font-weight: bold; /* background-color: #eee; */ } table.stats td.left { text-align: left; } table.vgraph { border-collapse: separate; border: solid 1px black; height: 200px; } table.vgraph th { background-color: #eee; font-weight: bold; border: solid 1px white; padding: 1px 0.5em; } table.vgraph td { vertical-align: bottom; padding: 0px 10px; } table.vgraph div.bar { background-color: #eee; } table.hgraph { border: solid 1px black; width: 800px; } table.hgraph th { background-color: #eee; font-weight: bold; border: solid 1px black; padding: 1px 0.5em; } table.hgraph td { vertical-align: center; padding: 2px 2px; } table.hgraph div.bar { background-color: #eee; height: 1em; } table.ssdiff { width: 100%; } table.ssdiff td { font-size: 75%; font-family: monospace; white-space: pre; padding: 1px 4px 1px 4px; border-left: solid 1px #aaa; border-right: solid 1px #aaa; } table.ssdiff td.add { color: black; background: #cfc; min-width: 50%; } table.ssdiff td.add_dark { color: black; background: #aca; min-width: 50%; } table.ssdiff span.add { background: #cfc; font-weight: bold; } table.ssdiff td.del { color: black; background: #fcc; min-width: 50%; } table.ssdiff td.del_dark { color: black; background: #caa; min-width: 50%; } table.ssdiff span.del { background: #fcc; font-weight: bold; } table.ssdiff td.changed { color: black; background: #ffc; min-width: 50%; } table.ssdiff td.changed_dark { color: black; background: #cca; min-width: 50%; } table.ssdiff td.lineno { color: black; background: #eee; text-align: right; width: 3em; min-width: 3em; } table.ssdiff td.hunk { color: #black; background: #ccf; border-top: solid 1px #aaa; border-bottom: solid 1px #aaa; } table.ssdiff td.head { border-top: solid 1px #aaa; border-bottom: solid 1px #aaa; } table.ssdiff td.head div.head { font-weight: bold; color: black; } table.ssdiff td.foot { border-top: solid 1px #aaa; border-left: none; border-right: none; border-bottom: none; } table.ssdiff td.space { border: none; } table.ssdiff td.space div { min-height: 3em; } /* Syntax highlighting */ table.blob .num { color:#2928ff; } table.blob .esc { color:#ff00ff; } table.blob .str { color:#ff0000; } table.blob .dstr { color:#818100; } table.blob .slc { color:#838183; font-style:italic; } table.blob .com { color:#838183; font-style:italic; } table.blob .dir { color:#008200; } table.blob .sym { color:#000000; } table.blob .kwa { color:#000000; font-weight:bold; } table.blob .kwb { color:#830000; } table.blob .kwc { color:#000000; font-weight:bold; } table.blob .kwd { color:#010181; } @@ -1,371 +1,383 @@ /* ui-diff.c: show diff between two blobs * * Copyright (C) 2006 Lars Hjemli * * Licensed under GNU General Public License v2 * (see COPYING for full license text) */ #include "cgit.h" #include "html.h" #include "ui-shared.h" #include "ui-ssdiff.h" unsigned char old_rev_sha1[20]; unsigned char new_rev_sha1[20]; static int files, slots; static int total_adds, total_rems, max_changes; static int lines_added, lines_removed; static struct fileinfo { char status; unsigned char old_sha1[20]; unsigned char new_sha1[20]; unsigned short old_mode; unsigned short new_mode; char *old_path; char *new_path; unsigned int added; unsigned int removed; unsigned long old_size; unsigned long new_size; int binary:1; } *items; static int use_ssdiff = 0; +static struct diff_filepair *current_filepair; + +struct diff_filespec *cgit_get_current_old_file(void) +{ + return current_filepair->one; +} + +struct diff_filespec *cgit_get_current_new_file(void) +{ + return current_filepair->two; +} static void print_fileinfo(struct fileinfo *info) { char *class; switch (info->status) { case DIFF_STATUS_ADDED: class = "add"; break; case DIFF_STATUS_COPIED: class = "cpy"; break; case DIFF_STATUS_DELETED: class = "del"; break; case DIFF_STATUS_MODIFIED: class = "upd"; break; case DIFF_STATUS_RENAMED: class = "mov"; break; case DIFF_STATUS_TYPE_CHANGED: class = "typ"; break; case DIFF_STATUS_UNKNOWN: class = "unk"; break; case DIFF_STATUS_UNMERGED: class = "stg"; break; default: die("bug: unhandled diff status %c", info->status); } html("<tr>"); htmlf("<td class='mode'>"); if (is_null_sha1(info->new_sha1)) { cgit_print_filemode(info->old_mode); } else { cgit_print_filemode(info->new_mode); } if (info->old_mode != info->new_mode && !is_null_sha1(info->old_sha1) && !is_null_sha1(info->new_sha1)) { html("<span class='modechange'>["); cgit_print_filemode(info->old_mode); html("]</span>"); } htmlf("</td><td class='%s'>", class); cgit_diff_link(info->new_path, NULL, NULL, ctx.qry.head, ctx.qry.sha1, ctx.qry.sha2, info->new_path, 0); if (info->status == DIFF_STATUS_COPIED || info->status == DIFF_STATUS_RENAMED) htmlf(" (%s from %s)", info->status == DIFF_STATUS_COPIED ? "copied" : "renamed", info->old_path); html("</td><td class='right'>"); if (info->binary) { htmlf("bin</td><td class='graph'>%ld -> %ld bytes", info->old_size, info->new_size); return; } htmlf("%d", info->added + info->removed); html("</td><td class='graph'>"); htmlf("<table summary='file diffstat' width='%d%%'><tr>", (max_changes > 100 ? 100 : max_changes)); htmlf("<td class='add' style='width: %.1f%%;'/>", info->added * 100.0 / max_changes); htmlf("<td class='rem' style='width: %.1f%%;'/>", info->removed * 100.0 / max_changes); htmlf("<td class='none' style='width: %.1f%%;'/>", (max_changes - info->removed - info->added) * 100.0 / max_changes); html("</tr></table></td></tr>\n"); } static void count_diff_lines(char *line, int len) { if (line && (len > 0)) { if (line[0] == '+') lines_added++; else if (line[0] == '-') lines_removed++; } } static void inspect_filepair(struct diff_filepair *pair) { int binary = 0; unsigned long old_size = 0; unsigned long new_size = 0; files++; lines_added = 0; lines_removed = 0; cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size, &new_size, &binary, 0, ctx.qry.ignorews, count_diff_lines); if (files >= slots) { if (slots == 0) slots = 4; else slots = slots * 2; items = xrealloc(items, slots * sizeof(struct fileinfo)); } items[files-1].status = pair->status; hashcpy(items[files-1].old_sha1, pair->one->sha1); hashcpy(items[files-1].new_sha1, pair->two->sha1); items[files-1].old_mode = pair->one->mode; items[files-1].new_mode = pair->two->mode; items[files-1].old_path = xstrdup(pair->one->path); items[files-1].new_path = xstrdup(pair->two->path); items[files-1].added = lines_added; items[files-1].removed = lines_removed; items[files-1].old_size = old_size; items[files-1].new_size = new_size; items[files-1].binary = binary; if (lines_added + lines_removed > max_changes) max_changes = lines_added + lines_removed; total_adds += lines_added; total_rems += lines_removed; } void cgit_print_diffstat(const unsigned char *old_sha1, const unsigned char *new_sha1, const char *prefix) { int i, save_context = ctx.qry.context; html("<div class='diffstat-header'>"); cgit_diff_link("Diffstat", NULL, NULL, ctx.qry.head, ctx.qry.sha1, ctx.qry.sha2, NULL, 0); if (prefix) htmlf(" (limited to '%s')", prefix); html(" ("); ctx.qry.context = (save_context > 0 ? save_context : 3) << 1; cgit_self_link("more", NULL, NULL, &ctx); html("/"); ctx.qry.context = (save_context > 3 ? save_context : 3) >> 1; cgit_self_link("less", NULL, NULL, &ctx); ctx.qry.context = save_context; html(" context)"); html(" ("); ctx.qry.ignorews = (ctx.qry.ignorews + 1) % 2; cgit_self_link(ctx.qry.ignorews ? "ignore" : "show", NULL, NULL, &ctx); ctx.qry.ignorews = (ctx.qry.ignorews + 1) % 2; html(" whitespace changes)"); html("</div>"); html("<table summary='diffstat' class='diffstat'>"); max_changes = 0; cgit_diff_tree(old_sha1, new_sha1, inspect_filepair, prefix, ctx.qry.ignorews); for(i = 0; i<files; i++) print_fileinfo(&items[i]); html("</table>"); html("<div class='diffstat-summary'>"); htmlf("%d files changed, %d insertions, %d deletions", files, total_adds, total_rems); html("</div>"); } /* * print a single line returned from xdiff */ static void print_line(char *line, int len) { char *class = "ctx"; char c = line[len-1]; if (line[0] == '+') class = "add"; else if (line[0] == '-') class = "del"; else if (line[0] == '@') class = "hunk"; htmlf("<div class='%s'>", class); line[len-1] = '\0'; html_txt(line); html("</div>"); line[len-1] = c; } static void header(unsigned char *sha1, char *path1, int mode1, unsigned char *sha2, char *path2, int mode2) { char *abbrev1, *abbrev2; int subproject; subproject = (S_ISGITLINK(mode1) || S_ISGITLINK(mode2)); html("<div class='head'>"); html("diff --git a/"); html_txt(path1); html(" b/"); html_txt(path2); if (is_null_sha1(sha1)) path1 = "dev/null"; if (is_null_sha1(sha2)) path2 = "dev/null"; if (mode1 == 0) htmlf("<br/>new file mode %.6o", mode2); if (mode2 == 0) htmlf("<br/>deleted file mode %.6o", mode1); if (!subproject) { abbrev1 = xstrdup(find_unique_abbrev(sha1, DEFAULT_ABBREV)); abbrev2 = xstrdup(find_unique_abbrev(sha2, DEFAULT_ABBREV)); htmlf("<br/>index %s..%s", abbrev1, abbrev2); free(abbrev1); free(abbrev2); if (mode1 != 0 && mode2 != 0) { htmlf(" %.6o", mode1); if (mode2 != mode1) htmlf("..%.6o", mode2); } html("<br/>--- a/"); if (mode1 != 0) cgit_tree_link(path1, NULL, NULL, ctx.qry.head, sha1_to_hex(old_rev_sha1), path1); else html_txt(path1); html("<br/>+++ b/"); if (mode2 != 0) cgit_tree_link(path2, NULL, NULL, ctx.qry.head, sha1_to_hex(new_rev_sha1), path2); else html_txt(path2); } html("</div>"); } static void print_ssdiff_link() { if (!strcmp(ctx.qry.page, "diff")) { if (use_ssdiff) cgit_diff_link("Unidiff", NULL, NULL, ctx.qry.head, ctx.qry.sha1, ctx.qry.sha2, ctx.qry.path, 1); else cgit_diff_link("Side-by-side diff", NULL, NULL, ctx.qry.head, ctx.qry.sha1, ctx.qry.sha2, ctx.qry.path, 1); } } static void filepair_cb(struct diff_filepair *pair) { unsigned long old_size = 0; unsigned long new_size = 0; int binary = 0; linediff_fn print_line_fn = print_line; + current_filepair = pair; if (use_ssdiff) { cgit_ssdiff_header_begin(); print_line_fn = cgit_ssdiff_line_cb; } header(pair->one->sha1, pair->one->path, pair->one->mode, pair->two->sha1, pair->two->path, pair->two->mode); if (use_ssdiff) cgit_ssdiff_header_end(); if (S_ISGITLINK(pair->one->mode) || S_ISGITLINK(pair->two->mode)) { if (S_ISGITLINK(pair->one->mode)) print_line_fn(fmt("-Subproject %s", sha1_to_hex(pair->one->sha1)), 52); if (S_ISGITLINK(pair->two->mode)) print_line_fn(fmt("+Subproject %s", sha1_to_hex(pair->two->sha1)), 52); if (use_ssdiff) cgit_ssdiff_footer(); return; } if (cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size, &new_size, &binary, ctx.qry.context, ctx.qry.ignorews, print_line_fn)) cgit_print_error("Error running diff"); if (binary) { if (use_ssdiff) html("<tr><td colspan='4'>Binary files differ</td></tr>"); else html("Binary files differ"); } if (use_ssdiff) cgit_ssdiff_footer(); } void cgit_print_diff(const char *new_rev, const char *old_rev, const char *prefix) { enum object_type type; unsigned long size; struct commit *commit, *commit2; if (!new_rev) new_rev = ctx.qry.head; get_sha1(new_rev, new_rev_sha1); type = sha1_object_info(new_rev_sha1, &size); if (type == OBJ_BAD) { cgit_print_error(fmt("Bad object name: %s", new_rev)); return; } commit = lookup_commit_reference(new_rev_sha1); if (!commit || parse_commit(commit)) cgit_print_error(fmt("Bad commit: %s", sha1_to_hex(new_rev_sha1))); if (old_rev) get_sha1(old_rev, old_rev_sha1); else if (commit->parents && commit->parents->item) hashcpy(old_rev_sha1, commit->parents->item->object.sha1); else hashclr(old_rev_sha1); if (!is_null_sha1(old_rev_sha1)) { type = sha1_object_info(old_rev_sha1, &size); if (type == OBJ_BAD) { cgit_print_error(fmt("Bad object name: %s", sha1_to_hex(old_rev_sha1))); return; } commit2 = lookup_commit_reference(old_rev_sha1); if (!commit2 || parse_commit(commit2)) cgit_print_error(fmt("Bad commit: %s", sha1_to_hex(old_rev_sha1))); } if ((ctx.qry.ssdiff && !ctx.cfg.ssdiff) || (!ctx.qry.ssdiff && ctx.cfg.ssdiff)) use_ssdiff = 1; print_ssdiff_link(); cgit_print_diffstat(old_rev_sha1, new_rev_sha1, prefix); if (use_ssdiff) { html("<table summary='ssdiff' class='ssdiff'>"); } else { html("<table summary='diff' class='diff'>"); html("<tr><td>"); } cgit_diff_tree(old_rev_sha1, new_rev_sha1, filepair_cb, prefix, ctx.qry.ignorews); if (!use_ssdiff) html("</td></tr>"); html("</table>"); } @@ -1,10 +1,16 @@ #ifndef UI_DIFF_H #define UI_DIFF_H extern void cgit_print_diffstat(const unsigned char *old_sha1, const unsigned char *new_sha1); extern void cgit_print_diff(const char *new_hex, const char *old_hex, const char *prefix); +extern struct diff_filespec *cgit_get_current_old_file(void); +extern struct diff_filespec *cgit_get_current_new_file(void); + +extern unsigned char old_rev_sha1[20]; +extern unsigned char new_rev_sha1[20]; + #endif /* UI_DIFF_H */ diff --git a/ui-ssdiff.c b/ui-ssdiff.c index 408e620..2481585 100644 --- a/ui-ssdiff.c +++ b/ui-ssdiff.c @@ -1,369 +1,383 @@ #include "cgit.h" #include "html.h" #include "ui-shared.h" +#include "ui-diff.h" extern int use_ssdiff; static int current_old_line, current_new_line; struct deferred_lines { int line_no; char *line; struct deferred_lines *next; }; static struct deferred_lines *deferred_old, *deferred_old_last; static struct deferred_lines *deferred_new, *deferred_new_last; static char *longest_common_subsequence(char *A, char *B) { int i, j, ri; int m = strlen(A); int n = strlen(B); int L[m + 1][n + 1]; int tmp1, tmp2; int lcs_length; char *result; for (i = m; i >= 0; i--) { for (j = n; j >= 0; j--) { if (A[i] == '\0' || B[j] == '\0') { L[i][j] = 0; } else if (A[i] == B[j]) { L[i][j] = 1 + L[i + 1][j + 1]; } else { tmp1 = L[i + 1][j]; tmp2 = L[i][j + 1]; L[i][j] = (tmp1 > tmp2 ? tmp1 : tmp2); } } } lcs_length = L[0][0]; result = xmalloc(lcs_length + 2); memset(result, 0, sizeof(*result) * (lcs_length + 2)); ri = 0; i = 0; j = 0; while (i < m && j < n) { if (A[i] == B[j]) { result[ri] = A[i]; ri += 1; i += 1; j += 1; } else if (L[i + 1][j] >= L[i][j + 1]) { i += 1; } else { j += 1; } } return result; } static int line_from_hunk(char *line, char type) { char *buf1, *buf2; int len; buf1 = strchr(line, type); if (buf1 == NULL) return 0; buf1 += 1; buf2 = strchr(buf1, ','); if (buf2 == NULL) return 0; len = buf2 - buf1; buf2 = xmalloc(len + 1); strncpy(buf2, buf1, len); buf2[len] = '\0'; int res = atoi(buf2); free(buf2); return res; } static char *replace_tabs(char *line) { char *prev_buf = line; char *cur_buf; int linelen = strlen(line); int n_tabs = 0; int i; char *result; char *spaces = " "; if (linelen == 0) { result = xmalloc(1); result[0] = '\0'; return result; } for (i = 0; i < linelen; i++) if (line[i] == '\t') n_tabs += 1; result = xmalloc(linelen + n_tabs * 8 + 1); result[0] = '\0'; while (1) { cur_buf = strchr(prev_buf, '\t'); if (!cur_buf) { strcat(result, prev_buf); break; } else { strcat(result, " "); strncat(result, spaces, 8 - (strlen(result) % 8)); strncat(result, prev_buf, cur_buf - prev_buf); } prev_buf = cur_buf + 1; } return result; } static int calc_deferred_lines(struct deferred_lines *start) { struct deferred_lines *item = start; int result = 0; while (item) { result += 1; item = item->next; } return result; } static void deferred_old_add(char *line, int line_no) { struct deferred_lines *item = xmalloc(sizeof(struct deferred_lines)); item->line = xstrdup(line); item->line_no = line_no; item->next = NULL; if (deferred_old) { deferred_old_last->next = item; deferred_old_last = item; } else { deferred_old = deferred_old_last = item; } } static void deferred_new_add(char *line, int line_no) { struct deferred_lines *item = xmalloc(sizeof(struct deferred_lines)); item->line = xstrdup(line); item->line_no = line_no; item->next = NULL; if (deferred_new) { deferred_new_last->next = item; deferred_new_last = item; } else { deferred_new = deferred_new_last = item; } } static void print_part_with_lcs(char *class, char *line, char *lcs) { int line_len = strlen(line); int i, j; char c[2] = " "; int same = 1; j = 0; for (i = 0; i < line_len; i++) { c[0] = line[i]; if (same) { if (line[i] == lcs[j]) j += 1; else { same = 0; htmlf("<span class='%s'>", class); } } else if (line[i] == lcs[j]) { same = 1; htmlf("</span>"); j += 1; } html_txt(c); } } static void print_ssdiff_line(char *class, int old_line_no, char *old_line, int new_line_no, char *new_line, int individual_chars) { char *lcs = NULL; + if (old_line) old_line = replace_tabs(old_line + 1); if (new_line) new_line = replace_tabs(new_line + 1); if (individual_chars && old_line && new_line) lcs = longest_common_subsequence(old_line, new_line); - html("<tr>"); - if (old_line_no > 0) - htmlf("<td class='lineno'>%d</td><td class='%s'>", - old_line_no, class); - else if (old_line) + html("<tr>\n"); + if (old_line_no > 0) { + struct diff_filespec *old_file = cgit_get_current_old_file(); + char *lineno_str = fmt("n%d", old_line_no); + char *id_str = fmt("%s#%s", is_null_sha1(old_file->sha1)?"HEAD":sha1_to_hex(old_rev_sha1), lineno_str); + html("<td class='lineno'><a class='no' href='"); + html(cgit_fileurl(ctx.repo->url, "tree", old_file->path, id_str)); + htmlf("' id='%s' name='%s'>%s</a>", lineno_str, lineno_str, lineno_str + 1); + html("</td>"); + htmlf("<td class='%s'>", class); + } else if (old_line) htmlf("<td class='lineno'></td><td class='%s'>", class); else htmlf("<td class='lineno'></td><td class='%s_dark'>", class); if (old_line) { if (lcs) print_part_with_lcs("del", old_line, lcs); else html_txt(old_line); } - html("</td>"); - if (new_line_no > 0) - htmlf("<td class='lineno'>%d</td><td class='%s'>", - new_line_no, class); - else if (new_line) + html("</td>\n"); + if (new_line_no > 0) { + struct diff_filespec *new_file = cgit_get_current_new_file(); + char *lineno_str = fmt("n%d", new_line_no); + char *id_str = fmt("%s#%s", is_null_sha1(new_file->sha1)?"HEAD":sha1_to_hex(new_rev_sha1), lineno_str); + html("<td class='lineno'><a class='no' href='"); + html(cgit_fileurl(ctx.repo->url, "tree", new_file->path, id_str)); + htmlf("' id='%s' name='%s'>%s</a>", lineno_str, lineno_str, lineno_str + 1); + html("</td>"); + htmlf("<td class='%s'>", class); + } else if (new_line) htmlf("<td class='lineno'></td><td class='%s'>", class); else htmlf("<td class='lineno'></td><td class='%s_dark'>", class); if (new_line) { if (lcs) print_part_with_lcs("add", new_line, lcs); else html_txt(new_line); } html("</td></tr>"); if (lcs) free(lcs); if (new_line) free(new_line); if (old_line) free(old_line); } static void print_deferred_old_lines() { struct deferred_lines *iter_old, *tmp; iter_old = deferred_old; while (iter_old) { print_ssdiff_line("del", iter_old->line_no, iter_old->line, -1, NULL, 0); tmp = iter_old->next; free(iter_old); iter_old = tmp; } } static void print_deferred_new_lines() { struct deferred_lines *iter_new, *tmp; iter_new = deferred_new; while (iter_new) { print_ssdiff_line("add", -1, NULL, iter_new->line_no, iter_new->line, 0); tmp = iter_new->next; free(iter_new); iter_new = tmp; } } static void print_deferred_changed_lines() { struct deferred_lines *iter_old, *iter_new, *tmp; int n_old_lines = calc_deferred_lines(deferred_old); int n_new_lines = calc_deferred_lines(deferred_new); int individual_chars = (n_old_lines == n_new_lines ? 1 : 0); iter_old = deferred_old; iter_new = deferred_new; while (iter_old || iter_new) { if (iter_old && iter_new) print_ssdiff_line("changed", iter_old->line_no, iter_old->line, iter_new->line_no, iter_new->line, individual_chars); else if (iter_old) print_ssdiff_line("changed", iter_old->line_no, iter_old->line, -1, NULL, 0); else if (iter_new) print_ssdiff_line("changed", -1, NULL, iter_new->line_no, iter_new->line, 0); if (iter_old) { tmp = iter_old->next; free(iter_old); iter_old = tmp; } if (iter_new) { tmp = iter_new->next; free(iter_new); iter_new = tmp; } } } void cgit_ssdiff_print_deferred_lines() { if (!deferred_old && !deferred_new) return; if (deferred_old && !deferred_new) print_deferred_old_lines(); else if (!deferred_old && deferred_new) print_deferred_new_lines(); else print_deferred_changed_lines(); deferred_old = deferred_old_last = NULL; deferred_new = deferred_new_last = NULL; } /* * print a single line returned from xdiff */ void cgit_ssdiff_line_cb(char *line, int len) { char c = line[len - 1]; line[len - 1] = '\0'; if (line[0] == '@') { current_old_line = line_from_hunk(line, '-'); current_new_line = line_from_hunk(line, '+'); } if (line[0] == ' ') { if (deferred_old || deferred_new) cgit_ssdiff_print_deferred_lines(); print_ssdiff_line("ctx", current_old_line, line, current_new_line, line, 0); current_old_line += 1; current_new_line += 1; } else if (line[0] == '+') { deferred_new_add(line, current_new_line); current_new_line += 1; } else if (line[0] == '-') { deferred_old_add(line, current_old_line); current_old_line += 1; } else if (line[0] == '@') { html("<tr><td colspan='4' class='hunk'>"); html_txt(line); html("</td></tr>"); } else { html("<tr><td colspan='4' class='ctx'>"); html_txt(line); html("</td></tr>"); } line[len - 1] = c; } void cgit_ssdiff_header_begin() { current_old_line = -1; current_new_line = -1; html("<tr><td class='space' colspan='4'><div></div></td></tr>"); html("<tr><td class='head' colspan='4'>"); } void cgit_ssdiff_header_end() { html("</td><tr>"); } void cgit_ssdiff_footer() { if (deferred_old || deferred_new) cgit_ssdiff_print_deferred_lines(); html("<tr><td class='foot' colspan='4'></td></tr>"); } |