]> git.cameronkatri.com Git - cgit.git/blob - ui-ssdiff.c
make enable-log-linecount independent of -filecount
[cgit.git] / ui-ssdiff.c
1 #include "cgit.h"
2 #include "html.h"
3 #include "ui-shared.h"
4 #include "ui-diff.h"
5
6 extern int use_ssdiff;
7
8 static int current_old_line, current_new_line;
9
10 struct deferred_lines {
11 int line_no;
12 char *line;
13 struct deferred_lines *next;
14 };
15
16 static struct deferred_lines *deferred_old, *deferred_old_last;
17 static struct deferred_lines *deferred_new, *deferred_new_last;
18
19 static char *longest_common_subsequence(char *A, char *B)
20 {
21 int i, j, ri;
22 int m = strlen(A);
23 int n = strlen(B);
24 int L[m + 1][n + 1];
25 int tmp1, tmp2;
26 int lcs_length;
27 char *result;
28
29 for (i = m; i >= 0; i--) {
30 for (j = n; j >= 0; j--) {
31 if (A[i] == '\0' || B[j] == '\0') {
32 L[i][j] = 0;
33 } else if (A[i] == B[j]) {
34 L[i][j] = 1 + L[i + 1][j + 1];
35 } else {
36 tmp1 = L[i + 1][j];
37 tmp2 = L[i][j + 1];
38 L[i][j] = (tmp1 > tmp2 ? tmp1 : tmp2);
39 }
40 }
41 }
42
43 lcs_length = L[0][0];
44 result = xmalloc(lcs_length + 2);
45 memset(result, 0, sizeof(*result) * (lcs_length + 2));
46
47 ri = 0;
48 i = 0;
49 j = 0;
50 while (i < m && j < n) {
51 if (A[i] == B[j]) {
52 result[ri] = A[i];
53 ri += 1;
54 i += 1;
55 j += 1;
56 } else if (L[i + 1][j] >= L[i][j + 1]) {
57 i += 1;
58 } else {
59 j += 1;
60 }
61 }
62 return result;
63 }
64
65 static int line_from_hunk(char *line, char type)
66 {
67 char *buf1, *buf2;
68 int len;
69
70 buf1 = strchr(line, type);
71 if (buf1 == NULL)
72 return 0;
73 buf1 += 1;
74 buf2 = strchr(buf1, ',');
75 if (buf2 == NULL)
76 return 0;
77 len = buf2 - buf1;
78 buf2 = xmalloc(len + 1);
79 strncpy(buf2, buf1, len);
80 buf2[len] = '\0';
81 int res = atoi(buf2);
82 free(buf2);
83 return res;
84 }
85
86 static char *replace_tabs(char *line)
87 {
88 char *prev_buf = line;
89 char *cur_buf;
90 int linelen = strlen(line);
91 int n_tabs = 0;
92 int i;
93 char *result;
94 char *spaces = " ";
95
96 if (linelen == 0) {
97 result = xmalloc(1);
98 result[0] = '\0';
99 return result;
100 }
101
102 for (i = 0; i < linelen; i++)
103 if (line[i] == '\t')
104 n_tabs += 1;
105 result = xmalloc(linelen + n_tabs * 8 + 1);
106 result[0] = '\0';
107
108 while (1) {
109 cur_buf = strchr(prev_buf, '\t');
110 if (!cur_buf) {
111 strcat(result, prev_buf);
112 break;
113 } else {
114 strcat(result, " ");
115 strncat(result, spaces, 8 - (strlen(result) % 8));
116 strncat(result, prev_buf, cur_buf - prev_buf);
117 }
118 prev_buf = cur_buf + 1;
119 }
120 return result;
121 }
122
123 static int calc_deferred_lines(struct deferred_lines *start)
124 {
125 struct deferred_lines *item = start;
126 int result = 0;
127 while (item) {
128 result += 1;
129 item = item->next;
130 }
131 return result;
132 }
133
134 static void deferred_old_add(char *line, int line_no)
135 {
136 struct deferred_lines *item = xmalloc(sizeof(struct deferred_lines));
137 item->line = xstrdup(line);
138 item->line_no = line_no;
139 item->next = NULL;
140 if (deferred_old) {
141 deferred_old_last->next = item;
142 deferred_old_last = item;
143 } else {
144 deferred_old = deferred_old_last = item;
145 }
146 }
147
148 static void deferred_new_add(char *line, int line_no)
149 {
150 struct deferred_lines *item = xmalloc(sizeof(struct deferred_lines));
151 item->line = xstrdup(line);
152 item->line_no = line_no;
153 item->next = NULL;
154 if (deferred_new) {
155 deferred_new_last->next = item;
156 deferred_new_last = item;
157 } else {
158 deferred_new = deferred_new_last = item;
159 }
160 }
161
162 static void print_part_with_lcs(char *class, char *line, char *lcs)
163 {
164 int line_len = strlen(line);
165 int i, j;
166 char c[2] = " ";
167 int same = 1;
168
169 j = 0;
170 for (i = 0; i < line_len; i++) {
171 c[0] = line[i];
172 if (same) {
173 if (line[i] == lcs[j])
174 j += 1;
175 else {
176 same = 0;
177 htmlf("<span class='%s'>", class);
178 }
179 } else if (line[i] == lcs[j]) {
180 same = 1;
181 htmlf("</span>");
182 j += 1;
183 }
184 html_txt(c);
185 }
186 }
187
188 static void print_ssdiff_line(char *class,
189 int old_line_no,
190 char *old_line,
191 int new_line_no,
192 char *new_line, int individual_chars)
193 {
194 char *lcs = NULL;
195
196 if (old_line)
197 old_line = replace_tabs(old_line + 1);
198 if (new_line)
199 new_line = replace_tabs(new_line + 1);
200 if (individual_chars && old_line && new_line)
201 lcs = longest_common_subsequence(old_line, new_line);
202 html("<tr>\n");
203 if (old_line_no > 0) {
204 struct diff_filespec *old_file = cgit_get_current_old_file();
205 char *lineno_str = fmt("n%d", old_line_no);
206 char *id_str = fmt("%s#%s", is_null_sha1(old_file->sha1)?"HEAD":sha1_to_hex(old_rev_sha1), lineno_str);
207 html("<td class='lineno'><a class='no' href='");
208 html(cgit_fileurl(ctx.repo->url, "tree", old_file->path, id_str));
209 htmlf("' id='%s' name='%s'>%s</a>", lineno_str, lineno_str, lineno_str + 1);
210 html("</td>");
211 htmlf("<td class='%s'>", class);
212 } else if (old_line)
213 htmlf("<td class='lineno'></td><td class='%s'>", class);
214 else
215 htmlf("<td class='lineno'></td><td class='%s_dark'>", class);
216 if (old_line) {
217 if (lcs)
218 print_part_with_lcs("del", old_line, lcs);
219 else
220 html_txt(old_line);
221 }
222
223 html("</td>\n");
224 if (new_line_no > 0) {
225 struct diff_filespec *new_file = cgit_get_current_new_file();
226 char *lineno_str = fmt("n%d", new_line_no);
227 char *id_str = fmt("%s#%s", is_null_sha1(new_file->sha1)?"HEAD":sha1_to_hex(new_rev_sha1), lineno_str);
228 html("<td class='lineno'><a class='no' href='");
229 html(cgit_fileurl(ctx.repo->url, "tree", new_file->path, id_str));
230 htmlf("' id='%s' name='%s'>%s</a>", lineno_str, lineno_str, lineno_str + 1);
231 html("</td>");
232 htmlf("<td class='%s'>", class);
233 } else if (new_line)
234 htmlf("<td class='lineno'></td><td class='%s'>", class);
235 else
236 htmlf("<td class='lineno'></td><td class='%s_dark'>", class);
237 if (new_line) {
238 if (lcs)
239 print_part_with_lcs("add", new_line, lcs);
240 else
241 html_txt(new_line);
242 }
243
244 html("</td></tr>");
245 if (lcs)
246 free(lcs);
247 if (new_line)
248 free(new_line);
249 if (old_line)
250 free(old_line);
251 }
252
253 static void print_deferred_old_lines()
254 {
255 struct deferred_lines *iter_old, *tmp;
256 iter_old = deferred_old;
257 while (iter_old) {
258 print_ssdiff_line("del", iter_old->line_no,
259 iter_old->line, -1, NULL, 0);
260 tmp = iter_old->next;
261 free(iter_old);
262 iter_old = tmp;
263 }
264 }
265
266 static void print_deferred_new_lines()
267 {
268 struct deferred_lines *iter_new, *tmp;
269 iter_new = deferred_new;
270 while (iter_new) {
271 print_ssdiff_line("add", -1, NULL,
272 iter_new->line_no, iter_new->line, 0);
273 tmp = iter_new->next;
274 free(iter_new);
275 iter_new = tmp;
276 }
277 }
278
279 static void print_deferred_changed_lines()
280 {
281 struct deferred_lines *iter_old, *iter_new, *tmp;
282 int n_old_lines = calc_deferred_lines(deferred_old);
283 int n_new_lines = calc_deferred_lines(deferred_new);
284 int individual_chars = (n_old_lines == n_new_lines ? 1 : 0);
285
286 iter_old = deferred_old;
287 iter_new = deferred_new;
288 while (iter_old || iter_new) {
289 if (iter_old && iter_new)
290 print_ssdiff_line("changed", iter_old->line_no,
291 iter_old->line,
292 iter_new->line_no, iter_new->line,
293 individual_chars);
294 else if (iter_old)
295 print_ssdiff_line("changed", iter_old->line_no,
296 iter_old->line, -1, NULL, 0);
297 else if (iter_new)
298 print_ssdiff_line("changed", -1, NULL,
299 iter_new->line_no, iter_new->line, 0);
300 if (iter_old) {
301 tmp = iter_old->next;
302 free(iter_old);
303 iter_old = tmp;
304 }
305
306 if (iter_new) {
307 tmp = iter_new->next;
308 free(iter_new);
309 iter_new = tmp;
310 }
311 }
312 }
313
314 void cgit_ssdiff_print_deferred_lines()
315 {
316 if (!deferred_old && !deferred_new)
317 return;
318 if (deferred_old && !deferred_new)
319 print_deferred_old_lines();
320 else if (!deferred_old && deferred_new)
321 print_deferred_new_lines();
322 else
323 print_deferred_changed_lines();
324 deferred_old = deferred_old_last = NULL;
325 deferred_new = deferred_new_last = NULL;
326 }
327
328 /*
329 * print a single line returned from xdiff
330 */
331 void cgit_ssdiff_line_cb(char *line, int len)
332 {
333 char c = line[len - 1];
334 line[len - 1] = '\0';
335 if (line[0] == '@') {
336 current_old_line = line_from_hunk(line, '-');
337 current_new_line = line_from_hunk(line, '+');
338 }
339
340 if (line[0] == ' ') {
341 if (deferred_old || deferred_new)
342 cgit_ssdiff_print_deferred_lines();
343 print_ssdiff_line("ctx", current_old_line, line,
344 current_new_line, line, 0);
345 current_old_line += 1;
346 current_new_line += 1;
347 } else if (line[0] == '+') {
348 deferred_new_add(line, current_new_line);
349 current_new_line += 1;
350 } else if (line[0] == '-') {
351 deferred_old_add(line, current_old_line);
352 current_old_line += 1;
353 } else if (line[0] == '@') {
354 html("<tr><td colspan='4' class='hunk'>");
355 html_txt(line);
356 html("</td></tr>");
357 } else {
358 html("<tr><td colspan='4' class='ctx'>");
359 html_txt(line);
360 html("</td></tr>");
361 }
362 line[len - 1] = c;
363 }
364
365 void cgit_ssdiff_header_begin()
366 {
367 current_old_line = -1;
368 current_new_line = -1;
369 html("<tr><td class='space' colspan='4'><div></div></td></tr>");
370 html("<tr><td class='head' colspan='4'>");
371 }
372
373 void cgit_ssdiff_header_end()
374 {
375 html("</td><tr>");
376 }
377
378 void cgit_ssdiff_footer()
379 {
380 if (deferred_old || deferred_new)
381 cgit_ssdiff_print_deferred_lines();
382 html("<tr><td class='foot' colspan='4'></td></tr>");
383 }