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 35size_t nstrftime (char *, size_t, char const *, struct tm const *, int, int); 36 37static char const *find_function (char const * const *, lin); 38static struct change *find_hunk (struct change *); 39static void mark_ignorable (struct change *); 40static void pr_context_hunk (struct change *); 41static void pr_unidiff_hunk (struct change *); 42 43/* Last place find_function started searching from. */ 44static lin find_function_last_search; 45 46/* The value find_function returned when it started searching there. */ 47static lin find_function_last_match; 48 49/* Print a label for a context diff, with a file name and date or a label. */ 50 51static void 52print_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 76void 77print_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 93void 94print_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 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/* 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 159static void 160pr_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 266static void 267print_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 288static void 289pr_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 385static struct change * 386find_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 425static void 426mark_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 452static char const * 453find_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