summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRagnar Ouchterlony <ragnar@lysator.liu.se>2009-10-25 18:13:22 +0100
committerLars Hjemli <hjemli@gmail.com>2009-11-07 15:37:11 +0100
commit735e15e38a484bf0daa98776fa7cde270a271cda (patch)
tree588545cead471e8997adc1ae5abca164a4873481
parent4a198e4b8ee62a9a8b5156a46bfce46dc7223fe9 (diff)
downloadcgit-735e15e38a484bf0daa98776fa7cde270a271cda.tar.gz
cgit-735e15e38a484bf0daa98776fa7cde270a271cda.tar.bz2
In side-by-side diff, add support for marking individual characters.
Refuses to do so if the left hand side of the diff has different amount of differing lines to the right hand side to avoid confusion. Note that I use the naive dynamic programming approach for calculating the longest common subsequence. We could probably be more efficient by using a better algorithm. The LCS calculating function is O(n*m) and uses up n*m amount of memory too (so if we we compare two strings of length 100, I use an array of 10000 for calculating the LCS). Might want to not calculate LCS if the length of the line is too large. Signed-off-by: Ragnar Ouchterlony <ragnar@lysator.liu.se>
-rw-r--r--cgit.css10
-rw-r--r--ui-ssdiff.c145
2 files changed, 130 insertions, 25 deletions
diff --git a/cgit.css b/cgit.css
index 3f37165..9e6d2a4 100644
--- a/cgit.css
+++ b/cgit.css
@@ -627,6 +627,11 @@ table.ssdiff td.add_dark {
min-width: 50%;
}
+table.ssdiff span.add {
+ background: #cfc;
+ font-weight: bold;
+}
+
table.ssdiff td.del {
color: black;
background: #fcc;
@@ -639,6 +644,11 @@ table.ssdiff td.del_dark {
min-width: 50%;
}
+table.ssdiff span.del {
+ background: #fcc;
+ font-weight: bold;
+}
+
table.ssdiff td.changed {
color: black;
background: #ffc;
diff --git a/ui-ssdiff.c b/ui-ssdiff.c
index 5673642..408e620 100644
--- a/ui-ssdiff.c
+++ b/ui-ssdiff.c
@@ -15,6 +15,52 @@ struct deferred_lines {
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;
@@ -73,6 +119,17 @@ static char *replace_tabs(char *line)
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));
@@ -101,9 +158,45 @@ static void deferred_new_add(char *line, int line_no)
}
}
-static void print_ssdiff_line(char *class, int old_line_no, char *old_line,
- int new_line_no, char *new_line)
+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'>",
@@ -112,15 +205,14 @@ static void print_ssdiff_line(char *class, int old_line_no, char *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) {
- old_line = replace_tabs(old_line + 1);
- html_txt(old_line);
- free(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);
@@ -128,24 +220,29 @@ static void print_ssdiff_line(char *class, int old_line_no, char *old_line,
htmlf("<td class='lineno'></td><td class='%s'>", class);
else
htmlf("<td class='lineno'></td><td class='%s_dark'>", class);
-
if (new_line) {
- new_line = replace_tabs(new_line + 1);
- html_txt(new_line);
- free(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);
+ iter_old->line, -1, NULL, 0);
tmp = iter_old->next;
free(iter_old);
iter_old = tmp;
@@ -155,11 +252,10 @@ static void print_deferred_old_lines()
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);
+ print_ssdiff_line("add", -1, NULL,
+ iter_new->line_no, iter_new->line, 0);
tmp = iter_new->next;
free(iter_new);
iter_new = tmp;
@@ -169,6 +265,9 @@ static void print_deferred_new_lines()
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;
@@ -176,14 +275,14 @@ static void print_deferred_changed_lines()
if (iter_old && iter_new)
print_ssdiff_line("changed", iter_old->line_no,
iter_old->line,
- iter_new->line_no, iter_new->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);
+ iter_old->line, -1, NULL, 0);
else if (iter_new)
print_ssdiff_line("changed", -1, NULL,
- iter_new->line_no, iter_new->line);
-
+ iter_new->line_no, iter_new->line, 0);
if (iter_old) {
tmp = iter_old->next;
free(iter_old);
@@ -202,14 +301,12 @@ 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;
}
@@ -220,9 +317,7 @@ void cgit_ssdiff_print_deferred_lines()
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, '+');
@@ -232,7 +327,7 @@ void cgit_ssdiff_line_cb(char *line, int len)
if (deferred_old || deferred_new)
cgit_ssdiff_print_deferred_lines();
print_ssdiff_line("ctx", current_old_line, line,
- current_new_line, line);
+ current_new_line, line, 0);
current_old_line += 1;
current_new_line += 1;
} else if (line[0] == '+') {