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
print_context_label(char const * mark,struct file_data * inf,char const * label)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
print_context_header(struct file_data inf[],bool unidiff)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
print_context_script(struct change * script,bool unidiff)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
print_context_number_range(struct file_data const * file,lin a,lin b)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
print_context_function(FILE * out,char const * function)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
pr_context_hunk(struct change * hunk)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
print_unidiff_number_range(struct file_data const * file,lin a,lin b)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
pr_unidiff_hunk(struct change * hunk)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 *
find_hunk(struct change * start)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
mark_ignorable(struct change * script)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 *
find_function(char const * const * linbuf,lin linenum)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