source: vendor/diffutils/2.8.1/src/context.c

Last change on this file was 2556, checked in by bird, 19 years ago

diffutils 2.8.1

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