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