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/* Print a label for a context diff, with a file name and date or a label. */ 48 49static void 50print_context_label (char const *mark, 51 struct file_data *inf, 52 char const *label) 53{ 54 if (label) 55 fprintf (outfile, "%s %s\n", mark, label); 56 else 57 { 58 char buf[MAX (INT_STRLEN_BOUND (int) + 32, 59 INT_STRLEN_BOUND (time_t) + 11)]; 60 struct tm const *tm = localtime (&inf->stat.st_mtime); 61 int nsec = TIMESPEC_NS (inf->stat.st_mtim); 62 if (! (tm && nstrftime (buf, sizeof buf, time_format, tm, 0, nsec))) 63 { 64 long sec = inf->stat.st_mtime; 65 verify (info_preserved, sizeof inf->stat.st_mtime <= sizeof sec); 66 sprintf (buf, "%ld.%.9d", sec, nsec); 67 } 68 fprintf (outfile, "%s %s\t%s\n", mark, inf->name, buf); 69 } 70} 71 72/* Print a header for a context diff, with the file names and dates. */ 73 74void 75print_context_header (struct file_data inf[], bool unidiff) 76{ 77 if (unidiff) 78 { 79 print_context_label ("---", &inf[0], file_label[0]); 80 print_context_label ("+++", &inf[1], file_label[1]); 81 } 82 else 83 { 84 print_context_label ("***", &inf[0], file_label[0]); 85 print_context_label ("---", &inf[1], file_label[1]); 86 } 87} 88 89/* Print an edit script in context format. */ 90 91void 92print_context_script (struct change *script, bool unidiff) 93{ 94 if (ignore_blank_lines || ignore_regexp.fastmap) 95 mark_ignorable (script); 96 else 97 { 98 struct change *e; 99 for (e = script; e; e = e->link) 100 e->ignore = 0; 101 } 102 103 find_function_last_search = - files[0].prefix_lines; 104 find_function_last_match = LIN_MAX; 105 106 if (unidiff) 107 print_script (script, find_hunk, pr_unidiff_hunk); 108 else 109 print_script (script, find_hunk, pr_context_hunk); 110} 111 112/* Print a pair of line numbers with a comma, translated for file FILE. 113 If the second number is not greater, use the first in place of it. 114 115 Args A and B are internal line numbers. 116 We print the translated (real) line numbers. */ 117 118static void 119print_context_number_range (struct file_data const *file, lin a, lin b) 120{ 121 long trans_a, trans_b; 122 translate_range (file, a, b, &trans_a, &trans_b); 123 124 /* We can have B <= A in the case of a range of no lines. 125 In this case, we should print the line number before the range, 126 which is B. 127 128 POSIX 1003.1-2001 requires two line numbers separated by a comma 129 even if the line numbers are the same. However, this does not 130 match existing practice and is surely an error in the 131 specification. */ 132 133 if (trans_b <= trans_a) 134 fprintf (outfile, "%ld", trans_b); 135 else 136 fprintf (outfile, "%ld,%ld", trans_a, trans_b); 137} 138 139/* Print FUNCTION in a context header. */ 140static void 141print_context_function (FILE *out, char const *function) 142{ 143 int i; 144 putc (' ', out); 145 for (i = 0; i < 40 && function[i] != '\n'; i++) 146 continue; 147 fwrite (function, 1, i, out); 148} 149 150/* Print a portion of an edit script in context format. 151 HUNK is the beginning of the portion to be printed. 152 The end is marked by a `link' that has been nulled out. 153 154 Prints out lines from both files, and precedes each 155 line with the appropriate flag-character. */ 156 157static void 158pr_context_hunk (struct change *hunk) 159{ 160 lin first0, last0, first1, last1, i; 161 char const *prefix; 162 char const *function; 163 FILE *out; 164 165 /* Determine range of line numbers involved in each file. */ 166 167 enum changes changes = analyze_hunk (hunk, &first0, &last0, &first1, &last1); 168 if (! changes) 169 return; 170 171 /* Include a context's width before and after. */ 172 173 i = - files[0].prefix_lines; 174 first0 = MAX (first0 - context, i); 175 first1 = MAX (first1 - context, i); 176 if (last0 < files[0].valid_lines - context) 177 last0 += context; 178 else 179 last0 = files[0].valid_lines - 1; 180 if (last1 < files[1].valid_lines - context) 181 last1 += context; 182 else 183 last1 = files[1].valid_lines - 1; 184 185 /* If desired, find the preceding function definition line in file 0. */ 186 function = 0; 187 if (function_regexp.fastmap) 188 function = find_function (files[0].linbuf, first0); 189 190 begin_output (); 191 out = outfile; 192 193 fprintf (out, "***************"); 194 195 if (function) 196 print_context_function (out, function); 197 198 fprintf (out, "\n*** "); 199 print_context_number_range (&files[0], first0, last0); 200 fprintf (out, " ****\n"); 201 202 if (changes & OLD) 203 { 204 struct change *next = hunk; 205 206 for (i = first0; i <= last0; i++) 207 { 208 /* Skip past changes that apply (in file 0) 209 only to lines before line I. */ 210 211 while (next && next->line0 + next->deleted <= i) 212 next = next->link; 213 214 /* Compute the marking for line I. */ 215 216 prefix = " "; 217 if (next && next->line0 <= i) 218 /* The change NEXT covers this line. 219 If lines were inserted here in file 1, this is "changed". 220 Otherwise it is "deleted". */ 221 prefix = (next->inserted > 0 ? "!" : "-"); 222 223 print_1_line (prefix, &files[0].linbuf[i]); 224 } 225 } 226 227 fprintf (out, "--- "); 228 print_context_number_range (&files[1], first1, last1); 229 fprintf (out, " ----\n"); 230 231 if (changes & NEW) 232 { 233 struct change *next = hunk; 234 235 for (i = first1; i <= last1; i++) 236 { 237 /* Skip past changes that apply (in file 1) 238 only to lines before line I. */ 239 240 while (next && next->line1 + next->inserted <= i) 241 next = next->link; 242 243 /* Compute the marking for line I. */ 244 245 prefix = " "; 246 if (next && next->line1 <= i) 247 /* The change NEXT covers this line. 248 If lines were deleted here in file 0, this is "changed". 249 Otherwise it is "inserted". */ 250 prefix = (next->deleted > 0 ? "!" : "+"); 251 252 print_1_line (prefix, &files[1].linbuf[i]); 253 } 254 } 255} 256 257/* Print a pair of line numbers with a comma, translated for file FILE. 258 If the second number is smaller, use the first in place of it. 259 If the numbers are equal, print just one number. 260 261 Args A and B are internal line numbers. 262 We print the translated (real) line numbers. */ 263 264static void 265print_unidiff_number_range (struct file_data const *file, lin a, lin b) 266{ 267 long trans_a, trans_b; 268 translate_range (file, a, b, &trans_a, &trans_b); 269 270 /* We can have B < A in the case of a range of no lines. 271 In this case, we should print the line number before the range, 272 which is B. */ 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