1170754Sdelphij/* Context-format output routines for GNU DIFF.
2170754Sdelphij
3170754Sdelphij   Copyright (C) 1988, 1989, 1991, 1992, 1993, 1994, 1995, 1998, 2001,
4170754Sdelphij   2002, 2004 Free Software Foundation, Inc.
5170754Sdelphij
6170754Sdelphij   This file is part of GNU DIFF.
7170754Sdelphij
8170754Sdelphij   GNU DIFF is free software; you can redistribute it and/or modify
9170754Sdelphij   it under the terms of the GNU General Public License as published by
10170754Sdelphij   the Free Software Foundation; either version 2, or (at your option)
11170754Sdelphij   any later version.
12170754Sdelphij
13170754Sdelphij   GNU DIFF is distributed in the hope that it will be useful,
14170754Sdelphij   but WITHOUT ANY WARRANTY; without even the implied warranty of
15170754Sdelphij   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16170754Sdelphij   GNU General Public License for more details.
17170754Sdelphij
18170754Sdelphij   You should have received a copy of the GNU General Public License
19170754Sdelphij   along with this program; see the file COPYING.
20170754Sdelphij   If not, write to the Free Software Foundation,
21170754Sdelphij   59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
22170754Sdelphij
23170754Sdelphij#include "diff.h"
24170754Sdelphij#include <inttostr.h>
25170754Sdelphij
26170754Sdelphij#ifdef ST_MTIM_NSEC
27170754Sdelphij# define TIMESPEC_NS(timespec) ((timespec).ST_MTIM_NSEC)
28170754Sdelphij#else
29170754Sdelphij# define TIMESPEC_NS(timespec) 0
30170754Sdelphij#endif
31170754Sdelphij
32239360Sobriensize_t nstrftime (char *, size_t, char const *, struct tm const *, int, long);
33170754Sdelphij
34170754Sdelphijstatic char const *find_function (char const * const *, lin);
35170754Sdelphijstatic struct change *find_hunk (struct change *);
36170754Sdelphijstatic void mark_ignorable (struct change *);
37170754Sdelphijstatic void pr_context_hunk (struct change *);
38170754Sdelphijstatic void pr_unidiff_hunk (struct change *);
39170754Sdelphij
40170754Sdelphij/* Last place find_function started searching from.  */
41170754Sdelphijstatic lin find_function_last_search;
42170754Sdelphij
43170754Sdelphij/* The value find_function returned when it started searching there.  */
44170754Sdelphijstatic lin find_function_last_match;
45170754Sdelphij
46170754Sdelphij/* Print a label for a context diff, with a file name and date or a label.  */
47170754Sdelphij
48170754Sdelphijstatic void
49170754Sdelphijprint_context_label (char const *mark,
50170754Sdelphij		     struct file_data *inf,
51170754Sdelphij		     char const *label)
52170754Sdelphij{
53170754Sdelphij  if (label)
54170754Sdelphij    fprintf (outfile, "%s %s\n", mark, label);
55170754Sdelphij  else
56170754Sdelphij    {
57170754Sdelphij      char buf[MAX (INT_STRLEN_BOUND (int) + 32,
58170754Sdelphij		    INT_STRLEN_BOUND (time_t) + 11)];
59170754Sdelphij      struct tm const *tm = localtime (&inf->stat.st_mtime);
60239360Sobrien      long nsec = TIMESPEC_NS (inf->stat.st_mtim);
61170754Sdelphij      if (! (tm && nstrftime (buf, sizeof buf, time_format, tm, 0, nsec)))
62170754Sdelphij	{
63239360Sobrien	  time_t sec = inf->stat.st_mtime;
64170754Sdelphij	  verify (info_preserved, sizeof inf->stat.st_mtime <= sizeof sec);
65239360Sobrien	  sprintf (buf, "%jd.%.9d", (intmax_t)sec, nsec);
66170754Sdelphij	}
67170754Sdelphij      fprintf (outfile, "%s %s\t%s\n", mark, inf->name, buf);
68170754Sdelphij    }
69170754Sdelphij}
70170754Sdelphij
71170754Sdelphij/* Print a header for a context diff, with the file names and dates.  */
72170754Sdelphij
73170754Sdelphijvoid
74170754Sdelphijprint_context_header (struct file_data inf[], bool unidiff)
75170754Sdelphij{
76170754Sdelphij  if (unidiff)
77170754Sdelphij    {
78170754Sdelphij      print_context_label ("---", &inf[0], file_label[0]);
79170754Sdelphij      print_context_label ("+++", &inf[1], file_label[1]);
80170754Sdelphij    }
81170754Sdelphij  else
82170754Sdelphij    {
83170754Sdelphij      print_context_label ("***", &inf[0], file_label[0]);
84170754Sdelphij      print_context_label ("---", &inf[1], file_label[1]);
85170754Sdelphij    }
86170754Sdelphij}
87170754Sdelphij
88170754Sdelphij/* Print an edit script in context format.  */
89170754Sdelphij
90170754Sdelphijvoid
91170754Sdelphijprint_context_script (struct change *script, bool unidiff)
92170754Sdelphij{
93170754Sdelphij  if (ignore_blank_lines || ignore_regexp.fastmap)
94170754Sdelphij    mark_ignorable (script);
95170754Sdelphij  else
96170754Sdelphij    {
97170754Sdelphij      struct change *e;
98170754Sdelphij      for (e = script; e; e = e->link)
99170754Sdelphij	e->ignore = false;
100170754Sdelphij    }
101170754Sdelphij
102170754Sdelphij  find_function_last_search = - files[0].prefix_lines;
103170754Sdelphij  find_function_last_match = LIN_MAX;
104170754Sdelphij
105170754Sdelphij  if (unidiff)
106170754Sdelphij    print_script (script, find_hunk, pr_unidiff_hunk);
107170754Sdelphij  else
108170754Sdelphij    print_script (script, find_hunk, pr_context_hunk);
109170754Sdelphij}
110170754Sdelphij
111170754Sdelphij/* Print a pair of line numbers with a comma, translated for file FILE.
112170754Sdelphij   If the second number is not greater, use the first in place of it.
113170754Sdelphij
114170754Sdelphij   Args A and B are internal line numbers.
115170754Sdelphij   We print the translated (real) line numbers.  */
116170754Sdelphij
117170754Sdelphijstatic void
118170754Sdelphijprint_context_number_range (struct file_data const *file, lin a, lin b)
119170754Sdelphij{
120170754Sdelphij  long int trans_a, trans_b;
121170754Sdelphij  translate_range (file, a, b, &trans_a, &trans_b);
122170754Sdelphij
123170754Sdelphij  /* We can have B <= A in the case of a range of no lines.
124170754Sdelphij     In this case, we should print the line number before the range,
125170754Sdelphij     which is B.
126170754Sdelphij
127170754Sdelphij     POSIX 1003.1-2001 requires two line numbers separated by a comma
128170754Sdelphij     even if the line numbers are the same.  However, this does not
129170754Sdelphij     match existing practice and is surely an error in the
130170754Sdelphij     specification.  */
131170754Sdelphij
132170754Sdelphij  if (trans_b <= trans_a)
133170754Sdelphij    fprintf (outfile, "%ld", trans_b);
134170754Sdelphij  else
135170754Sdelphij    fprintf (outfile, "%ld,%ld", trans_a, trans_b);
136170754Sdelphij}
137170754Sdelphij
138170754Sdelphij/* Print FUNCTION in a context header.  */
139170754Sdelphijstatic void
140170754Sdelphijprint_context_function (FILE *out, char const *function)
141170754Sdelphij{
142170754Sdelphij  int i;
143170754Sdelphij  putc (' ', out);
144170754Sdelphij  for (i = 0; i < 40 && function[i] != '\n'; i++)
145170754Sdelphij    continue;
146170754Sdelphij  fwrite (function, sizeof (char), i, out);
147170754Sdelphij}
148170754Sdelphij
149170754Sdelphij/* Print a portion of an edit script in context format.
150170754Sdelphij   HUNK is the beginning of the portion to be printed.
151170754Sdelphij   The end is marked by a `link' that has been nulled out.
152170754Sdelphij
153170754Sdelphij   Prints out lines from both files, and precedes each
154170754Sdelphij   line with the appropriate flag-character.  */
155170754Sdelphij
156170754Sdelphijstatic void
157170754Sdelphijpr_context_hunk (struct change *hunk)
158170754Sdelphij{
159170754Sdelphij  lin first0, last0, first1, last1, i;
160170754Sdelphij  char const *prefix;
161170754Sdelphij  char const *function;
162170754Sdelphij  FILE *out;
163170754Sdelphij
164170754Sdelphij  /* Determine range of line numbers involved in each file.  */
165170754Sdelphij
166170754Sdelphij  enum changes changes = analyze_hunk (hunk, &first0, &last0, &first1, &last1);
167170754Sdelphij  if (! changes)
168170754Sdelphij    return;
169170754Sdelphij
170170754Sdelphij  /* Include a context's width before and after.  */
171170754Sdelphij
172170754Sdelphij  i = - files[0].prefix_lines;
173170754Sdelphij  first0 = MAX (first0 - context, i);
174170754Sdelphij  first1 = MAX (first1 - context, i);
175170754Sdelphij  if (last0 < files[0].valid_lines - context)
176170754Sdelphij    last0 += context;
177170754Sdelphij  else
178170754Sdelphij    last0 = files[0].valid_lines - 1;
179170754Sdelphij  if (last1 < files[1].valid_lines - context)
180170754Sdelphij    last1 += context;
181170754Sdelphij  else
182170754Sdelphij    last1 = files[1].valid_lines - 1;
183170754Sdelphij
184170754Sdelphij  /* If desired, find the preceding function definition line in file 0.  */
185170754Sdelphij  function = 0;
186170754Sdelphij  if (function_regexp.fastmap)
187170754Sdelphij    function = find_function (files[0].linbuf, first0);
188170754Sdelphij
189170754Sdelphij  begin_output ();
190170754Sdelphij  out = outfile;
191170754Sdelphij
192170754Sdelphij  fprintf (out, "***************");
193170754Sdelphij
194170754Sdelphij  if (function)
195170754Sdelphij    print_context_function (out, function);
196170754Sdelphij
197170754Sdelphij  fprintf (out, "\n*** ");
198170754Sdelphij  print_context_number_range (&files[0], first0, last0);
199170754Sdelphij  fprintf (out, " ****\n");
200170754Sdelphij
201170754Sdelphij  if (changes & OLD)
202170754Sdelphij    {
203170754Sdelphij      struct change *next = hunk;
204170754Sdelphij
205170754Sdelphij      for (i = first0; i <= last0; i++)
206170754Sdelphij	{
207170754Sdelphij	  /* Skip past changes that apply (in file 0)
208170754Sdelphij	     only to lines before line I.  */
209170754Sdelphij
210170754Sdelphij	  while (next && next->line0 + next->deleted <= i)
211170754Sdelphij	    next = next->link;
212170754Sdelphij
213170754Sdelphij	  /* Compute the marking for line I.  */
214170754Sdelphij
215170754Sdelphij	  prefix = " ";
216170754Sdelphij	  if (next && next->line0 <= i)
217170754Sdelphij	    /* The change NEXT covers this line.
218170754Sdelphij	       If lines were inserted here in file 1, this is "changed".
219170754Sdelphij	       Otherwise it is "deleted".  */
220170754Sdelphij	    prefix = (next->inserted > 0 ? "!" : "-");
221170754Sdelphij
222170754Sdelphij	  print_1_line (prefix, &files[0].linbuf[i]);
223170754Sdelphij	}
224170754Sdelphij    }
225170754Sdelphij
226170754Sdelphij  fprintf (out, "--- ");
227170754Sdelphij  print_context_number_range (&files[1], first1, last1);
228170754Sdelphij  fprintf (out, " ----\n");
229170754Sdelphij
230170754Sdelphij  if (changes & NEW)
231170754Sdelphij    {
232170754Sdelphij      struct change *next = hunk;
233170754Sdelphij
234170754Sdelphij      for (i = first1; i <= last1; i++)
235170754Sdelphij	{
236170754Sdelphij	  /* Skip past changes that apply (in file 1)
237170754Sdelphij	     only to lines before line I.  */
238170754Sdelphij
239170754Sdelphij	  while (next && next->line1 + next->inserted <= i)
240170754Sdelphij	    next = next->link;
241170754Sdelphij
242170754Sdelphij	  /* Compute the marking for line I.  */
243170754Sdelphij
244170754Sdelphij	  prefix = " ";
245170754Sdelphij	  if (next && next->line1 <= i)
246170754Sdelphij	    /* The change NEXT covers this line.
247170754Sdelphij	       If lines were deleted here in file 0, this is "changed".
248170754Sdelphij	       Otherwise it is "inserted".  */
249170754Sdelphij	    prefix = (next->deleted > 0 ? "!" : "+");
250170754Sdelphij
251170754Sdelphij	  print_1_line (prefix, &files[1].linbuf[i]);
252170754Sdelphij	}
253170754Sdelphij    }
254170754Sdelphij}
255170754Sdelphij
256170754Sdelphij/* Print a pair of line numbers with a comma, translated for file FILE.
257170754Sdelphij   If the second number is smaller, use the first in place of it.
258170754Sdelphij   If the numbers are equal, print just one number.
259170754Sdelphij
260170754Sdelphij   Args A and B are internal line numbers.
261170754Sdelphij   We print the translated (real) line numbers.  */
262170754Sdelphij
263170754Sdelphijstatic void
264170754Sdelphijprint_unidiff_number_range (struct file_data const *file, lin a, lin b)
265170754Sdelphij{
266170754Sdelphij  long int trans_a, trans_b;
267170754Sdelphij  translate_range (file, a, b, &trans_a, &trans_b);
268170754Sdelphij
269170754Sdelphij  /* We can have B < A in the case of a range of no lines.
270170754Sdelphij     In this case, we print the line number before the range,
271170754Sdelphij     which is B.  It would be more logical to print A, but
272170754Sdelphij     'patch' expects B in order to detect diffs against empty files.  */
273170754Sdelphij  if (trans_b <= trans_a)
274170754Sdelphij    fprintf (outfile, trans_b < trans_a ? "%ld,0" : "%ld", trans_b);
275170754Sdelphij  else
276170754Sdelphij    fprintf (outfile, "%ld,%ld", trans_a, trans_b - trans_a + 1);
277170754Sdelphij}
278170754Sdelphij
279170754Sdelphij/* Print a portion of an edit script in unidiff format.
280170754Sdelphij   HUNK is the beginning of the portion to be printed.
281170754Sdelphij   The end is marked by a `link' that has been nulled out.
282170754Sdelphij
283170754Sdelphij   Prints out lines from both files, and precedes each
284170754Sdelphij   line with the appropriate flag-character.  */
285170754Sdelphij
286170754Sdelphijstatic void
287170754Sdelphijpr_unidiff_hunk (struct change *hunk)
288170754Sdelphij{
289170754Sdelphij  lin first0, last0, first1, last1;
290170754Sdelphij  lin i, j, k;
291170754Sdelphij  struct change *next;
292170754Sdelphij  char const *function;
293170754Sdelphij  FILE *out;
294170754Sdelphij
295170754Sdelphij  /* Determine range of line numbers involved in each file.  */
296170754Sdelphij
297170754Sdelphij  if (! analyze_hunk (hunk, &first0, &last0, &first1, &last1))
298170754Sdelphij    return;
299170754Sdelphij
300170754Sdelphij  /* Include a context's width before and after.  */
301170754Sdelphij
302170754Sdelphij  i = - files[0].prefix_lines;
303170754Sdelphij  first0 = MAX (first0 - context, i);
304170754Sdelphij  first1 = MAX (first1 - context, i);
305170754Sdelphij  if (last0 < files[0].valid_lines - context)
306170754Sdelphij    last0 += context;
307170754Sdelphij  else
308170754Sdelphij    last0 = files[0].valid_lines - 1;
309170754Sdelphij  if (last1 < files[1].valid_lines - context)
310170754Sdelphij    last1 += context;
311170754Sdelphij  else
312170754Sdelphij    last1 = files[1].valid_lines - 1;
313170754Sdelphij
314170754Sdelphij  /* If desired, find the preceding function definition line in file 0.  */
315170754Sdelphij  function = 0;
316170754Sdelphij  if (function_regexp.fastmap)
317170754Sdelphij    function = find_function (files[0].linbuf, first0);
318170754Sdelphij
319170754Sdelphij  begin_output ();
320170754Sdelphij  out = outfile;
321170754Sdelphij
322170754Sdelphij  fprintf (out, "@@ -");
323170754Sdelphij  print_unidiff_number_range (&files[0], first0, last0);
324170754Sdelphij  fprintf (out, " +");
325170754Sdelphij  print_unidiff_number_range (&files[1], first1, last1);
326170754Sdelphij  fprintf (out, " @@");
327170754Sdelphij
328170754Sdelphij  if (function)
329170754Sdelphij    print_context_function (out, function);
330170754Sdelphij
331170754Sdelphij  putc ('\n', out);
332170754Sdelphij
333170754Sdelphij  next = hunk;
334170754Sdelphij  i = first0;
335170754Sdelphij  j = first1;
336170754Sdelphij
337170754Sdelphij  while (i <= last0 || j <= last1)
338170754Sdelphij    {
339170754Sdelphij
340170754Sdelphij      /* If the line isn't a difference, output the context from file 0. */
341170754Sdelphij
342170754Sdelphij      if (!next || i < next->line0)
343170754Sdelphij	{
344170754Sdelphij	  putc (initial_tab ? '\t' : ' ', out);
345170754Sdelphij	  print_1_line (0, &files[0].linbuf[i++]);
346170754Sdelphij	  j++;
347170754Sdelphij	}
348170754Sdelphij      else
349170754Sdelphij	{
350170754Sdelphij	  /* For each difference, first output the deleted part. */
351170754Sdelphij
352170754Sdelphij	  k = next->deleted;
353170754Sdelphij	  while (k--)
354170754Sdelphij	    {
355170754Sdelphij	      putc ('-', out);
356170754Sdelphij	      if (initial_tab)
357170754Sdelphij		putc ('\t', out);
358170754Sdelphij	      print_1_line (0, &files[0].linbuf[i++]);
359170754Sdelphij	    }
360170754Sdelphij
361170754Sdelphij	  /* Then output the inserted part. */
362170754Sdelphij
363170754Sdelphij	  k = next->inserted;
364170754Sdelphij	  while (k--)
365170754Sdelphij	    {
366170754Sdelphij	      putc ('+', out);
367170754Sdelphij	      if (initial_tab)
368170754Sdelphij		putc ('\t', out);
369170754Sdelphij	      print_1_line (0, &files[1].linbuf[j++]);
370170754Sdelphij	    }
371170754Sdelphij
372170754Sdelphij	  /* We're done with this hunk, so on to the next! */
373170754Sdelphij
374170754Sdelphij	  next = next->link;
375170754Sdelphij	}
376170754Sdelphij    }
377170754Sdelphij}
378170754Sdelphij
379170754Sdelphij/* Scan a (forward-ordered) edit script for the first place that more than
380170754Sdelphij   2*CONTEXT unchanged lines appear, and return a pointer
381170754Sdelphij   to the `struct change' for the last change before those lines.  */
382170754Sdelphij
383170754Sdelphijstatic struct change *
384170754Sdelphijfind_hunk (struct change *start)
385170754Sdelphij{
386170754Sdelphij  struct change *prev;
387170754Sdelphij  lin top0, top1;
388170754Sdelphij  lin thresh;
389170754Sdelphij
390170754Sdelphij  /* Threshold distance is 2 * CONTEXT + 1 between two non-ignorable
391170754Sdelphij     changes, but only CONTEXT if one is ignorable.  Watch out for
392170754Sdelphij     integer overflow, though.  */
393170754Sdelphij  lin non_ignorable_threshold =
394170754Sdelphij    (LIN_MAX - 1) / 2 < context ? LIN_MAX : 2 * context + 1;
395170754Sdelphij  lin ignorable_threshold = context;
396170754Sdelphij
397170754Sdelphij  do
398170754Sdelphij    {
399170754Sdelphij      /* Compute number of first line in each file beyond this changed.  */
400170754Sdelphij      top0 = start->line0 + start->deleted;
401170754Sdelphij      top1 = start->line1 + start->inserted;
402170754Sdelphij      prev = start;
403170754Sdelphij      start = start->link;
404170754Sdelphij      thresh = (prev->ignore || (start && start->ignore)
405170754Sdelphij		? ignorable_threshold
406170754Sdelphij		: non_ignorable_threshold);
407170754Sdelphij      /* It is not supposed to matter which file we check in the end-test.
408170754Sdelphij	 If it would matter, crash.  */
409170754Sdelphij      if (start && start->line0 - top0 != start->line1 - top1)
410170754Sdelphij	abort ();
411170754Sdelphij    } while (start
412170754Sdelphij	     /* Keep going if less than THRESH lines
413170754Sdelphij		elapse before the affected line.  */
414170754Sdelphij	     && start->line0 - top0 < thresh);
415170754Sdelphij
416170754Sdelphij  return prev;
417170754Sdelphij}
418170754Sdelphij
419170754Sdelphij/* Set the `ignore' flag properly in each change in SCRIPT.
420170754Sdelphij   It should be 1 if all the lines inserted or deleted in that change
421170754Sdelphij   are ignorable lines.  */
422170754Sdelphij
423170754Sdelphijstatic void
424170754Sdelphijmark_ignorable (struct change *script)
425170754Sdelphij{
426170754Sdelphij  while (script)
427170754Sdelphij    {
428170754Sdelphij      struct change *next = script->link;
429170754Sdelphij      lin first0, last0, first1, last1;
430170754Sdelphij
431170754Sdelphij      /* Turn this change into a hunk: detach it from the others.  */
432170754Sdelphij      script->link = 0;
433170754Sdelphij
434170754Sdelphij      /* Determine whether this change is ignorable.  */
435170754Sdelphij      script->ignore = ! analyze_hunk (script,
436170754Sdelphij				       &first0, &last0, &first1, &last1);
437170754Sdelphij
438170754Sdelphij      /* Reconnect the chain as before.  */
439170754Sdelphij      script->link = next;
440170754Sdelphij
441170754Sdelphij      /* Advance to the following change.  */
442170754Sdelphij      script = next;
443170754Sdelphij    }
444170754Sdelphij}
445170754Sdelphij
446170754Sdelphij/* Find the last function-header line in LINBUF prior to line number LINENUM.
447170754Sdelphij   This is a line containing a match for the regexp in `function_regexp'.
448170754Sdelphij   Return the address of the text, or 0 if no function-header is found.  */
449170754Sdelphij
450170754Sdelphijstatic char const *
451170754Sdelphijfind_function (char const * const *linbuf, lin linenum)
452170754Sdelphij{
453170754Sdelphij  lin i = linenum;
454170754Sdelphij  lin last = find_function_last_search;
455170754Sdelphij  find_function_last_search = i;
456170754Sdelphij
457170754Sdelphij  while (last <= --i)
458170754Sdelphij    {
459170754Sdelphij      /* See if this line is what we want.  */
460170754Sdelphij      char const *line = linbuf[i];
461170754Sdelphij      size_t linelen = linbuf[i + 1] - line - 1;
462170754Sdelphij
463170754Sdelphij      /* FIXME: re_search's size args should be size_t, not int.  */
464170754Sdelphij      int len = MIN (linelen, INT_MAX);
465170754Sdelphij
466170754Sdelphij      if (0 <= re_search (&function_regexp, line, len, 0, len, 0))
467170754Sdelphij	{
468170754Sdelphij	  find_function_last_match = i;
469170754Sdelphij	  return line;
470170754Sdelphij	}
471170754Sdelphij    }
472170754Sdelphij  /* If we search back to where we started searching the previous time,
473170754Sdelphij     find the line we found last time.  */
474170754Sdelphij  if (find_function_last_match != LIN_MAX)
475170754Sdelphij    return linbuf[find_function_last_match];
476170754Sdelphij
477170754Sdelphij  return 0;
478170754Sdelphij}
479