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