master xplshn/aruu / cmd / pseudo / diff3.c
  1/* See LICENSE file for copyright and license details. */
  2#include "config.h"
  3#include "diffutil.h"
  4#include "util.h"
  5
  6#include <ctype.h>
  7#include <stdio.h>
  8#include <stdlib.h>
  9#include <string.h>
 10#include <unistd.h>
 11
 12struct region {
 13  size_t from;
 14  size_t to;
 15};
 16
 17struct pair_hunk {
 18  struct region old;
 19  struct region new;
 20};
 21
 22enum hunk_kind {
 23  HUNK_NONE,
 24  HUNK_TYPE1,
 25  HUNK_TYPE2,
 26  HUNK_TYPE3,
 27};
 28
 29struct three_hunk {
 30  enum hunk_kind kind;
 31  struct region  r1;
 32  struct region  r2;
 33  struct region  r3;
 34  int            dup;
 35};
 36
 37static int   aflag;
 38static int   Tflag;
 39static int   eflag;
 40static int   Eflag;
 41static int   xflag;
 42static int   Xflag;
 43static int   Aflag;
 44static int   mflag;
 45static int   iflag;
 46static int   strip_cr;
 47static char *labels[3];
 48static int   nlabels;
 49
 50static void
 51usage(void)
 52{
 53  eprintf("usage: %s [-3aAeEimTxX] [-L label] file1 file2 file3\n", argv0);
 54}
 55
 56static void
 57convert_hunks(const struct diffhunks *in, struct pair_hunk **out, size_t *outn)
 58{
 59  size_t            i;
 60  struct pair_hunk *p;
 61
 62  p = ecalloc(in->n + 1, sizeof(*p));
 63  for (i = 0; i < in->n; i++) {
 64    p[i].old.from = in->v[i].old.from;
 65    p[i].old.to   = in->v[i].old.to;
 66    p[i].new.from = in->v[i].new.from;
 67    p[i].new.to   = in->v[i].new.to;
 68  }
 69  *out  = p;
 70  *outn = in->n;
 71}
 72
 73static int
 74region_same(
 75    const struct difffile *a, struct region *ra, const struct difffile *b, struct region *rb
 76)
 77{
 78  size_t i;
 79  if (ra->to - ra->from != rb->to - rb->from)
 80    return 0;
 81  for (i = 0; i < ra->to - ra->from; i++) {
 82    if (a->lines[ra->from + i].len != b->lines[rb->from + i].len)
 83      return 0;
 84    if (memcmp(a->lines[ra->from + i].data, b->lines[rb->from + i].data, a->lines[ra->from + i].len)
 85        != 0)
 86      return 0;
 87  }
 88  return 1;
 89}
 90
 91static int
 92merge_hunks(
 93    const struct difffile  *f1,
 94    const struct difffile  *f2,
 95    const struct difffile  *f3,
 96    const struct pair_hunk *d13,
 97    size_t                  n13,
 98    const struct pair_hunk *d23,
 99    size_t                  n23,
100    struct three_hunk     **out,
101    size_t                 *outn
102)
103{
104  struct three_hunk *hs;
105  size_t             i, j, k, cap;
106  int                have1, have2;
107  size_t             a1, a2, b1, b2;
108
109  cap = n13 + n23 + 4;
110  hs  = ecalloc(cap, sizeof(*hs));
111  k   = 0;
112  i = j = 0;
113
114  while (i < n13 || j < n23) {
115    have1 = i < n13;
116    have2 = j < n23;
117    a1    = have1 ? d13[i].new.from : 0;
118    a2    = have1 ? d13[i].new.to : 0;
119    b1    = have2 ? d23[j].old.from : 0;
120    b2    = have2 ? d23[j].old.to : 0;
121
122    if (have1 && (!have2 || a2 <= b1)) {
123      if (k + 1 >= cap) {
124        cap *= 2;
125        hs = erealloc(hs, cap * sizeof(*hs));
126      }
127      hs[k].kind    = HUNK_TYPE1;
128      hs[k].r1.from = d13[i].old.from;
129      hs[k].r1.to   = d13[i].old.to;
130      hs[k].r2.from = d13[i].new.from;
131      hs[k].r2.to   = d13[i].new.to;
132      hs[k].r3.from = d13[i].new.from;
133      hs[k].r3.to   = d13[i].new.to;
134      hs[k].dup     = 0;
135      k++;
136      i++;
137      continue;
138    }
139    if (have2 && (!have1 || b2 < a1)) {
140      if (k + 1 >= cap) {
141        cap *= 2;
142        hs = erealloc(hs, cap * sizeof(*hs));
143      }
144      hs[k].kind    = HUNK_TYPE2;
145      hs[k].r1.from = d23[j].old.from;
146      hs[k].r1.to   = d23[j].old.to;
147      hs[k].r2.from = d23[j].old.from;
148      hs[k].r2.to   = d23[j].old.to;
149      hs[k].r3.from = d23[j].new.from;
150      hs[k].r3.to   = d23[j].new.to;
151      hs[k].dup     = 0;
152      k++;
153      j++;
154      continue;
155    }
156    {
157      struct region r2;
158      size_t        extra_pre, extra_post;
159
160      r2.from    = a1 < b1 ? a1 : b1;
161      r2.to      = a2 > b2 ? a2 : b2;
162      extra_pre  = (a1 > b1) ? (a1 - b1) : (b1 - a1);
163      extra_post = (a2 > b2) ? (a2 - b2) : (b2 - a2);
164      if (k + 1 >= cap) {
165        cap *= 2;
166        hs = erealloc(hs, cap * sizeof(*hs));
167      }
168      hs[k].kind = HUNK_TYPE3;
169      hs[k].r2   = r2;
170      if (a1 >= b1) {
171        hs[k].r1.from = d13[i].old.from - extra_pre;
172        hs[k].r1.to   = d13[i].old.to + extra_post;
173      } else {
174        hs[k].r1.from = d13[i].old.from;
175        hs[k].r1.to   = d13[i].old.to + (extra_pre + extra_post);
176      }
177      if (b1 >= a1) {
178        hs[k].r3.from = d23[j].new.from - extra_pre;
179        hs[k].r3.to   = d23[j].new.to + extra_post;
180      } else {
181        hs[k].r3.from = d23[j].new.from;
182        hs[k].r3.to   = d23[j].new.to + (extra_pre + extra_post);
183      }
184      hs[k].dup = region_same(f1, &hs[k].r1, f3, &hs[k].r3);
185      k++;
186      i++;
187      j++;
188    }
189  }
190  *out  = hs;
191  *outn = k;
192  (void)f1;
193  (void)f2;
194  (void)f3;
195  return 0;
196}
197
198static void
199print_lines(const struct difffile *f, size_t from, size_t to)
200{
201  size_t i;
202  for (i = from; i < to; i++) {
203    fwrite(f->lines[i].data, 1, f->lines[i].len, stdout);
204    fputc('\n', stdout);
205  }
206}
207
208static void
209print_range_cmd(FILE *out, size_t from, size_t to, char op)
210{
211  if (to <= from) {
212    fprintf(out, "%zua\n", from == 0 ? 0 : from);
213  } else {
214    fprintf(out, "%zu", from + 1);
215    if (to - from > 1)
216      fprintf(out, ",%zu", to);
217    fprintf(out, "%c\n", op);
218  }
219}
220
221static int
222format_normal(
223    const struct three_hunk *hs,
224    size_t                   n,
225    const struct difffile   *f1,
226    const struct difffile   *f2,
227    const struct difffile   *f3
228)
229{
230  size_t i;
231  for (i = 0; i < n; i++) {
232    const struct three_hunk *h   = &hs[i];
233    const char              *tag = "";
234    if (h->kind == HUNK_TYPE1)
235      tag = "1";
236    else if (h->kind == HUNK_TYPE2)
237      tag = "2";
238    else if (h->kind == HUNK_TYPE3)
239      tag = h->dup ? "3" : "";
240    printf("====%s\n", tag);
241    if (h->kind == HUNK_TYPE1) {
242      printf("1:%zu", h->r1.from + 1);
243      if (h->r1.to - h->r1.from > 1)
244        printf(",%zu", h->r1.to);
245      printf("a\n");
246      printf("3:%zu", h->r3.from + 1);
247      if (h->r3.to - h->r3.from > 1)
248        printf(",%zu", h->r3.to);
249      printf("a\n");
250      print_lines(f1, h->r1.from, h->r1.to);
251    } else if (h->kind == HUNK_TYPE2) {
252      printf("2:%zu", h->r2.from + 1);
253      if (h->r2.to - h->r2.from > 1)
254        printf(",%zu", h->r2.to);
255      printf("a\n");
256      printf("3:%zu", h->r3.from + 1);
257      if (h->r3.to - h->r3.from > 1)
258        printf(",%zu", h->r3.to);
259      printf("a\n");
260      print_lines(f3, h->r3.from, h->r3.to);
261    } else {
262      printf("1:%zu", h->r1.from + 1);
263      if (h->r1.to - h->r1.from > 1)
264        printf(",%zu", h->r1.to);
265      printf("c\n");
266      printf("2:%zu", h->r2.from + 1);
267      if (h->r2.to - h->r2.from > 1)
268        printf(",%zu", h->r2.to);
269      printf("c\n");
270      printf("3:%zu", h->r3.from + 1);
271      if (h->r3.to - h->r3.from > 1)
272        printf(",%zu", h->r3.to);
273      printf("c\n");
274      print_lines(f1, h->r1.from, h->r1.to);
275      print_lines(f3, h->r3.from, h->r3.to);
276    }
277  }
278  (void)f2;
279  return 0;
280}
281
282static int
283format_ed(
284    const struct three_hunk *hs,
285    size_t                   n,
286    int                      overlap_only,
287    int                      show_all,
288    const struct difffile   *f2,
289    const struct difffile   *f3
290)
291{
292  size_t i;
293  for (i = n; i > 0; i--) {
294    const struct three_hunk *h = &hs[i - 1];
295
296    if (h->kind == HUNK_TYPE1) {
297      if (h->r1.to == h->r1.from) {
298        print_range_cmd(stdout, h->r2.from, h->r2.from, 'a');
299        print_lines(f2, h->r2.from, h->r2.from);
300        printf(".\n");
301      } else {
302        print_range_cmd(stdout, h->r2.from, h->r2.to, 'c');
303        print_lines(f2, h->r1.from, h->r1.to);
304        printf(".\n");
305      }
306      continue;
307    }
308    if (h->kind == HUNK_TYPE2) {
309      print_range_cmd(stdout, h->r2.from, h->r2.to, 'c');
310      print_lines(f3, h->r3.from, h->r3.to);
311      printf(".\n");
312      continue;
313    }
314    if (h->dup) {
315      print_range_cmd(stdout, h->r2.from, h->r2.to, 'c');
316      print_lines(f2, h->r1.from, h->r1.to);
317      printf(".\n");
318      continue;
319    }
320    if (overlap_only)
321      continue;
322    if (show_all) {
323      print_range_cmd(stdout, h->r2.from, h->r2.to, 'c');
324      print_lines(f3, h->r3.from, h->r3.to);
325      printf(".\n");
326    }
327  }
328  return 0;
329}
330
331#if FEATURE_DIFF3_MERGE
332static int
333format_merge(
334    const struct three_hunk *hs,
335    size_t                   n,
336    const struct difffile   *f1,
337    const struct difffile   *f2,
338    const struct difffile   *f3
339)
340{
341  size_t      i, j;
342  size_t      cursor;
343  const char *l1, *l2, *l3;
344
345  l1     = nlabels >= 1 ? labels[0] : "file1";
346  l2     = nlabels >= 2 ? labels[1] : "file2";
347  l3     = nlabels >= 3 ? labels[2] : "file3";
348  cursor = 0;
349  for (i = 0; i < n; i++) {
350    const struct three_hunk *h = &hs[i];
351    for (j = cursor; j < h->r2.from; j++) {
352      fwrite(f2->lines[j].data, 1, f2->lines[j].len, stdout);
353      fputc('\n', stdout);
354    }
355    cursor = h->r2.to;
356    if (h->kind == HUNK_TYPE1) {
357      print_lines(f1, h->r1.from, h->r1.to);
358    } else if (h->kind == HUNK_TYPE2) {
359      print_lines(f3, h->r3.from, h->r3.to);
360    } else if (h->dup) {
361      print_lines(f1, h->r1.from, h->r1.to);
362    } else {
363      printf("<<<<<<< %s\n", l1);
364      print_lines(f1, h->r1.from, h->r1.to);
365      printf("||||||| %s\n", l2);
366      print_lines(f2, h->r2.from, h->r2.to);
367      printf("=======\n");
368      print_lines(f3, h->r3.from, h->r3.to);
369      printf(">>>>>>> %s\n", l3);
370    }
371  }
372  for (j = cursor; j < f2->nlines; j++) {
373    fwrite(f2->lines[j].data, 1, f2->lines[j].len, stdout);
374    fputc('\n', stdout);
375  }
376  return 0;
377}
378#endif
379
380// ?man diff3: three-way file comparison
381// ?man arguments: file1 file2 file3
382// ?man compare three files line by line and show the differences
383int
384main(int argc, char *argv[])
385{
386  struct difffile    f1, f2, f3;
387  struct pair_hunk  *d13, *d23;
388  size_t             n13, n23;
389  struct three_hunk *hs;
390  size_t             nhunks;
391  struct diffhunks   raw13, raw23;
392  struct diffopts    opts;
393  int                ret, conflicts;
394
395  ARGBEGIN
396  {
397    case '3':
398      // ?man -3: like -A but skip overlapping changes
399      Aflag = 1;
400      break;
401    case 'a':
402      // ?man -a: treat all files as text
403      aflag = 1;
404      break;
405    case 'A':
406      // ?man -A: emit ed script with all changes including overlaps
407      Aflag = 1;
408      break;
409    case 'e':
410      // ?man -e: emit ed script to apply non-overlapping changes
411      eflag = 1;
412      break;
413    case 'E':
414      // ?man -E: like -e but include overlap markers
415      Eflag = 1;
416      break;
417#if FEATURE_DIFF3_MERGE
418    case 'i':
419      // ?man -i: ignore case when comparing lines
420      iflag = 1;
421      break;
422    case 'L':
423      // ?man -L:label: use label in place of file name in merge output
424      if (nlabels >= 3)
425        usage();
426      labels[nlabels++] = EARGF(usage());
427      break;
428    case 'm':
429      // ?man -m: emit merged output with conflict markers
430      mflag = 1;
431      break;
432    case 'T':
433      // ?man -T: prefix output lines with a tab
434      Tflag = 1;
435      break;
436#endif
437    case 'x':
438      // ?man -x: emit ed script for overlapping changes only
439      xflag = 1;
440      break;
441    case 'X':
442      // ?man -X: emit ed script for overlapping changes with markers
443      Xflag = 1;
444      break;
445    default:
446      usage();
447  }
448  ARGEND
449
450  if (argc != 3)
451    usage();
452
453  if (diff_load(&f1, argv[0]) < 0)
454    return 2;
455  if (diff_load(&f2, argv[1]) < 0) {
456    diff_free(&f1);
457    return 2;
458  }
459  if (diff_load(&f3, argv[2]) < 0) {
460    diff_free(&f1);
461    diff_free(&f2);
462    return 2;
463  }
464
465  memset(&opts, 0, sizeof(opts));
466  opts.format      = DIFF_NORMAL;
467  opts.ignore_case = iflag;
468  opts.strip_cr    = strip_cr;
469  opts.context     = 3;
470
471  if (diff_compute(&raw13, &f1, &f2, &opts) < 0 || diff_compute(&raw23, &f2, &f3, &opts) < 0) {
472    diff_free(&f1);
473    diff_free(&f2);
474    diff_free(&f3);
475    return 2;
476  }
477  convert_hunks(&raw13, &d13, &n13);
478  convert_hunks(&raw23, &d23, &n23);
479
480  if (merge_hunks(&f1, &f2, &f3, d13, n13, d23, n23, &hs, &nhunks) < 0) {
481    diff_hunks_free(&raw13);
482    diff_hunks_free(&raw23);
483    free(d13);
484    free(d23);
485    diff_free(&f1);
486    diff_free(&f2);
487    diff_free(&f3);
488    return 2;
489  }
490
491  ret       = 0;
492  conflicts = 0;
493  if (nhunks == 0) {
494    ret = 0;
495  } else {
496#if FEATURE_DIFF3_MERGE
497    if (mflag) {
498      size_t i;
499      for (i = 0; i < nhunks; i++)
500        if (hs[i].kind == HUNK_TYPE3 && !hs[i].dup)
501          conflicts++;
502      format_merge(hs, nhunks, &f1, &f2, &f3);
503      ret = conflicts ? 1 : 0;
504    } else
505#endif
506        if (eflag || Eflag) {
507      format_ed(hs, nhunks, Eflag && !eflag, 0, &f2, &f3);
508      ret = 1;
509    } else if (xflag) {
510      format_ed(hs, nhunks, 1, 0, &f2, &f3);
511      ret = 1;
512    } else if (Xflag) {
513      format_ed(hs, nhunks, 0, 1, &f2, &f3);
514      ret = 1;
515    } else if (Aflag) {
516      format_ed(hs, nhunks, 0, 1, &f2, &f3);
517      ret = 1;
518    } else {
519      format_normal(hs, nhunks, &f1, &f2, &f3);
520      ret = 1;
521    }
522  }
523
524  if (fshut(stdout, "<stdout>"))
525    ret = 2;
526
527  diff_hunks_free(&raw13);
528  diff_hunks_free(&raw23);
529  free(d13);
530  free(d23);
531  free(hs);
532  diff_free(&f1);
533  diff_free(&f2);
534  diff_free(&f3);
535  return ret;
536}