1/* See LICENSE file for copyright and license details. */
2#include "../diffutil.h"
3#include "../text.h"
4#include "../util.h"
5
6#include <ctype.h>
7#include <stdio.h>
8#include <stdlib.h>
9#include <string.h>
10
11static int
12read_all(FILE *fp, char **out, size_t *outlen, const char *path)
13{
14 char buf[8192];
15 char *data;
16 size_t cap, len;
17 ssize_t n;
18
19 data = NULL;
20 cap = len = 0;
21 while ((n = fread(buf, 1, sizeof(buf), fp)) > 0) {
22 if (len + (size_t)n > cap) {
23 cap = (len + (size_t)n) * 2 + 64;
24 data = erealloc(data, cap);
25 }
26 memcpy(data + len, buf, (size_t)n);
27 len += (size_t)n;
28 }
29 if (ferror(fp)) {
30 weprintf("read %s:", path);
31 free(data);
32 return -1;
33 }
34 if (!data) {
35 data = emalloc(1);
36 data[0] = '\0';
37 } else if (len == cap) {
38 data = erealloc(data, cap + 1);
39 data[len] = '\0';
40 }
41 *out = data;
42 *outlen = len;
43 return 0;
44}
45
46static void
47split_lines(struct diffline **out, size_t *outn, char *data, size_t len)
48{
49 struct diffline *lines;
50 size_t i, n, start;
51
52 n = 0;
53 for (i = 0; i < len; i++)
54 if (data[i] == '\n')
55 n++;
56 if (len > 0 && (len == 0 || data[len - 1] != '\n'))
57 n++;
58 if (n == 0) {
59 *out = NULL;
60 *outn = 0;
61 return;
62 }
63 lines = ecalloc(n, sizeof(*lines));
64 i = 0;
65 start = 0;
66 for (n = 0; n < (size_t)len; n++) {
67 if (data[n] == '\n') {
68 lines[i].data = data + start;
69 lines[i].len = n - start;
70 i++;
71 start = n + 1;
72 }
73 }
74 if (start < len) {
75 lines[i].data = data + start;
76 lines[i].len = len - start;
77 i++;
78 }
79 *out = lines;
80 *outn = i;
81}
82
83int
84diff_load(struct difffile *df, const char *path)
85{
86 FILE *fp;
87 char *data;
88 size_t len;
89
90 if (!strcmp(path, "-")) {
91 fp = stdin;
92 } else if (!(fp = fopen(path, "r"))) {
93 weprintf("open %s:", path);
94 return -1;
95 }
96 if (read_all(fp, &data, &len, path) < 0) {
97 if (fp != stdin)
98 fclose(fp);
99 return -1;
100 }
101 if (fp != stdin)
102 fclose(fp);
103 df->lines = NULL;
104 df->nlines = 0;
105 if (len == 0) {
106 df->lines = ecalloc(1, sizeof(*df->lines));
107 df->lines[0].data = data;
108 df->lines[0].len = 0;
109 df->nlines = 0;
110 return 0;
111 }
112 split_lines(&df->lines, &df->nlines, data, len);
113 {
114 struct diffline *grown;
115 grown = erealloc(df->lines, (df->nlines + 1) * sizeof(*grown));
116 grown[df->nlines].data = data;
117 grown[df->nlines].len = 0;
118 df->lines = grown;
119 }
120 return 0;
121}
122
123void
124diff_free(struct difffile *df)
125{
126 char *base;
127
128 if (!df->lines)
129 return;
130 if (df->nlines == 0) {
131 free(df->lines[0].data);
132 free(df->lines);
133 df->lines = NULL;
134 df->nlines = 0;
135 return;
136 }
137 base = df->lines[df->nlines].data;
138 free(base);
139 free(df->lines);
140 df->lines = NULL;
141 df->nlines = 0;
142}
143
144static int
145lines_equal(const struct diffline *a, const struct diffline *b, const struct diffopts *opts)
146{
147 size_t i, j;
148
149 if (opts && opts->ignore_blanks) {
150 i = j = 0;
151 while (i < a->len && j < b->len) {
152 while (i < a->len && isspace((unsigned char)a->data[i]))
153 i++;
154 while (j < b->len && isspace((unsigned char)b->data[j]))
155 j++;
156 if (i >= a->len || j >= b->len)
157 break;
158 if (opts->ignore_case) {
159 if (tolower((unsigned char)a->data[i]) != tolower((unsigned char)b->data[j]))
160 return 0;
161 } else if (a->data[i] != b->data[j]) {
162 return 0;
163 }
164 i++;
165 j++;
166 }
167 while (i < a->len && isspace((unsigned char)a->data[i]))
168 i++;
169 while (j < b->len && isspace((unsigned char)b->data[j]))
170 j++;
171 return i == a->len && j == b->len;
172 }
173
174 if (opts && opts->ignore_case) {
175 if (a->len != b->len)
176 return 0;
177 for (i = 0; i < a->len; i++) {
178 if (tolower((unsigned char)a->data[i]) != tolower((unsigned char)b->data[i]))
179 return 0;
180 }
181 return 1;
182 }
183
184 if (a->len != b->len)
185 return 0;
186 return memcmp(a->data, b->data, a->len) == 0;
187}
188
189struct lcs_cell {
190 int diag;
191 int up;
192 int left;
193};
194
195int
196diff_compute(
197 struct diffhunks *hunks,
198 const struct difffile *old,
199 const struct difffile *new,
200 const struct diffopts *opts
201)
202{
203 struct lcs_cell *c;
204 size_t n, m, i, j, k, cap;
205 struct diffhunk *h;
206 int *xs, *ys;
207 size_t hfrom, hto, vfrom, vto;
208 struct diffopts default_opts;
209
210 if (!opts) {
211 memset(&default_opts, 0, sizeof(default_opts));
212 default_opts.context = 3;
213 opts = &default_opts;
214 }
215
216 n = old->nlines;
217 m = new->nlines;
218
219 if (n == 0 && m == 0) {
220 hunks->v = NULL;
221 hunks->n = 0;
222 return 0;
223 }
224
225 c = ecalloc((n + 1) * (m + 1), sizeof(*c));
226 for (i = 0; i <= n; i++) {
227 for (j = 0; j <= m; j++) {
228 if (i == 0 && j == 0) {
229 c[i * (m + 1) + j].diag = 0;
230 continue;
231 }
232 if (i > 0 && j > 0 && lines_equal(&old->lines[i - 1], &new->lines[j - 1], opts)) {
233 c[i * (m + 1) + j].diag = c[(i - 1) * (m + 1) + (j - 1)].diag + 1;
234 c[i * (m + 1) + j].up = 0;
235 c[i * (m + 1) + j].left = 0;
236 continue;
237 }
238 if (i > 0 && (j == 0 || c[(i - 1) * (m + 1) + j].diag >= c[i * (m + 1) + (j - 1)].diag)) {
239 c[i * (m + 1) + j].diag = c[(i - 1) * (m + 1) + j].diag;
240 c[i * (m + 1) + j].up = 1;
241 c[i * (m + 1) + j].left = 0;
242 } else {
243 c[i * (m + 1) + j].diag = c[i * (m + 1) + (j - 1)].diag;
244 c[i * (m + 1) + j].up = 0;
245 c[i * (m + 1) + j].left = 1;
246 }
247 }
248 }
249
250 cap = n + m + 16;
251 xs = ecalloc(cap, sizeof(*xs));
252 ys = ecalloc(cap, sizeof(*ys));
253 k = 0;
254 i = n;
255 j = m;
256 while (i > 0 || j > 0) {
257 if (i > 0 && j > 0 && lines_equal(&old->lines[i - 1], &new->lines[j - 1], opts)) {
258 xs[k] = 0;
259 ys[k] = 0;
260 i--;
261 j--;
262 } else if (i > 0 && c[i * (m + 1) + j].up) {
263 xs[k] = 1;
264 ys[k] = 0;
265 i--;
266 } else {
267 xs[k] = 0;
268 ys[k] = 2;
269 j--;
270 }
271 k++;
272 }
273 hunks->v = NULL;
274 hunks->n = 0;
275 h = NULL;
276 i = 0;
277 j = 0;
278 {
279 size_t step, idx;
280 int op;
281 step = k;
282 while (step > 0) {
283 idx = step - 1;
284 op = xs[idx] | ys[idx];
285 if (op == 0) {
286 step--;
287 i++;
288 j++;
289 continue;
290 }
291 hfrom = i;
292 vfrom = j;
293 hto = i;
294 vto = j;
295 while (step > 0) {
296 idx = step - 1;
297 op = xs[idx] | ys[idx];
298 if (op == 0)
299 break;
300 if (xs[idx] == 1)
301 hto++;
302 if (ys[idx] == 2)
303 vto++;
304 step--;
305 }
306 i = hto;
307 j = vto;
308 hunks->v = erealloc(hunks->v, (hunks->n + 1) * sizeof(*h));
309 h = &hunks->v[hunks->n];
310 h->old.from = hfrom;
311 h->old.to = hto;
312 h->new.from = vfrom;
313 h->new.to = vto;
314 hunks->n++;
315 }
316 }
317 free(c);
318 free(xs);
319 free(ys);
320 return 0;
321}
322
323void
324diff_hunks_free(struct diffhunks *hunks)
325{
326 if (!hunks)
327 return;
328 free(hunks->v);
329 hunks->v = NULL;
330 hunks->n = 0;
331}
332
333int
334diff_looks_binary(const struct difffile *df)
335{
336 size_t i, scan;
337
338 scan = 4096;
339 for (i = 0; i < df->nlines && scan > 0; i++) {
340 size_t take = df->lines[i].len < scan ? df->lines[i].len : scan;
341 if (memchr(df->lines[i].data, '\0', take))
342 return 1;
343 scan -= take;
344 }
345 return 0;
346}
347
348static void
349print_range(FILE *out, size_t from, size_t to)
350{
351 if (to - from <= 1)
352 fprintf(out, "%zu", from + 1);
353 else
354 fprintf(out, "%zu,%zu", from + 1, to);
355}
356
357static void
358print_lines(FILE *out, const struct diffline *lines, size_t from, size_t to, char prefix)
359{
360 size_t i;
361 for (i = from; i < to; i++) {
362 fputc(prefix, out);
363 fwrite(lines[i].data, 1, lines[i].len, out);
364 fputc('\n', out);
365 }
366}
367
368static int
369format_normal(
370 FILE *out, const struct diffhunks *hunks, const struct difffile *old, const struct difffile *new
371)
372{
373 size_t i;
374 for (i = 0; i < hunks->n; i++) {
375 struct diffhunk *h = &hunks->v[i];
376 size_t oldn = h->old.to - h->old.from;
377 size_t newn = h->new.to - h->new.from;
378 char oc;
379
380 oc = oldn == 0 ? 'a' : (newn == 0 ? 'd' : 'c');
381 print_range(out, h->old.from, h->old.to);
382 fputc(oc, out);
383 print_range(out, h->new.from, h->new.to);
384 fputc('\n', out);
385 if (oldn && newn) {
386 print_lines(out, old->lines, h->old.from, h->old.to, '<');
387 fprintf(out, "---\n");
388 print_lines(out, new->lines, h->new.from, h->new.to, '>');
389 } else if (oldn) {
390 print_lines(out, old->lines, h->old.from, h->old.to, '<');
391 } else {
392 print_lines(out, new->lines, h->new.from, h->new.to, '>');
393 }
394 }
395 return 0;
396}
397
398static void
399emit_unified_hunk(
400 FILE *out,
401 const struct diffhunk *h,
402 const struct difffile *old,
403 const struct difffile *new,
404 size_t ctx
405)
406{
407 size_t oldfrom, oldto, newfrom, newto;
408 size_t i;
409
410 oldfrom = h->old.from < ctx ? 0 : h->old.from - ctx;
411 oldto = h->old.to + ctx;
412 if (oldto > old->nlines)
413 oldto = old->nlines;
414 newfrom = h->new.from < ctx ? 0 : h->new.from - ctx;
415 newto = h->new.to + ctx;
416 if (newto > new->nlines)
417 newto = new->nlines;
418
419 if (oldto - oldfrom <= 1)
420 fprintf(out, "@@ -%zu +%zu,%zu @@\n", oldfrom + 1, newfrom + 1, newto - newfrom);
421 else if (newto - newfrom <= 1)
422 fprintf(out, "@@ -%zu,%zu +%zu @@\n", oldfrom + 1, oldto - oldfrom, newfrom + 1);
423 else
424 fprintf(
425 out, "@@ -%zu,%zu +%zu,%zu @@\n", oldfrom + 1, oldto - oldfrom, newfrom + 1, newto - newfrom
426 );
427
428 for (i = oldfrom; i < h->old.from; i++) {
429 fputc(' ', out);
430 fwrite(old->lines[i].data, 1, old->lines[i].len, out);
431 fputc('\n', out);
432 }
433 print_lines(out, old->lines, h->old.from, h->old.to, '-');
434 print_lines(out, new->lines, h->new.from, h->new.to, '+');
435 for (i = h->old.to; i < oldto; i++) {
436 fputc(' ', out);
437 fwrite(old->lines[i].data, 1, old->lines[i].len, out);
438 fputc('\n', out);
439 }
440}
441
442static int
443format_unified(
444 FILE *out,
445 const struct diffhunks *hunks,
446 const struct difffile *old,
447 const struct difffile *new,
448 const char *oldlabel,
449 const char *newlabel,
450 size_t ctx
451)
452{
453 size_t i;
454 if (oldlabel)
455 fprintf(out, "--- %s\n", oldlabel);
456 if (newlabel)
457 fprintf(out, "+++ %s\n", newlabel);
458 for (i = 0; i < hunks->n; i++)
459 emit_unified_hunk(out, &hunks->v[i], old, new, ctx);
460 return 0;
461}
462
463static void
464emit_context_hunk(
465 FILE *out,
466 const struct diffhunk *h,
467 const struct difffile *old,
468 const struct difffile *new,
469 size_t ctx
470)
471{
472 size_t oldfrom, oldto, newfrom, newto;
473 size_t i;
474
475 oldfrom = h->old.from < ctx ? 0 : h->old.from - ctx;
476 oldto = h->old.to + ctx;
477 if (oldto > old->nlines)
478 oldto = old->nlines;
479 newfrom = h->new.from < ctx ? 0 : h->new.from - ctx;
480 newto = h->new.to + ctx;
481 if (newto > new->nlines)
482 newto = new->nlines;
483
484 fprintf(out, "***************\n");
485 if (oldto - oldfrom <= 1)
486 fprintf(out, "*** %zu ****\n", oldfrom + 1);
487 else
488 fprintf(out, "*** %zu,%zu ****\n", oldfrom + 1, oldto - oldfrom);
489 for (i = oldfrom; i < h->old.from; i++) {
490 fputc(' ', out);
491 fwrite(old->lines[i].data, 1, old->lines[i].len, out);
492 fputc('\n', out);
493 }
494 print_lines(out, old->lines, h->old.from, h->old.to, '-');
495 for (i = h->old.to; i < oldto; i++) {
496 fputc(' ', out);
497 fwrite(old->lines[i].data, 1, old->lines[i].len, out);
498 fputc('\n', out);
499 }
500 if (newto - newfrom <= 1)
501 fprintf(out, "--- %zu ----\n", newfrom + 1);
502 else
503 fprintf(out, "--- %zu,%zu ----\n", newfrom + 1, newto - newfrom);
504 for (i = newfrom; i < h->new.from; i++) {
505 fputc(' ', out);
506 fwrite(new->lines[i].data, 1, new->lines[i].len, out);
507 fputc('\n', out);
508 }
509 print_lines(out, new->lines, h->new.from, h->new.to, '+');
510 for (i = h->new.to; i < newto; i++) {
511 fputc(' ', out);
512 fwrite(new->lines[i].data, 1, new->lines[i].len, out);
513 fputc('\n', out);
514 }
515}
516
517static int
518format_context(
519 FILE *out,
520 const struct diffhunks *hunks,
521 const struct difffile *old,
522 const struct difffile *new,
523 const char *oldlabel,
524 const char *newlabel,
525 size_t ctx
526)
527{
528 size_t i;
529 if (oldlabel)
530 fprintf(out, "*** %s\n", oldlabel);
531 if (newlabel)
532 fprintf(out, "--- %s\n", newlabel);
533 for (i = 0; i < hunks->n; i++)
534 emit_context_hunk(out, &hunks->v[i], old, new, ctx);
535 return 0;
536}
537
538static int
539format_ed(
540 FILE *out, const struct diffhunks *hunks, const struct difffile *old, const struct difffile *new
541)
542{
543 size_t i;
544 (void)old;
545 for (i = 0; i < hunks->n; i++) {
546 struct diffhunk *h = &hunks->v[i];
547 size_t oldn = h->old.to - h->old.from;
548 size_t newn = h->new.to - h->new.from;
549 char op;
550
551 op = oldn == 0 ? 'a' : (newn == 0 ? 'd' : 'c');
552 print_range(out, h->old.from, h->old.to);
553 fputc(op, out);
554 fputc('\n', out);
555 if (op == 'a' || op == 'c') {
556 size_t j;
557 for (j = h->new.from; j < h->new.to; j++) {
558 if (new->lines[j].len == 1 && new->lines[j].data[0] == '.')
559 fprintf(out, "..\n");
560 else if (new->lines[j].len == 0)
561 fprintf(out, ".\n");
562 else {
563 fwrite(new->lines[j].data, 1, new->lines[j].len, out);
564 fputc('\n', out);
565 }
566 }
567 fprintf(out, ".\n");
568 }
569 }
570 return 0;
571}
572
573int
574diff_format(
575 FILE *out,
576 const struct diffhunks *hunks,
577 const struct difffile *old,
578 const struct difffile *new,
579 const char *oldlabel,
580 const char *newlabel,
581 const struct diffopts *opts
582)
583{
584 struct diffopts default_opts;
585 size_t ctx;
586
587 if (!opts) {
588 memset(&default_opts, 0, sizeof(default_opts));
589 default_opts.context = 3;
590 opts = &default_opts;
591 }
592 ctx = opts->context ? opts->context : 3;
593 switch (opts->format) {
594 case DIFF_NORMAL:
595 return format_normal(out, hunks, old, new);
596 case DIFF_UNIFIED:
597 return format_unified(out, hunks, old, new, oldlabel, newlabel, ctx);
598 case DIFF_CONTEXT:
599 return format_context(out, hunks, old, new, oldlabel, newlabel, ctx);
600 case DIFF_ED:
601 case DIFF_RCSED:
602 return format_ed(out, hunks, old, new);
603 case DIFF_BRIEF:
604 if (hunks->n > 0)
605 return 1;
606 return 0;
607 }
608 return 0;
609}