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
32size_t nstrftime (char *, size_t, char const *, struct tm const *, int, long);
33
34static char const *find_function (char const * const *, lin);
35static struct change *find_hunk (struct change *);
36static void mark_ignorable (struct change *);
37static void pr_context_hunk (struct change *);
38static void pr_unidiff_hunk (struct change *);
39
40/* Last place find_function started searching from.  */
41static lin find_function_last_search;
42
43/* The value find_function returned when it started searching there.  */
44static lin find_function_last_match;
45
46/* Print a label for a context diff, with a file name and date or a label.  */
47
48static void
49print_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
73void
74print_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
90void
91print_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
117static void
118print_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.  */
139static void
140print_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
156static void
157pr_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
263static void
264print_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
286static void
287pr_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
383static struct change *
384find_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
423static void
424mark_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
450static char const *
451find_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