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