1*f8c23a2bSchristos /* $NetBSD: context.c,v 1.2 2016/01/13 03:39:28 christos Exp $ */
275f6d617Schristos
375f6d617Schristos /* Context-format output routines for GNU DIFF.
475f6d617Schristos
575f6d617Schristos Copyright (C) 1988, 1989, 1991, 1992, 1993, 1994, 1995, 1998, 2001,
675f6d617Schristos 2002 Free Software Foundation, Inc.
775f6d617Schristos
875f6d617Schristos This file is part of GNU DIFF.
975f6d617Schristos
1075f6d617Schristos GNU DIFF is free software; you can redistribute it and/or modify
1175f6d617Schristos it under the terms of the GNU General Public License as published by
1275f6d617Schristos the Free Software Foundation; either version 2, or (at your option)
1375f6d617Schristos any later version.
1475f6d617Schristos
1575f6d617Schristos GNU DIFF is distributed in the hope that it will be useful,
1675f6d617Schristos but WITHOUT ANY WARRANTY; without even the implied warranty of
1775f6d617Schristos MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1875f6d617Schristos GNU General Public License for more details.
1975f6d617Schristos
2075f6d617Schristos You should have received a copy of the GNU General Public License
2175f6d617Schristos along with this program; see the file COPYING.
2275f6d617Schristos If not, write to the Free Software Foundation,
2375f6d617Schristos 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
2475f6d617Schristos
2575f6d617Schristos #include "diff.h"
2675f6d617Schristos #include <inttostr.h>
2775f6d617Schristos #include <regex.h>
2875f6d617Schristos
2975f6d617Schristos #ifdef ST_MTIM_NSEC
3075f6d617Schristos # define TIMESPEC_NS(timespec) ((timespec).ST_MTIM_NSEC)
3175f6d617Schristos #else
3275f6d617Schristos # define TIMESPEC_NS(timespec) 0
3375f6d617Schristos #endif
3475f6d617Schristos
3575f6d617Schristos size_t nstrftime (char *, size_t, char const *, struct tm const *, int, int);
3675f6d617Schristos
3775f6d617Schristos static char const *find_function (char const * const *, lin);
3875f6d617Schristos static struct change *find_hunk (struct change *);
3975f6d617Schristos static void mark_ignorable (struct change *);
4075f6d617Schristos static void pr_context_hunk (struct change *);
4175f6d617Schristos static void pr_unidiff_hunk (struct change *);
4275f6d617Schristos
4375f6d617Schristos /* Last place find_function started searching from. */
4475f6d617Schristos static lin find_function_last_search;
4575f6d617Schristos
4675f6d617Schristos /* The value find_function returned when it started searching there. */
4775f6d617Schristos static lin find_function_last_match;
4875f6d617Schristos
4975f6d617Schristos /* Print a label for a context diff, with a file name and date or a label. */
5075f6d617Schristos
5175f6d617Schristos static void
print_context_label(char const * mark,struct file_data * inf,char const * label)5275f6d617Schristos print_context_label (char const *mark,
5375f6d617Schristos struct file_data *inf,
5475f6d617Schristos char const *label)
5575f6d617Schristos {
5675f6d617Schristos if (label)
5775f6d617Schristos fprintf (outfile, "%s %s\n", mark, label);
5875f6d617Schristos else
5975f6d617Schristos {
6075f6d617Schristos char buf[MAX (INT_STRLEN_BOUND (int) + 32,
6175f6d617Schristos INT_STRLEN_BOUND (time_t) + 11)];
6275f6d617Schristos struct tm const *tm = localtime (&inf->stat.st_mtime);
6375f6d617Schristos int nsec = TIMESPEC_NS (inf->stat.st_mtim);
6475f6d617Schristos if (! (tm && nstrftime (buf, sizeof buf, time_format, tm, 0, nsec)))
6575f6d617Schristos {
66*f8c23a2bSchristos long long sec = inf->stat.st_mtime;
6775f6d617Schristos verify (info_preserved, sizeof inf->stat.st_mtime <= sizeof sec);
68*f8c23a2bSchristos sprintf (buf, "%lld.%.9d", sec, nsec);
6975f6d617Schristos }
7075f6d617Schristos fprintf (outfile, "%s %s\t%s\n", mark, inf->name, buf);
7175f6d617Schristos }
7275f6d617Schristos }
7375f6d617Schristos
7475f6d617Schristos /* Print a header for a context diff, with the file names and dates. */
7575f6d617Schristos
7675f6d617Schristos void
print_context_header(struct file_data inf[],bool unidiff)7775f6d617Schristos print_context_header (struct file_data inf[], bool unidiff)
7875f6d617Schristos {
7975f6d617Schristos if (unidiff)
8075f6d617Schristos {
8175f6d617Schristos print_context_label ("---", &inf[0], file_label[0]);
8275f6d617Schristos print_context_label ("+++", &inf[1], file_label[1]);
8375f6d617Schristos }
8475f6d617Schristos else
8575f6d617Schristos {
8675f6d617Schristos print_context_label ("***", &inf[0], file_label[0]);
8775f6d617Schristos print_context_label ("---", &inf[1], file_label[1]);
8875f6d617Schristos }
8975f6d617Schristos }
9075f6d617Schristos
9175f6d617Schristos /* Print an edit script in context format. */
9275f6d617Schristos
9375f6d617Schristos void
print_context_script(struct change * script,bool unidiff)9475f6d617Schristos print_context_script (struct change *script, bool unidiff)
9575f6d617Schristos {
9675f6d617Schristos if (ignore_blank_lines || ignore_regexp.fastmap)
9775f6d617Schristos mark_ignorable (script);
9875f6d617Schristos else
9975f6d617Schristos {
10075f6d617Schristos struct change *e;
10175f6d617Schristos for (e = script; e; e = e->link)
10275f6d617Schristos e->ignore = 0;
10375f6d617Schristos }
10475f6d617Schristos
10575f6d617Schristos find_function_last_search = - files[0].prefix_lines;
10675f6d617Schristos find_function_last_match = LIN_MAX;
10775f6d617Schristos
10875f6d617Schristos if (unidiff)
10975f6d617Schristos print_script (script, find_hunk, pr_unidiff_hunk);
11075f6d617Schristos else
11175f6d617Schristos print_script (script, find_hunk, pr_context_hunk);
11275f6d617Schristos }
11375f6d617Schristos
11475f6d617Schristos /* Print a pair of line numbers with a comma, translated for file FILE.
11575f6d617Schristos If the second number is not greater, use the first in place of it.
11675f6d617Schristos
11775f6d617Schristos Args A and B are internal line numbers.
11875f6d617Schristos We print the translated (real) line numbers. */
11975f6d617Schristos
12075f6d617Schristos static void
print_context_number_range(struct file_data const * file,lin a,lin b)12175f6d617Schristos print_context_number_range (struct file_data const *file, lin a, lin b)
12275f6d617Schristos {
12375f6d617Schristos long trans_a, trans_b;
12475f6d617Schristos translate_range (file, a, b, &trans_a, &trans_b);
12575f6d617Schristos
12675f6d617Schristos /* We can have B <= A in the case of a range of no lines.
12775f6d617Schristos In this case, we should print the line number before the range,
12875f6d617Schristos which is B.
12975f6d617Schristos
13075f6d617Schristos POSIX 1003.1-2001 requires two line numbers separated by a comma
13175f6d617Schristos even if the line numbers are the same. However, this does not
13275f6d617Schristos match existing practice and is surely an error in the
13375f6d617Schristos specification. */
13475f6d617Schristos
13575f6d617Schristos if (trans_b <= trans_a)
13675f6d617Schristos fprintf (outfile, "%ld", trans_b);
13775f6d617Schristos else
13875f6d617Schristos fprintf (outfile, "%ld,%ld", trans_a, trans_b);
13975f6d617Schristos }
14075f6d617Schristos
14175f6d617Schristos /* Print FUNCTION in a context header. */
14275f6d617Schristos static void
print_context_function(FILE * out,char const * function)14375f6d617Schristos print_context_function (FILE *out, char const *function)
14475f6d617Schristos {
14575f6d617Schristos int i;
14675f6d617Schristos putc (' ', out);
14775f6d617Schristos for (i = 0; i < 40 && function[i] != '\n'; i++)
14875f6d617Schristos continue;
14975f6d617Schristos fwrite (function, 1, i, out);
15075f6d617Schristos }
15175f6d617Schristos
15275f6d617Schristos /* Print a portion of an edit script in context format.
15375f6d617Schristos HUNK is the beginning of the portion to be printed.
15475f6d617Schristos The end is marked by a `link' that has been nulled out.
15575f6d617Schristos
15675f6d617Schristos Prints out lines from both files, and precedes each
15775f6d617Schristos line with the appropriate flag-character. */
15875f6d617Schristos
15975f6d617Schristos static void
pr_context_hunk(struct change * hunk)16075f6d617Schristos pr_context_hunk (struct change *hunk)
16175f6d617Schristos {
16275f6d617Schristos lin first0, last0, first1, last1, i;
16375f6d617Schristos char const *prefix;
16475f6d617Schristos char const *function;
16575f6d617Schristos FILE *out;
16675f6d617Schristos
16775f6d617Schristos /* Determine range of line numbers involved in each file. */
16875f6d617Schristos
16975f6d617Schristos enum changes changes = analyze_hunk (hunk, &first0, &last0, &first1, &last1);
17075f6d617Schristos if (! changes)
17175f6d617Schristos return;
17275f6d617Schristos
17375f6d617Schristos /* Include a context's width before and after. */
17475f6d617Schristos
17575f6d617Schristos i = - files[0].prefix_lines;
17675f6d617Schristos first0 = MAX (first0 - context, i);
17775f6d617Schristos first1 = MAX (first1 - context, i);
17875f6d617Schristos if (last0 < files[0].valid_lines - context)
17975f6d617Schristos last0 += context;
18075f6d617Schristos else
18175f6d617Schristos last0 = files[0].valid_lines - 1;
18275f6d617Schristos if (last1 < files[1].valid_lines - context)
18375f6d617Schristos last1 += context;
18475f6d617Schristos else
18575f6d617Schristos last1 = files[1].valid_lines - 1;
18675f6d617Schristos
18775f6d617Schristos /* If desired, find the preceding function definition line in file 0. */
18875f6d617Schristos function = 0;
18975f6d617Schristos if (function_regexp.fastmap)
19075f6d617Schristos function = find_function (files[0].linbuf, first0);
19175f6d617Schristos
19275f6d617Schristos begin_output ();
19375f6d617Schristos out = outfile;
19475f6d617Schristos
19575f6d617Schristos fprintf (out, "***************");
19675f6d617Schristos
19775f6d617Schristos if (function)
19875f6d617Schristos print_context_function (out, function);
19975f6d617Schristos
20075f6d617Schristos fprintf (out, "\n*** ");
20175f6d617Schristos print_context_number_range (&files[0], first0, last0);
20275f6d617Schristos fprintf (out, " ****\n");
20375f6d617Schristos
20475f6d617Schristos if (changes & OLD)
20575f6d617Schristos {
20675f6d617Schristos struct change *next = hunk;
20775f6d617Schristos
20875f6d617Schristos for (i = first0; i <= last0; i++)
20975f6d617Schristos {
21075f6d617Schristos /* Skip past changes that apply (in file 0)
21175f6d617Schristos only to lines before line I. */
21275f6d617Schristos
21375f6d617Schristos while (next && next->line0 + next->deleted <= i)
21475f6d617Schristos next = next->link;
21575f6d617Schristos
21675f6d617Schristos /* Compute the marking for line I. */
21775f6d617Schristos
21875f6d617Schristos prefix = " ";
21975f6d617Schristos if (next && next->line0 <= i)
22075f6d617Schristos /* The change NEXT covers this line.
22175f6d617Schristos If lines were inserted here in file 1, this is "changed".
22275f6d617Schristos Otherwise it is "deleted". */
22375f6d617Schristos prefix = (next->inserted > 0 ? "!" : "-");
22475f6d617Schristos
22575f6d617Schristos print_1_line (prefix, &files[0].linbuf[i]);
22675f6d617Schristos }
22775f6d617Schristos }
22875f6d617Schristos
22975f6d617Schristos fprintf (out, "--- ");
23075f6d617Schristos print_context_number_range (&files[1], first1, last1);
23175f6d617Schristos fprintf (out, " ----\n");
23275f6d617Schristos
23375f6d617Schristos if (changes & NEW)
23475f6d617Schristos {
23575f6d617Schristos struct change *next = hunk;
23675f6d617Schristos
23775f6d617Schristos for (i = first1; i <= last1; i++)
23875f6d617Schristos {
23975f6d617Schristos /* Skip past changes that apply (in file 1)
24075f6d617Schristos only to lines before line I. */
24175f6d617Schristos
24275f6d617Schristos while (next && next->line1 + next->inserted <= i)
24375f6d617Schristos next = next->link;
24475f6d617Schristos
24575f6d617Schristos /* Compute the marking for line I. */
24675f6d617Schristos
24775f6d617Schristos prefix = " ";
24875f6d617Schristos if (next && next->line1 <= i)
24975f6d617Schristos /* The change NEXT covers this line.
25075f6d617Schristos If lines were deleted here in file 0, this is "changed".
25175f6d617Schristos Otherwise it is "inserted". */
25275f6d617Schristos prefix = (next->deleted > 0 ? "!" : "+");
25375f6d617Schristos
25475f6d617Schristos print_1_line (prefix, &files[1].linbuf[i]);
25575f6d617Schristos }
25675f6d617Schristos }
25775f6d617Schristos }
25875f6d617Schristos
25975f6d617Schristos /* Print a pair of line numbers with a comma, translated for file FILE.
26075f6d617Schristos If the second number is smaller, use the first in place of it.
26175f6d617Schristos If the numbers are equal, print just one number.
26275f6d617Schristos
26375f6d617Schristos Args A and B are internal line numbers.
26475f6d617Schristos We print the translated (real) line numbers. */
26575f6d617Schristos
26675f6d617Schristos static void
print_unidiff_number_range(struct file_data const * file,lin a,lin b)26775f6d617Schristos print_unidiff_number_range (struct file_data const *file, lin a, lin b)
26875f6d617Schristos {
26975f6d617Schristos long trans_a, trans_b;
27075f6d617Schristos translate_range (file, a, b, &trans_a, &trans_b);
27175f6d617Schristos
27275f6d617Schristos /* We can have B < A in the case of a range of no lines.
27375f6d617Schristos In this case, we should print the line number before the range,
27475f6d617Schristos which is B. */
27575f6d617Schristos if (trans_b <= trans_a)
27675f6d617Schristos fprintf (outfile, trans_b < trans_a ? "%ld,0" : "%ld", trans_b);
27775f6d617Schristos else
27875f6d617Schristos fprintf (outfile, "%ld,%ld", trans_a, trans_b - trans_a + 1);
27975f6d617Schristos }
28075f6d617Schristos
28175f6d617Schristos /* Print a portion of an edit script in unidiff format.
28275f6d617Schristos HUNK is the beginning of the portion to be printed.
28375f6d617Schristos The end is marked by a `link' that has been nulled out.
28475f6d617Schristos
28575f6d617Schristos Prints out lines from both files, and precedes each
28675f6d617Schristos line with the appropriate flag-character. */
28775f6d617Schristos
28875f6d617Schristos static void
pr_unidiff_hunk(struct change * hunk)28975f6d617Schristos pr_unidiff_hunk (struct change *hunk)
29075f6d617Schristos {
29175f6d617Schristos lin first0, last0, first1, last1;
29275f6d617Schristos lin i, j, k;
29375f6d617Schristos struct change *next;
29475f6d617Schristos char const *function;
29575f6d617Schristos FILE *out;
29675f6d617Schristos
29775f6d617Schristos /* Determine range of line numbers involved in each file. */
29875f6d617Schristos
29975f6d617Schristos if (! analyze_hunk (hunk, &first0, &last0, &first1, &last1))
30075f6d617Schristos return;
30175f6d617Schristos
30275f6d617Schristos /* Include a context's width before and after. */
30375f6d617Schristos
30475f6d617Schristos i = - files[0].prefix_lines;
30575f6d617Schristos first0 = MAX (first0 - context, i);
30675f6d617Schristos first1 = MAX (first1 - context, i);
30775f6d617Schristos if (last0 < files[0].valid_lines - context)
30875f6d617Schristos last0 += context;
30975f6d617Schristos else
31075f6d617Schristos last0 = files[0].valid_lines - 1;
31175f6d617Schristos if (last1 < files[1].valid_lines - context)
31275f6d617Schristos last1 += context;
31375f6d617Schristos else
31475f6d617Schristos last1 = files[1].valid_lines - 1;
31575f6d617Schristos
31675f6d617Schristos /* If desired, find the preceding function definition line in file 0. */
31775f6d617Schristos function = 0;
31875f6d617Schristos if (function_regexp.fastmap)
31975f6d617Schristos function = find_function (files[0].linbuf, first0);
32075f6d617Schristos
32175f6d617Schristos begin_output ();
32275f6d617Schristos out = outfile;
32375f6d617Schristos
32475f6d617Schristos fprintf (out, "@@ -");
32575f6d617Schristos print_unidiff_number_range (&files[0], first0, last0);
32675f6d617Schristos fprintf (out, " +");
32775f6d617Schristos print_unidiff_number_range (&files[1], first1, last1);
32875f6d617Schristos fprintf (out, " @@");
32975f6d617Schristos
33075f6d617Schristos if (function)
33175f6d617Schristos print_context_function (out, function);
33275f6d617Schristos
33375f6d617Schristos putc ('\n', out);
33475f6d617Schristos
33575f6d617Schristos next = hunk;
33675f6d617Schristos i = first0;
33775f6d617Schristos j = first1;
33875f6d617Schristos
33975f6d617Schristos while (i <= last0 || j <= last1)
34075f6d617Schristos {
34175f6d617Schristos
34275f6d617Schristos /* If the line isn't a difference, output the context from file 0. */
34375f6d617Schristos
34475f6d617Schristos if (!next || i < next->line0)
34575f6d617Schristos {
34675f6d617Schristos putc (initial_tab ? '\t' : ' ', out);
34775f6d617Schristos print_1_line (0, &files[0].linbuf[i++]);
34875f6d617Schristos j++;
34975f6d617Schristos }
35075f6d617Schristos else
35175f6d617Schristos {
35275f6d617Schristos /* For each difference, first output the deleted part. */
35375f6d617Schristos
35475f6d617Schristos k = next->deleted;
35575f6d617Schristos while (k--)
35675f6d617Schristos {
35775f6d617Schristos putc ('-', out);
35875f6d617Schristos if (initial_tab)
35975f6d617Schristos putc ('\t', out);
36075f6d617Schristos print_1_line (0, &files[0].linbuf[i++]);
36175f6d617Schristos }
36275f6d617Schristos
36375f6d617Schristos /* Then output the inserted part. */
36475f6d617Schristos
36575f6d617Schristos k = next->inserted;
36675f6d617Schristos while (k--)
36775f6d617Schristos {
36875f6d617Schristos putc ('+', out);
36975f6d617Schristos if (initial_tab)
37075f6d617Schristos putc ('\t', out);
37175f6d617Schristos print_1_line (0, &files[1].linbuf[j++]);
37275f6d617Schristos }
37375f6d617Schristos
37475f6d617Schristos /* We're done with this hunk, so on to the next! */
37575f6d617Schristos
37675f6d617Schristos next = next->link;
37775f6d617Schristos }
37875f6d617Schristos }
37975f6d617Schristos }
38075f6d617Schristos
38175f6d617Schristos /* Scan a (forward-ordered) edit script for the first place that more than
38275f6d617Schristos 2*CONTEXT unchanged lines appear, and return a pointer
38375f6d617Schristos to the `struct change' for the last change before those lines. */
38475f6d617Schristos
38575f6d617Schristos static struct change *
find_hunk(struct change * start)38675f6d617Schristos find_hunk (struct change *start)
38775f6d617Schristos {
38875f6d617Schristos struct change *prev;
38975f6d617Schristos lin top0, top1;
39075f6d617Schristos lin thresh;
39175f6d617Schristos
39275f6d617Schristos /* Threshold distance is 2 * CONTEXT + 1 between two non-ignorable
39375f6d617Schristos changes, but only CONTEXT if one is ignorable. Watch out for
39475f6d617Schristos integer overflow, though. */
39575f6d617Schristos lin non_ignorable_threshold =
39675f6d617Schristos (LIN_MAX - 1) / 2 < context ? LIN_MAX : 2 * context + 1;
39775f6d617Schristos lin ignorable_threshold = context;
39875f6d617Schristos
39975f6d617Schristos do
40075f6d617Schristos {
40175f6d617Schristos /* Compute number of first line in each file beyond this changed. */
40275f6d617Schristos top0 = start->line0 + start->deleted;
40375f6d617Schristos top1 = start->line1 + start->inserted;
40475f6d617Schristos prev = start;
40575f6d617Schristos start = start->link;
40675f6d617Schristos thresh = (prev->ignore || (start && start->ignore)
40775f6d617Schristos ? ignorable_threshold
40875f6d617Schristos : non_ignorable_threshold);
40975f6d617Schristos /* It is not supposed to matter which file we check in the end-test.
41075f6d617Schristos If it would matter, crash. */
41175f6d617Schristos if (start && start->line0 - top0 != start->line1 - top1)
41275f6d617Schristos abort ();
41375f6d617Schristos } while (start
41475f6d617Schristos /* Keep going if less than THRESH lines
41575f6d617Schristos elapse before the affected line. */
41675f6d617Schristos && start->line0 - top0 < thresh);
41775f6d617Schristos
41875f6d617Schristos return prev;
41975f6d617Schristos }
42075f6d617Schristos
42175f6d617Schristos /* Set the `ignore' flag properly in each change in SCRIPT.
42275f6d617Schristos It should be 1 if all the lines inserted or deleted in that change
42375f6d617Schristos are ignorable lines. */
42475f6d617Schristos
42575f6d617Schristos static void
mark_ignorable(struct change * script)42675f6d617Schristos mark_ignorable (struct change *script)
42775f6d617Schristos {
42875f6d617Schristos while (script)
42975f6d617Schristos {
43075f6d617Schristos struct change *next = script->link;
43175f6d617Schristos lin first0, last0, first1, last1;
43275f6d617Schristos
43375f6d617Schristos /* Turn this change into a hunk: detach it from the others. */
43475f6d617Schristos script->link = 0;
43575f6d617Schristos
43675f6d617Schristos /* Determine whether this change is ignorable. */
43775f6d617Schristos script->ignore = ! analyze_hunk (script,
43875f6d617Schristos &first0, &last0, &first1, &last1);
43975f6d617Schristos
44075f6d617Schristos /* Reconnect the chain as before. */
44175f6d617Schristos script->link = next;
44275f6d617Schristos
44375f6d617Schristos /* Advance to the following change. */
44475f6d617Schristos script = next;
44575f6d617Schristos }
44675f6d617Schristos }
44775f6d617Schristos
44875f6d617Schristos /* Find the last function-header line in LINBUF prior to line number LINENUM.
44975f6d617Schristos This is a line containing a match for the regexp in `function_regexp'.
45075f6d617Schristos Return the address of the text, or 0 if no function-header is found. */
45175f6d617Schristos
45275f6d617Schristos static char const *
find_function(char const * const * linbuf,lin linenum)45375f6d617Schristos find_function (char const * const *linbuf, lin linenum)
45475f6d617Schristos {
45575f6d617Schristos lin i = linenum;
45675f6d617Schristos lin last = find_function_last_search;
45775f6d617Schristos find_function_last_search = i;
45875f6d617Schristos
45975f6d617Schristos while (last <= --i)
46075f6d617Schristos {
46175f6d617Schristos /* See if this line is what we want. */
46275f6d617Schristos char const *line = linbuf[i];
46375f6d617Schristos size_t linelen = linbuf[i + 1] - line - 1;
46475f6d617Schristos
46575f6d617Schristos /* FIXME: re_search's size args should be size_t, not int. */
46675f6d617Schristos int len = MIN (linelen, INT_MAX);
46775f6d617Schristos
46875f6d617Schristos if (0 <= re_search (&function_regexp, line, len, 0, len, 0))
46975f6d617Schristos {
47075f6d617Schristos find_function_last_match = i;
47175f6d617Schristos return line;
47275f6d617Schristos }
47375f6d617Schristos }
47475f6d617Schristos /* If we search back to where we started searching the previous time,
47575f6d617Schristos find the line we found last time. */
47675f6d617Schristos if (find_function_last_match != LIN_MAX)
47775f6d617Schristos return linbuf[find_function_last_match];
47875f6d617Schristos
47975f6d617Schristos return 0;
48075f6d617Schristos }
481