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