Imported Upstream version 3.8
[platform/upstream/diffutils.git] / src / context.c
1 /* Context-format output routines for GNU DIFF.
2
3    Copyright (C) 1988-1989, 1991-1995, 1998, 2001-2002, 2004, 2006, 2009-2013,
4    2015-2021 Free Software Foundation, Inc.
5
6    This file is part of GNU DIFF.
7
8    This program is free software: you can redistribute it and/or modify
9    it under the terms of the GNU General Public License as published by
10    the Free Software Foundation, either version 3 of the License, or
11    (at your option) any later version.
12
13    This program is distributed in the hope that it will be useful,
14    but WITHOUT ANY WARRANTY; without even the implied warranty of
15    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16    GNU General Public License for more details.
17
18    You should have received a copy of the GNU General Public License
19    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
20
21 #include "diff.h"
22 #include "c-ctype.h"
23 #include <stat-time.h>
24 #include <strftime.h>
25
26 static char const *find_function (char const * const *, lin);
27 static struct change *find_hunk (struct change *);
28 static void mark_ignorable (struct change *);
29 static void pr_context_hunk (struct change *);
30 static void pr_unidiff_hunk (struct change *);
31
32 /* Last place find_function started searching from.  */
33 static lin find_function_last_search;
34
35 /* The value find_function returned when it started searching there.  */
36 static lin find_function_last_match;
37 \f
38 /* Print a label for a context diff, with a file name and date or a label.  */
39
40 static void
41 print_context_label (char const *mark,
42                      struct file_data *inf,
43                      char const *name,
44                      char const *label)
45 {
46   set_color_context (HEADER_CONTEXT);
47   if (label)
48     fprintf (outfile, "%s %s", mark, label);
49   else
50     {
51       char buf[MAX (INT_STRLEN_BOUND (int) + 32,
52                     INT_STRLEN_BOUND (time_t) + 11)];
53       struct tm const *tm = localtime (&inf->stat.st_mtime);
54       int nsec = get_stat_mtime_ns (&inf->stat);
55       if (! (tm && nstrftime (buf, sizeof buf, time_format, tm, 0, nsec)))
56         {
57           verify (TYPE_IS_INTEGER (time_t));
58           if (LONG_MIN <= TYPE_MINIMUM (time_t)
59               && TYPE_MAXIMUM (time_t) <= LONG_MAX)
60             {
61               long int sec = inf->stat.st_mtime;
62               sprintf (buf, "%ld.%.9d", sec, nsec);
63             }
64           else if (TYPE_MAXIMUM (time_t) <= INTMAX_MAX)
65             {
66               intmax_t sec = inf->stat.st_mtime;
67               sprintf (buf, "%"PRIdMAX".%.9d", sec, nsec);
68             }
69           else
70             {
71               uintmax_t sec = inf->stat.st_mtime;
72               sprintf (buf, "%"PRIuMAX".%.9d", sec, nsec);
73             }
74         }
75       fprintf (outfile, "%s %s\t%s", mark, name, buf);
76     }
77   set_color_context (RESET_CONTEXT);
78   putc ('\n', outfile);
79 }
80
81 /* Print a header for a context diff, with the file names and dates.  */
82
83 void
84 print_context_header (struct file_data inf[], char const *const *names, bool unidiff)
85 {
86   if (unidiff)
87     {
88       print_context_label ("---", &inf[0], names[0], file_label[0]);
89       print_context_label ("+++", &inf[1], names[1], file_label[1]);
90     }
91   else
92     {
93       print_context_label ("***", &inf[0], names[0], file_label[0]);
94       print_context_label ("---", &inf[1], names[1], file_label[1]);
95     }
96 }
97
98 /* Print an edit script in context format.  */
99
100 void
101 print_context_script (struct change *script, bool unidiff)
102 {
103   if (ignore_blank_lines || ignore_regexp.fastmap)
104     mark_ignorable (script);
105   else
106     {
107       struct change *e;
108       for (e = script; e; e = e->link)
109         e->ignore = false;
110     }
111
112   find_function_last_search = - files[0].prefix_lines;
113   find_function_last_match = LIN_MAX;
114
115   if (unidiff)
116     print_script (script, find_hunk, pr_unidiff_hunk);
117   else
118     print_script (script, find_hunk, pr_context_hunk);
119 }
120 \f
121 /* Print a pair of line numbers with a comma, translated for file FILE.
122    If the second number is not greater, use the first in place of it.
123
124    Args A and B are internal line numbers.
125    We print the translated (real) line numbers.  */
126
127 static void
128 print_context_number_range (struct file_data const *file, lin a, lin b)
129 {
130   printint trans_a, trans_b;
131   translate_range (file, a, b, &trans_a, &trans_b);
132
133   /* We can have B <= A in the case of a range of no lines.
134      In this case, we should print the line number before the range,
135      which is B.
136
137      POSIX 1003.1-2001 requires two line numbers separated by a comma
138      even if the line numbers are the same.  However, this does not
139      match existing practice and is surely an error in the
140      specification.  */
141
142   if (trans_b <= trans_a)
143     fprintf (outfile, "%"pI"d", trans_b);
144   else
145     fprintf (outfile, "%"pI"d,%"pI"d", trans_a, trans_b);
146 }
147
148 /* Print FUNCTION in a context header.  */
149 static void
150 print_context_function (FILE *out, char const *function)
151 {
152   int i, j;
153   putc (' ', out);
154   for (i = 0; c_isspace ((unsigned char) function[i]) && function[i] != '\n'; i++)
155     continue;
156   for (j = i; j < i + 40 && function[j] != '\n'; j++)
157     continue;
158   while (i < j && c_isspace ((unsigned char) function[j - 1]))
159     j--;
160   fwrite (function + i, sizeof (char), j - i, out);
161 }
162 \f
163 /* Print a portion of an edit script in context format.
164    HUNK is the beginning of the portion to be printed.
165    The end is marked by a 'link' that has been nulled out.
166
167    Prints out lines from both files, and precedes each
168    line with the appropriate flag-character.  */
169
170 static void
171 pr_context_hunk (struct change *hunk)
172 {
173   lin first0, last0, first1, last1, i;
174   char const *prefix;
175   char const *function;
176   FILE *out;
177
178   /* Determine range of line numbers involved in each file.  */
179
180   enum changes changes = analyze_hunk (hunk, &first0, &last0, &first1, &last1);
181   if (! changes)
182     return;
183
184   /* Include a context's width before and after.  */
185
186   i = - files[0].prefix_lines;
187   first0 = MAX (first0 - context, i);
188   first1 = MAX (first1 - context, i);
189   if (last0 < files[0].valid_lines - context)
190     last0 += context;
191   else
192     last0 = files[0].valid_lines - 1;
193   if (last1 < files[1].valid_lines - context)
194     last1 += context;
195   else
196     last1 = files[1].valid_lines - 1;
197
198   /* If desired, find the preceding function definition line in file 0.  */
199   function = NULL;
200   if (function_regexp.fastmap)
201     function = find_function (files[0].linbuf, first0);
202
203   begin_output ();
204   out = outfile;
205
206   fputs ("***************", out);
207
208   if (function)
209     print_context_function (out, function);
210
211   putc ('\n', out);
212   set_color_context (LINE_NUMBER_CONTEXT);
213   fputs ("*** ", out);
214   print_context_number_range (&files[0], first0, last0);
215   fputs (" ****", out);
216   set_color_context (RESET_CONTEXT);
217   putc ('\n', out);
218
219   if (changes & OLD)
220     {
221       struct change *next = hunk;
222
223       for (i = first0; i <= last0; i++)
224         {
225           set_color_context (DELETE_CONTEXT);
226
227           /* Skip past changes that apply (in file 0)
228              only to lines before line I.  */
229
230           while (next && next->line0 + next->deleted <= i)
231             next = next->link;
232
233           /* Compute the marking for line I.  */
234
235           prefix = " ";
236           if (next && next->line0 <= i)
237             {
238               /* The change NEXT covers this line.
239                  If lines were inserted here in file 1, this is "changed".
240                  Otherwise it is "deleted".  */
241               prefix = (next->inserted > 0 ? "!" : "-");
242             }
243           print_1_line_nl (prefix, &files[0].linbuf[i], true);
244           set_color_context (RESET_CONTEXT);
245           if (files[0].linbuf[i + 1][-1] == '\n')
246             putc ('\n', out);
247         }
248     }
249
250   set_color_context (LINE_NUMBER_CONTEXT);
251   fputs ("--- ", out);
252   print_context_number_range (&files[1], first1, last1);
253   fputs (" ----", out);
254   set_color_context (RESET_CONTEXT);
255   putc ('\n', out);
256
257   if (changes & NEW)
258     {
259       struct change *next = hunk;
260
261       for (i = first1; i <= last1; i++)
262         {
263           set_color_context (ADD_CONTEXT);
264
265           /* Skip past changes that apply (in file 1)
266              only to lines before line I.  */
267
268           while (next && next->line1 + next->inserted <= i)
269             next = next->link;
270
271           /* Compute the marking for line I.  */
272
273           prefix = " ";
274           if (next && next->line1 <= i)
275             {
276               /* The change NEXT covers this line.
277                  If lines were deleted here in file 0, this is "changed".
278                  Otherwise it is "inserted".  */
279               prefix = (next->deleted > 0 ? "!" : "+");
280             }
281           print_1_line_nl (prefix, &files[1].linbuf[i], true);
282           set_color_context (RESET_CONTEXT);
283           if (files[1].linbuf[i + 1][-1] == '\n')
284             putc ('\n', out);
285         }
286     }
287 }
288 \f
289 /* Print a pair of line numbers with a comma, translated for file FILE.
290    If the second number is smaller, use the first in place of it.
291    If the numbers are equal, print just one number.
292
293    Args A and B are internal line numbers.
294    We print the translated (real) line numbers.  */
295
296 static void
297 print_unidiff_number_range (struct file_data const *file, lin a, lin b)
298 {
299   printint trans_a, trans_b;
300   translate_range (file, a, b, &trans_a, &trans_b);
301
302   /* We can have B < A in the case of a range of no lines.
303      In this case, we print the line number before the range,
304      which is B.  It would be more logical to print A, but
305      'patch' expects B in order to detect diffs against empty files.  */
306   if (trans_b <= trans_a)
307     fprintf (outfile, trans_b < trans_a ? "%"pI"d,0" : "%"pI"d", trans_b);
308   else
309     fprintf (outfile, "%"pI"d,%"pI"d", trans_a, trans_b - trans_a + 1);
310 }
311 \f
312 /* Print a portion of an edit script in unidiff format.
313    HUNK is the beginning of the portion to be printed.
314    The end is marked by a 'link' that has been nulled out.
315
316    Prints out lines from both files, and precedes each
317    line with the appropriate flag-character.  */
318
319 static void
320 pr_unidiff_hunk (struct change *hunk)
321 {
322   lin first0, last0, first1, last1;
323   lin i, j, k;
324   struct change *next;
325   char const *function;
326   FILE *out;
327
328   /* Determine range of line numbers involved in each file.  */
329
330   if (! analyze_hunk (hunk, &first0, &last0, &first1, &last1))
331     return;
332
333   /* Include a context's width before and after.  */
334
335   i = - files[0].prefix_lines;
336   first0 = MAX (first0 - context, i);
337   first1 = MAX (first1 - context, i);
338   if (last0 < files[0].valid_lines - context)
339     last0 += context;
340   else
341     last0 = files[0].valid_lines - 1;
342   if (last1 < files[1].valid_lines - context)
343     last1 += context;
344   else
345     last1 = files[1].valid_lines - 1;
346
347   /* If desired, find the preceding function definition line in file 0.  */
348   function = NULL;
349   if (function_regexp.fastmap)
350     function = find_function (files[0].linbuf, first0);
351
352   begin_output ();
353   out = outfile;
354
355   set_color_context (LINE_NUMBER_CONTEXT);
356   fputs ("@@ -", out);
357   print_unidiff_number_range (&files[0], first0, last0);
358   fputs (" +", out);
359   print_unidiff_number_range (&files[1], first1, last1);
360   fputs (" @@", out);
361   set_color_context (RESET_CONTEXT);
362
363   if (function)
364     print_context_function (out, function);
365
366   putc ('\n', out);
367
368   next = hunk;
369   i = first0;
370   j = first1;
371
372   while (i <= last0 || j <= last1)
373     {
374
375       /* If the line isn't a difference, output the context from file 0. */
376
377       if (!next || i < next->line0)
378         {
379           char const *const *line = &files[0].linbuf[i++];
380           if (! (suppress_blank_empty && **line == '\n'))
381             putc (initial_tab ? '\t' : ' ', out);
382           print_1_line (NULL, line);
383           j++;
384         }
385       else
386         {
387           /* For each difference, first output the deleted part. */
388
389           k = next->deleted;
390
391           while (k--)
392             {
393               char const * const *line = &files[0].linbuf[i++];
394               set_color_context (DELETE_CONTEXT);
395               putc ('-', out);
396               if (initial_tab && ! (suppress_blank_empty && **line == '\n'))
397                 putc ('\t', out);
398               print_1_line_nl (NULL, line, true);
399
400               set_color_context (RESET_CONTEXT);
401
402               if (line[1][-1] == '\n')
403                 putc ('\n', out);
404             }
405
406           /* Then output the inserted part. */
407
408           k = next->inserted;
409
410           while (k--)
411             {
412               char const * const *line = &files[1].linbuf[j++];
413               set_color_context (ADD_CONTEXT);
414               putc ('+', out);
415               if (initial_tab && ! (suppress_blank_empty && **line == '\n'))
416                 putc ('\t', out);
417               print_1_line_nl (NULL, line, true);
418
419               set_color_context (RESET_CONTEXT);
420
421               if (line[1][-1] == '\n')
422                 putc ('\n', out);
423             }
424
425           /* We're done with this hunk, so on to the next! */
426
427           next = next->link;
428         }
429     }
430 }
431 \f
432 /* Scan a (forward-ordered) edit script for the first place that more than
433    2*CONTEXT unchanged lines appear, and return a pointer
434    to the 'struct change' for the last change before those lines.  */
435
436 static struct change * _GL_ATTRIBUTE_PURE
437 find_hunk (struct change *start)
438 {
439   struct change *prev;
440   lin top0, top1;
441   lin thresh;
442
443   /* Threshold distance is CONTEXT if the second change is ignorable,
444      2 * CONTEXT + 1 otherwise.  Integer overflow can't happen, due
445      to CONTEXT_LIM.  */
446   lin ignorable_threshold = context;
447   lin non_ignorable_threshold = 2 * context + 1;
448
449   do
450     {
451       /* Compute number of first line in each file beyond this changed.  */
452       top0 = start->line0 + start->deleted;
453       top1 = start->line1 + start->inserted;
454       prev = start;
455       start = start->link;
456       thresh = (start && start->ignore
457                 ? ignorable_threshold
458                 : non_ignorable_threshold);
459       /* It is not supposed to matter which file we check in the end-test.
460          If it would matter, crash.  */
461       if (start && start->line0 - top0 != start->line1 - top1)
462         abort ();
463     } while (start
464              /* Keep going if less than THRESH lines
465                 elapse before the affected line.  */
466              && start->line0 - top0 < thresh);
467
468   return prev;
469 }
470
471 /* Set the 'ignore' flag properly in each change in SCRIPT.
472    It should be 1 if all the lines inserted or deleted in that change
473    are ignorable lines.  */
474
475 static void
476 mark_ignorable (struct change *script)
477 {
478   while (script)
479     {
480       struct change *next = script->link;
481       lin first0, last0, first1, last1;
482
483       /* Turn this change into a hunk: detach it from the others.  */
484       script->link = NULL;
485
486       /* Determine whether this change is ignorable.  */
487       script->ignore = ! analyze_hunk (script,
488                                        &first0, &last0, &first1, &last1);
489
490       /* Reconnect the chain as before.  */
491       script->link = next;
492
493       /* Advance to the following change.  */
494       script = next;
495     }
496 }
497 \f
498 /* Find the last function-header line in LINBUF prior to line number LINENUM.
499    This is a line containing a match for the regexp in 'function_regexp'.
500    Return the address of the text, or NULL if no function-header is found.  */
501
502 static char const *
503 find_function (char const * const *linbuf, lin linenum)
504 {
505   lin i = linenum;
506   lin last = find_function_last_search;
507   find_function_last_search = i;
508
509   while (last <= --i)
510     {
511       /* See if this line is what we want.  */
512       char const *line = linbuf[i];
513       size_t linelen = linbuf[i + 1] - line - 1;
514
515       /* FIXME: re_search's size args should be size_t, not int.  */
516       int len = MIN (linelen, INT_MAX);
517
518       if (0 <= re_search (&function_regexp, line, len, 0, len, NULL))
519         {
520           find_function_last_match = i;
521           return line;
522         }
523     }
524   /* If we search back to where we started searching the previous time,
525      find the line we found last time.  */
526   if (find_function_last_match != LIN_MAX)
527     return linbuf[find_function_last_match];
528
529   return NULL;
530 }