xref: /dragonfly/contrib/diffutils/src/context.c (revision f0dba201)
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-2018 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 
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 
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 
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 
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 
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 
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 
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 }
531