1 /* 2 * Copyright (C) 1986-2005 The Free Software Foundation, Inc. 3 * 4 * Portions Copyright (C) 1998-2005 Derek Price, Ximbiot <http://ximbiot.com>, 5 * and others. 6 * 7 * Portions Copyright (C) 1992, Brian Berliner and Jeff Polk 8 * Portions Copyright (C) 1989-1992, Brian Berliner 9 * 10 * You may distribute under the terms of the GNU General Public License as 11 * specified in the README file that comes with the CVS source distribution. 12 * 13 * Difference 14 * 15 * Run diff against versions in the repository. Options that are specified are 16 * passed on directly to "rcsdiff". 17 * 18 * Without any file arguments, runs diff against all the currently modified 19 * files. 20 */ 21 22 #include "cvs.h" 23 24 enum diff_file 25 { 26 DIFF_ERROR, 27 DIFF_ADDED, 28 DIFF_REMOVED, 29 DIFF_DIFFERENT, 30 DIFF_SAME 31 }; 32 33 static Dtype diff_dirproc (void *callerdat, const char *dir, 34 const char *pos_repos, const char *update_dir, 35 List *entries); 36 static int diff_filesdoneproc (void *callerdat, int err, 37 const char *repos, const char *update_dir, 38 List *entries); 39 static int diff_dirleaveproc (void *callerdat, const char *dir, 40 int err, const char *update_dir, 41 List *entries); 42 static enum diff_file diff_file_nodiff (struct file_info *finfo, Vers_TS *vers, 43 enum diff_file, char **rev1_cache ); 44 static int diff_fileproc (void *callerdat, struct file_info *finfo); 45 static void diff_mark_errors (int err); 46 47 48 /* Global variables. Would be cleaner if we just put this stuff in a 49 struct like log.c does. */ 50 51 /* Command line tags, from -r option. Points into argv. */ 52 static char *diff_rev1, *diff_rev2; 53 /* Command line dates, from -D option. Malloc'd. */ 54 static char *diff_date1, *diff_date2; 55 static char *use_rev1, *use_rev2; 56 static int have_rev1_label, have_rev2_label; 57 58 /* Revision of the user file, if it is unchanged from something in the 59 repository and we want to use that fact. */ 60 static char *user_file_rev; 61 62 static char *options; 63 static char **diff_argv; 64 static int diff_argc; 65 static size_t diff_arg_allocated; 66 static int diff_errors; 67 static int empty_files; 68 69 static const char *const diff_usage[] = 70 { 71 "Usage: %s %s [-lR] [-k kopt] [format_options]\n", 72 " [[-r rev1 | -D date1] [-r rev2 | -D date2]] [files...] \n", 73 "\t-l\tLocal directory only, not recursive\n", 74 "\t-R\tProcess directories recursively.\n", 75 "\t-k kopt\tSpecify keyword expansion mode.\n", 76 "\t-D d1\tDiff revision for date against working file.\n", 77 "\t-D d2\tDiff rev1/date1 against date2.\n", 78 "\t-r rev1\tDiff revision for rev1 against working file.\n", 79 "\t-r rev2\tDiff rev1/date1 against rev2.\n", 80 "\nformat_options:\n", 81 " -i --ignore-case Consider upper- and lower-case to be the same.\n", 82 " -w --ignore-all-space Ignore all white space.\n", 83 " -b --ignore-space-change Ignore changes in the amount of white space.\n", 84 " -B --ignore-blank-lines Ignore changes whose lines are all blank.\n", 85 " -I RE --ignore-matching-lines=RE Ignore changes whose lines all match RE.\n", 86 " --binary Read and write data in binary mode.\n", 87 " -a --text Treat all files as text.\n\n", 88 " -c -C NUM --context[=NUM] Output NUM (default 2) lines of copied context.\n", 89 " -u -U NUM --unified[=NUM] Output NUM (default 2) lines of unified context.\n", 90 " -NUM Use NUM context lines.\n", 91 " -L LABEL --label LABEL Use LABEL instead of file name.\n", 92 " -p --show-c-function Show which C function each change is in.\n", 93 " -F RE --show-function-line=RE Show the most recent line matching RE.\n", 94 " --brief Output only whether files differ.\n", 95 " -e --ed Output an ed script.\n", 96 " -f --forward-ed Output something like an ed script in forward order.\n", 97 " -n --rcs Output an RCS format diff.\n", 98 " -y --side-by-side Output in two columns.\n", 99 " -W NUM --width=NUM Output at most NUM (default 130) characters per line.\n", 100 " --left-column Output only the left column of common lines.\n", 101 " --suppress-common-lines Do not output common lines.\n", 102 " --ifdef=NAME Output merged file to show `#ifdef NAME' diffs.\n", 103 " --GTYPE-group-format=GFMT Similar, but format GTYPE input groups with GFMT.\n", 104 " --line-format=LFMT Similar, but format all input lines with LFMT.\n", 105 " --LTYPE-line-format=LFMT Similar, but format LTYPE input lines with LFMT.\n", 106 " LTYPE is `old', `new', or `unchanged'. GTYPE is LTYPE or `changed'.\n", 107 " GFMT may contain:\n", 108 " %%< lines from FILE1\n", 109 " %%> lines from FILE2\n", 110 " %%= lines common to FILE1 and FILE2\n", 111 " %%[-][WIDTH][.[PREC]]{doxX}LETTER printf-style spec for LETTER\n", 112 " LETTERs are as follows for new group, lower case for old group:\n", 113 " F first line number\n", 114 " L last line number\n", 115 " N number of lines = L-F+1\n", 116 " E F-1\n", 117 " M L+1\n", 118 " LFMT may contain:\n", 119 " %%L contents of line\n", 120 " %%l contents of line, excluding any trailing newline\n", 121 " %%[-][WIDTH][.[PREC]]{doxX}n printf-style spec for input line number\n", 122 " Either GFMT or LFMT may contain:\n", 123 " %%%% %%\n", 124 " %%c'C' the single character C\n", 125 " %%c'\\OOO' the character with octal code OOO\n\n", 126 " -t --expand-tabs Expand tabs to spaces in output.\n", 127 " -T --initial-tab Make tabs line up by prepending a tab.\n\n", 128 " -N --new-file Treat absent files as empty.\n", 129 " -s --report-identical-files Report when two files are the same.\n", 130 " --horizon-lines=NUM Keep NUM lines of the common prefix and suffix.\n", 131 " -d --minimal Try hard to find a smaller set of changes.\n", 132 " -H --speed-large-files Assume large files and many scattered small changes.\n", 133 "\n(Specify the --help global option for a list of other help options)\n", 134 NULL 135 }; 136 137 /* I copied this array directly out of diff.c in diffutils 2.7, after 138 removing the following entries, none of which seem relevant to use 139 with CVS: 140 --help 141 --version (-v) 142 --recursive (-r) 143 --unidirectional-new-file (-P) 144 --starting-file (-S) 145 --exclude (-x) 146 --exclude-from (-X) 147 --sdiff-merge-assist 148 --paginate (-l) (doesn't work with library callbacks) 149 150 I changed the options which take optional arguments (--context and 151 --unified) to return a number rather than a letter, so that the 152 optional argument could be handled more easily. I changed the 153 --brief and --ifdef options to return numbers, since -q and -D mean 154 something else to cvs diff. 155 156 The numbers 129- that appear in the fourth element of some entries 157 tell the big switch in `diff' how to process those options. -- Ian 158 159 The following options, which diff lists as "An alias, no longer 160 recommended" have been removed: --file-label --entire-new-file 161 --ascii --print. */ 162 163 static struct option const longopts[] = 164 { 165 {"ignore-blank-lines", 0, 0, 'B'}, 166 {"context", 2, 0, 143}, 167 {"ifdef", 1, 0, 131}, 168 {"show-function-line", 1, 0, 'F'}, 169 {"speed-large-files", 0, 0, 'H'}, 170 {"ignore-matching-lines", 1, 0, 'I'}, 171 {"label", 1, 0, 'L'}, 172 {"new-file", 0, 0, 'N'}, 173 {"initial-tab", 0, 0, 'T'}, 174 {"width", 1, 0, 'W'}, 175 {"text", 0, 0, 'a'}, 176 {"ignore-space-change", 0, 0, 'b'}, 177 {"minimal", 0, 0, 'd'}, 178 {"ed", 0, 0, 'e'}, 179 {"forward-ed", 0, 0, 'f'}, 180 {"ignore-case", 0, 0, 'i'}, 181 {"rcs", 0, 0, 'n'}, 182 {"show-c-function", 0, 0, 'p'}, 183 184 /* This is a potentially very useful option, except the output is so 185 silly. It would be much better for it to look like "cvs rdiff -s" 186 which displays all the same info, minus quite a few lines of 187 extraneous garbage. */ 188 {"brief", 0, 0, 145}, 189 190 {"report-identical-files", 0, 0, 's'}, 191 {"expand-tabs", 0, 0, 't'}, 192 {"ignore-all-space", 0, 0, 'w'}, 193 {"side-by-side", 0, 0, 'y'}, 194 {"unified", 2, 0, 146}, 195 {"left-column", 0, 0, 129}, 196 {"suppress-common-lines", 0, 0, 130}, 197 {"old-line-format", 1, 0, 132}, 198 {"new-line-format", 1, 0, 133}, 199 {"unchanged-line-format", 1, 0, 134}, 200 {"line-format", 1, 0, 135}, 201 {"old-group-format", 1, 0, 136}, 202 {"new-group-format", 1, 0, 137}, 203 {"unchanged-group-format", 1, 0, 138}, 204 {"changed-group-format", 1, 0, 139}, 205 {"horizon-lines", 1, 0, 140}, 206 {"binary", 0, 0, 142}, 207 {0, 0, 0, 0} 208 }; 209 210 211 212 /* Add one of OPT or LONGOPT, and ARGUMENT, when present, to global DIFF_ARGV. 213 * 214 * INPUTS 215 * opt A character option representation. 216 * longopt A long option name. 217 * argument Optional option argument. 218 * 219 * GLOBALS 220 * diff_argc The number of arguments in DIFF_ARGV. 221 * diff_argv Array of argument strings. 222 * diff_arg_allocated Allocated length of DIFF_ARGV. 223 * 224 * NOTES 225 * Behavior when both OPT & LONGOPT are provided is undefined. 226 * 227 * RETURNS 228 * Nothing. 229 */ 230 static void 231 add_diff_args (char opt, const char *longopt, const char *argument) 232 { 233 char *tmp; 234 235 /* Add opt or longopt to diff_arv. */ 236 assert (opt || (longopt && *longopt)); 237 assert (!(opt && (longopt && *longopt))); 238 if (opt) tmp = Xasprintf ("-%c", opt); 239 else tmp = Xasprintf ("--%s", longopt); 240 run_add_arg_p (&diff_argc, &diff_arg_allocated, &diff_argv, tmp); 241 free (tmp); 242 243 /* When present, add ARGUMENT to DIFF_ARGV. */ 244 if (argument) 245 run_add_arg_p (&diff_argc, &diff_arg_allocated, &diff_argv, argument); 246 } 247 248 249 250 /* CVS 1.9 and similar versions seemed to have pretty weird handling 251 of -y and -T. In the cases where it called rcsdiff, 252 they would have the meanings mentioned below. In the cases where it 253 called diff, they would have the meanings mentioned in "longopts". 254 Noone seems to have missed them, so I think the right thing to do is 255 just to remove the options altogether (which I have done). 256 257 In the case of -z and -q, "cvs diff" did not accept them even back 258 when we called rcsdiff (at least, it hasn't accepted them 259 recently). 260 261 In comparing rcsdiff to the new CVS implementation, I noticed that 262 the following rcsdiff flags are not handled by CVS diff: 263 264 -y: perform diff even when the requested revisions are the 265 same revision number 266 -q: run quietly 267 -T: preserve modification time on the RCS file 268 -z: specify timezone for use in file labels 269 270 I think these are not really relevant. -y is undocumented even in 271 RCS 5.7, and seems like a minor change at best. According to RCS 272 documentation, -T only applies when a RCS file has been modified 273 because of lock changes; doesn't CVS sidestep RCS's entire lock 274 structure? -z seems to be unsupported by CVS diff, and has a 275 different meaning as a global option anyway. (Adding it could be 276 a feature, but if it is left out for now, it should not break 277 anything.) For the purposes of producing output, CVS diff appears 278 mostly to ignore -q. Maybe this should be fixed, but I think it's 279 a larger issue than the changes included here. */ 280 281 int 282 diff (int argc, char **argv) 283 { 284 int c, err = 0; 285 int local = 0; 286 int which; 287 int option_index; 288 char *diff_orig1, *diff_orig2; 289 290 if (argc == -1) 291 usage (diff_usage); 292 293 have_rev1_label = have_rev2_label = 0; 294 295 /* 296 * Note that we catch all the valid arguments here, so that we can 297 * intercept the -r arguments for doing revision diffs; and -l/-R for a 298 * non-recursive/recursive diff. 299 */ 300 301 /* Clean out our global variables (multiroot can call us multiple 302 times and the server can too, if the client sends several 303 diff commands). */ 304 run_arg_free_p (diff_argc, diff_argv); 305 diff_argc = 0; 306 307 diff_orig1 = NULL; 308 diff_orig2 = NULL; 309 diff_rev1 = NULL; 310 diff_rev2 = NULL; 311 diff_date1 = NULL; 312 diff_date2 = NULL; 313 314 getoptreset (); 315 /* FIXME: This should really be allocating an argv to be passed to diff 316 * later rather than strcatting onto the opts variable. We have some 317 * handling routines that can already handle most of the argc/argv 318 * maintenance for us and currently, if anyone were to attempt to pass a 319 * quoted string in here, it would be split on spaces and tabs on its way 320 * to diff. 321 */ 322 while ((c = getopt_long (argc, argv, 323 "+abcdefhilnpstuwy0123456789BHNRTC:D:F:I:L:U:W:k:r:", 324 longopts, &option_index)) != -1) 325 { 326 switch (c) 327 { 328 case 'y': 329 add_diff_args (0, "side-by-side", NULL); 330 break; 331 case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': 332 case 'h': case 'i': case 'n': case 'p': case 's': case 't': 333 case 'u': case 'w': 334 case '0': case '1': case '2': case '3': case '4': case '5': 335 case '6': case '7': case '8': case '9': 336 case 'B': case 'H': case 'T': 337 add_diff_args (c, NULL, NULL); 338 break; 339 case 'L': 340 if (have_rev1_label++) 341 if (have_rev2_label++) 342 { 343 error (0, 0, "extra -L arguments ignored"); 344 break; 345 } 346 /* Fall through. */ 347 case 'C': case 'F': case 'I': case 'U': case 'W': 348 add_diff_args (c, NULL, optarg); 349 break; 350 case 129: case 130: case 131: case 132: case 133: case 134: 351 case 135: case 136: case 137: case 138: case 139: case 140: 352 case 141: case 142: case 143: case 145: case 146: 353 add_diff_args (0, longopts[option_index].name, 354 longopts[option_index].has_arg ? optarg : NULL); 355 break; 356 case 'R': 357 local = 0; 358 break; 359 case 'l': 360 local = 1; 361 break; 362 case 'k': 363 if (options) 364 free (options); 365 options = RCS_check_kflag (optarg); 366 break; 367 case 'r': 368 if (diff_rev2 || diff_date2) 369 error (1, 0, 370 "no more than two revisions/dates can be specified"); 371 if (diff_rev1 || diff_date1) 372 { 373 diff_orig2 = xstrdup (optarg); 374 parse_tagdate (&diff_rev2, &diff_date2, optarg); 375 } 376 else 377 { 378 diff_orig1 = xstrdup (optarg); 379 parse_tagdate (&diff_rev1, &diff_date1, optarg); 380 } 381 break; 382 case 'D': 383 if (diff_rev2 || diff_date2) 384 error (1, 0, 385 "no more than two revisions/dates can be specified"); 386 if (diff_rev1 || diff_date1) 387 diff_date2 = Make_Date (optarg); 388 else 389 diff_date1 = Make_Date (optarg); 390 break; 391 case 'N': 392 empty_files = 1; 393 break; 394 case '?': 395 default: 396 usage (diff_usage); 397 break; 398 } 399 } 400 argc -= optind; 401 argv += optind; 402 403 /* make sure options is non-null */ 404 if (!options) 405 options = xstrdup (""); 406 407 #ifdef CLIENT_SUPPORT 408 if (current_parsed_root->isremote) { 409 /* We're the client side. Fire up the remote server. */ 410 start_server (); 411 412 ign_setup (); 413 414 if (local) 415 send_arg("-l"); 416 if (empty_files) 417 send_arg("-N"); 418 send_options (diff_argc, diff_argv); 419 if (options[0] != '\0') 420 send_arg (options); 421 if (diff_orig1) 422 option_with_arg ("-r", diff_orig1); 423 else if (diff_date1) 424 client_senddate (diff_date1); 425 if (diff_orig2) 426 option_with_arg ("-r", diff_orig2); 427 else if (diff_date2) 428 client_senddate (diff_date2); 429 send_arg ("--"); 430 431 /* Send the current files unless diffing two revs from the archive */ 432 if (!diff_rev2 && !diff_date2) 433 send_files (argc, argv, local, 0, 0); 434 else 435 send_files (argc, argv, local, 0, SEND_NO_CONTENTS); 436 437 send_file_names (argc, argv, SEND_EXPAND_WILD); 438 439 send_to_server ("diff\012", 0); 440 err = get_responses_and_close (); 441 free (options); 442 options = NULL; 443 return err; 444 } 445 #endif 446 447 if (diff_rev1 != NULL) 448 tag_check_valid (diff_rev1, argc, argv, local, 0, "", false); 449 if (diff_rev2 != NULL) 450 tag_check_valid (diff_rev2, argc, argv, local, 0, "", false); 451 452 which = W_LOCAL; 453 if (diff_rev1 || diff_date1) 454 which |= W_REPOS | W_ATTIC; 455 456 wrap_setup (); 457 458 /* start the recursion processor */ 459 err = start_recursion (diff_fileproc, diff_filesdoneproc, diff_dirproc, 460 diff_dirleaveproc, NULL, argc, argv, local, 461 which, 0, CVS_LOCK_READ, NULL, 1, NULL); 462 463 /* clean up */ 464 free (options); 465 options = NULL; 466 467 if (diff_date1 != NULL) 468 free (diff_date1); 469 if (diff_date2 != NULL) 470 free (diff_date2); 471 472 return err; 473 } 474 475 476 477 /* 478 * Do a file diff 479 */ 480 /* ARGSUSED */ 481 static int 482 diff_fileproc (void *callerdat, struct file_info *finfo) 483 { 484 int status, err = 2; /* 2 == trouble, like rcsdiff */ 485 Vers_TS *vers; 486 enum diff_file empty_file = DIFF_DIFFERENT; 487 char *tmp = NULL; 488 char *tocvsPath = NULL; 489 char *fname = NULL; 490 char *label1; 491 char *label2; 492 char *rev1_cache = NULL; 493 494 user_file_rev = 0; 495 vers = Version_TS (finfo, NULL, NULL, NULL, 1, 0); 496 497 if (diff_rev2 || diff_date2) 498 { 499 /* Skip all the following checks regarding the user file; we're 500 not using it. */ 501 502 /* cvsacl patch */ 503 #ifdef SERVER_SUPPORT 504 if (use_cvs_acl /* && server_active */) 505 { 506 if (diff_rev1) 507 { 508 if (!access_allowed (NULL, finfo->repository, diff_rev1, 5, 509 NULL, NULL, 1)) 510 { 511 if (stop_at_first_permission_denied) 512 error (1, 0, "permission denied for %s", 513 Short_Repository (finfo->repository)); 514 else 515 error (0, 0, "permission denied for %s/%s", 516 Short_Repository (finfo->repository), 517 finfo->file); 518 519 return (0); 520 } 521 } 522 if (diff_rev2) 523 { 524 if (!access_allowed (NULL, finfo->repository, diff_rev2, 5, 525 NULL, NULL, 1)) 526 { 527 if (stop_at_first_permission_denied) 528 error (1, 0, "permission denied for %s", 529 Short_Repository (finfo->repository)); 530 else 531 error (0, 0, "permission denied for %s/%s", 532 Short_Repository (finfo->repository), 533 finfo->file); 534 535 return (0); 536 } 537 } 538 } 539 #endif 540 541 } 542 else if (vers->vn_user == NULL) 543 { 544 /* The file does not exist in the working directory. */ 545 if ((diff_rev1 || diff_date1) 546 && vers->srcfile != NULL) 547 { 548 /* The file does exist in the repository. */ 549 if (empty_files) 550 empty_file = DIFF_REMOVED; 551 else 552 { 553 int exists; 554 555 exists = 0; 556 /* special handling for TAG_HEAD */ 557 if (diff_rev1 && strcmp (diff_rev1, TAG_HEAD) == 0) 558 { 559 char *head = 560 (vers->vn_rcs == NULL 561 ? NULL 562 : RCS_branch_head (vers->srcfile, vers->vn_rcs)); 563 exists = head != NULL && !RCS_isdead (vers->srcfile, head); 564 if (head != NULL) 565 free (head); 566 } 567 else 568 { 569 Vers_TS *xvers; 570 571 xvers = Version_TS (finfo, NULL, diff_rev1, diff_date1, 572 1, 0); 573 exists = xvers->vn_rcs && !RCS_isdead (xvers->srcfile, 574 xvers->vn_rcs); 575 freevers_ts (&xvers); 576 } 577 if (exists) 578 error (0, 0, 579 "%s no longer exists, no comparison available", 580 finfo->fullname); 581 goto out; 582 } 583 } 584 else 585 { 586 error (0, 0, "I know nothing about %s", finfo->fullname); 587 goto out; 588 } 589 } 590 else if (vers->vn_user[0] == '0' && vers->vn_user[1] == '\0') 591 { 592 /* The file was added locally. */ 593 int exists = 0; 594 595 if (vers->srcfile != NULL) 596 { 597 /* The file does exist in the repository. */ 598 599 if (diff_rev1 || diff_date1) 600 { 601 /* special handling for TAG_HEAD */ 602 if (diff_rev1 && strcmp (diff_rev1, TAG_HEAD) == 0) 603 { 604 char *head = 605 (vers->vn_rcs == NULL 606 ? NULL 607 : RCS_branch_head (vers->srcfile, vers->vn_rcs)); 608 exists = head && !RCS_isdead (vers->srcfile, head); 609 if (head != NULL) 610 free (head); 611 } 612 else 613 { 614 Vers_TS *xvers; 615 616 xvers = Version_TS (finfo, NULL, diff_rev1, diff_date1, 617 1, 0); 618 exists = xvers->vn_rcs 619 && !RCS_isdead (xvers->srcfile, xvers->vn_rcs); 620 freevers_ts (&xvers); 621 } 622 } 623 else 624 { 625 /* The file was added locally, but an RCS archive exists. Our 626 * base revision must be dead. 627 */ 628 /* No need to set, exists = 0, here. That's the default. */ 629 } 630 } 631 if (!exists) 632 { 633 /* If we got here, then either the RCS archive does not exist or 634 * the relevant revision is dead. 635 */ 636 if (empty_files) 637 empty_file = DIFF_ADDED; 638 else 639 { 640 error (0, 0, "%s is a new entry, no comparison available", 641 finfo->fullname); 642 goto out; 643 } 644 } 645 } 646 else if (vers->vn_user[0] == '-') 647 { 648 if (empty_files) 649 empty_file = DIFF_REMOVED; 650 else 651 { 652 error (0, 0, "%s was removed, no comparison available", 653 finfo->fullname); 654 goto out; 655 } 656 } 657 else 658 { 659 if (!vers->vn_rcs && !vers->srcfile) 660 { 661 error (0, 0, "cannot find revision control file for %s", 662 finfo->fullname); 663 goto out; 664 } 665 else 666 { 667 if (vers->ts_user == NULL) 668 { 669 error (0, 0, "cannot find %s", finfo->fullname); 670 goto out; 671 } 672 else if (!strcmp (vers->ts_user, vers->ts_rcs)) 673 { 674 /* The user file matches some revision in the repository 675 Diff against the repository (for remote CVS, we might not 676 have a copy of the user file around). */ 677 user_file_rev = vers->vn_user; 678 } 679 } 680 } 681 682 empty_file = diff_file_nodiff (finfo, vers, empty_file, &rev1_cache); 683 if (empty_file == DIFF_SAME) 684 { 685 /* In the server case, would be nice to send a "Checked-in" 686 response, so that the client can rewrite its timestamp. 687 server_checked_in by itself isn't the right thing (it 688 needs a server_register), but I'm not sure what is. 689 It isn't clear to me how "cvs status" handles this (that 690 is, for a client which sends Modified not Is-modified to 691 "cvs status"), but it does. */ 692 err = 0; 693 goto out; 694 } 695 else if (empty_file == DIFF_ERROR) 696 goto out; 697 698 /* Output an "Index:" line for patch to use */ 699 cvs_output ("Index: ", 0); 700 cvs_output (finfo->fullname, 0); 701 cvs_output ("\n", 1); 702 703 tocvsPath = wrap_tocvs_process_file (finfo->file); 704 if (tocvsPath) 705 { 706 /* Backup the current version of the file to CVS/,,filename */ 707 fname = Xasprintf (fname, "%s/%s%s", CVSADM, CVSPREFIX, finfo->file); 708 if (unlink_file_dir (fname) < 0) 709 if (!existence_error (errno)) 710 error (1, errno, "cannot remove %s", fname); 711 rename_file (finfo->file, fname); 712 /* Copy the wrapped file to the current directory then go to work */ 713 copy_file (tocvsPath, finfo->file); 714 } 715 716 /* Set up file labels appropriate for compatibility with the Larry Wall 717 * implementation of patch if the user didn't specify. This is irrelevant 718 * according to the POSIX.2 specification. 719 */ 720 label1 = NULL; 721 label2 = NULL; 722 /* The user cannot set the rev2 label without first setting the rev1 723 * label. 724 */ 725 if (!have_rev2_label) 726 { 727 if (empty_file == DIFF_REMOVED) 728 label2 = make_file_label (DEVNULL, NULL, NULL); 729 else 730 label2 = make_file_label (finfo->fullname, use_rev2, 731 vers->srcfile); 732 if (!have_rev1_label) 733 { 734 if (empty_file == DIFF_ADDED) 735 label1 = make_file_label (DEVNULL, NULL, NULL); 736 else 737 label1 = make_file_label (finfo->fullname, use_rev1, 738 vers->srcfile); 739 } 740 } 741 742 if (empty_file == DIFF_ADDED || empty_file == DIFF_REMOVED) 743 { 744 /* This is fullname, not file, possibly despite the POSIX.2 745 * specification, because that's the way all the Larry Wall 746 * implementations of patch (are there other implementations?) want 747 * things and the POSIX.2 spec appears to leave room for this. 748 */ 749 cvs_output ("\ 750 ===================================================================\n\ 751 RCS file: ", 0); 752 cvs_output (finfo->fullname, 0); 753 cvs_output ("\n", 1); 754 755 cvs_output ("diff -N ", 0); 756 cvs_output (finfo->fullname, 0); 757 cvs_output ("\n", 1); 758 759 if (empty_file == DIFF_ADDED) 760 { 761 if (use_rev2 == NULL) 762 status = diff_exec (DEVNULL, finfo->file, label1, label2, 763 diff_argc, diff_argv, RUN_TTY); 764 else 765 { 766 int retcode; 767 768 tmp = cvs_temp_name (); 769 retcode = RCS_checkout (vers->srcfile, NULL, use_rev2, NULL, 770 *options ? options : vers->options, 771 tmp, NULL, NULL); 772 if (retcode != 0) 773 goto out; 774 775 status = diff_exec (DEVNULL, tmp, label1, label2, 776 diff_argc, diff_argv, RUN_TTY); 777 } 778 } 779 else 780 { 781 int retcode; 782 783 tmp = cvs_temp_name (); 784 retcode = RCS_checkout (vers->srcfile, NULL, use_rev1, NULL, 785 *options ? options : vers->options, 786 tmp, NULL, NULL); 787 if (retcode != 0) 788 goto out; 789 790 status = diff_exec (tmp, DEVNULL, label1, label2, 791 diff_argc, diff_argv, RUN_TTY); 792 } 793 } 794 else 795 { 796 status = RCS_exec_rcsdiff (vers->srcfile, diff_argc, diff_argv, 797 *options ? options : vers->options, 798 use_rev1, rev1_cache, use_rev2, 799 label1, label2, finfo->file); 800 801 } 802 803 if (label1) free (label1); 804 if (label2) free (label2); 805 806 switch (status) 807 { 808 case -1: /* fork failed */ 809 error (1, errno, "fork failed while diffing %s", 810 vers->srcfile->path); 811 case 0: /* everything ok */ 812 err = 0; 813 break; 814 default: /* other error */ 815 err = status; 816 break; 817 } 818 819 out: 820 if( tocvsPath != NULL ) 821 { 822 if (unlink_file_dir (finfo->file) < 0) 823 if (! existence_error (errno)) 824 error (1, errno, "cannot remove %s", finfo->file); 825 826 rename_file (fname, finfo->file); 827 if (unlink_file (tocvsPath) < 0) 828 error (1, errno, "cannot remove %s", tocvsPath); 829 free (fname); 830 } 831 832 /* Call CVS_UNLINK() rather than unlink_file() below to avoid the check 833 * for noexec. 834 */ 835 if (tmp != NULL) 836 { 837 if (CVS_UNLINK (tmp) < 0) 838 error (0, errno, "cannot remove %s", tmp); 839 free (tmp); 840 } 841 if (rev1_cache != NULL) 842 { 843 if (CVS_UNLINK (rev1_cache) < 0) 844 error (0, errno, "cannot remove %s", rev1_cache); 845 free (rev1_cache); 846 } 847 848 freevers_ts (&vers); 849 diff_mark_errors (err); 850 return err; 851 } 852 853 854 855 /* 856 * Remember the exit status for each file. 857 */ 858 static void 859 diff_mark_errors (int err) 860 { 861 if (err > diff_errors) 862 diff_errors = err; 863 } 864 865 866 867 /* 868 * Print a warm fuzzy message when we enter a dir 869 * 870 * Don't try to diff directories that don't exist! -- DW 871 */ 872 /* ARGSUSED */ 873 static Dtype 874 diff_dirproc (void *callerdat, const char *dir, const char *pos_repos, 875 const char *update_dir, List *entries) 876 { 877 /* XXX - check for dirs we don't want to process??? */ 878 879 /* YES ... for instance dirs that don't exist!!! -- DW */ 880 if (!isdir (dir)) 881 return R_SKIP_ALL; 882 883 /* cvsacl patch */ 884 #ifdef SERVER_SUPPORT 885 if (use_cvs_acl /* && server_active */) 886 { 887 if (diff_rev1) 888 { 889 if (!access_allowed (NULL, update_dir, diff_rev1, 5, NULL, NULL, 1)) 890 { 891 if (stop_at_first_permission_denied) 892 error (1, 0, "permission denied for %s", 893 Short_Repository (update_dir)); 894 else 895 error (0, 0, "permission denied for %s/%s", 896 Short_Repository (update_dir), update_dir); 897 898 return (0); 899 } 900 } 901 if (diff_rev2) 902 { 903 if (!access_allowed (NULL, update_dir, diff_rev2, 5, NULL, NULL, 1)) 904 { 905 if (stop_at_first_permission_denied) 906 error (1, 0, "permission denied for %s", 907 Short_Repository (update_dir)); 908 else 909 error (0, 0, "permission denied for %s/%s", 910 Short_Repository (update_dir), update_dir); 911 912 return (0); 913 } 914 } 915 } 916 #endif 917 if (!quiet) 918 error (0, 0, "Diffing %s", update_dir); 919 return R_PROCESS; 920 } 921 922 923 924 /* 925 * Concoct the proper exit status - done with files 926 */ 927 /* ARGSUSED */ 928 static int 929 diff_filesdoneproc (void *callerdat, int err, const char *repos, 930 const char *update_dir, List *entries) 931 { 932 return diff_errors; 933 } 934 935 936 937 /* 938 * Concoct the proper exit status - leaving directories 939 */ 940 /* ARGSUSED */ 941 static int 942 diff_dirleaveproc (void *callerdat, const char *dir, int err, 943 const char *update_dir, List *entries) 944 { 945 return diff_errors; 946 } 947 948 949 950 /* 951 * verify that a file is different 952 * 953 * INPUTS 954 * finfo 955 * vers 956 * empty_file 957 * 958 * OUTPUTS 959 * rev1_cache Cache the contents of rev1 if we look it up. 960 */ 961 static enum diff_file 962 diff_file_nodiff (struct file_info *finfo, Vers_TS *vers, 963 enum diff_file empty_file, char **rev1_cache) 964 { 965 Vers_TS *xvers; 966 int retcode; 967 968 TRACE (TRACE_FUNCTION, "diff_file_nodiff (%s, %d)", 969 finfo->fullname ? finfo->fullname : "(null)", empty_file); 970 971 /* free up any old use_rev* variables and reset 'em */ 972 if (use_rev1) 973 free (use_rev1); 974 if (use_rev2) 975 free (use_rev2); 976 use_rev1 = use_rev2 = NULL; 977 978 if (diff_rev1 || diff_date1) 979 { 980 /* special handling for TAG_HEAD */ 981 if (diff_rev1 && strcmp (diff_rev1, TAG_HEAD) == 0) 982 { 983 if (vers->vn_rcs != NULL && vers->srcfile != NULL) 984 use_rev1 = RCS_branch_head (vers->srcfile, vers->vn_rcs); 985 } 986 else 987 { 988 xvers = Version_TS (finfo, NULL, diff_rev1, diff_date1, 1, 0); 989 if (xvers->vn_rcs != NULL) 990 use_rev1 = xstrdup (xvers->vn_rcs); 991 freevers_ts (&xvers); 992 } 993 } 994 if (diff_rev2 || diff_date2) 995 { 996 /* special handling for TAG_HEAD */ 997 if (diff_rev2 && strcmp (diff_rev2, TAG_HEAD) == 0) 998 { 999 if (vers->vn_rcs && vers->srcfile) 1000 use_rev2 = RCS_branch_head (vers->srcfile, vers->vn_rcs); 1001 } 1002 else 1003 { 1004 xvers = Version_TS (finfo, NULL, diff_rev2, diff_date2, 1, 0); 1005 if (xvers->vn_rcs != NULL) 1006 use_rev2 = xstrdup (xvers->vn_rcs); 1007 freevers_ts (&xvers); 1008 } 1009 1010 if (use_rev1 == NULL || RCS_isdead (vers->srcfile, use_rev1)) 1011 { 1012 /* The first revision does not exist. If EMPTY_FILES is 1013 true, treat this as an added file. Otherwise, warn 1014 about the missing tag. */ 1015 if (use_rev2 == NULL || RCS_isdead (vers->srcfile, use_rev2)) 1016 /* At least in the case where DIFF_REV1 and DIFF_REV2 1017 * are both numeric (and non-existant (NULL), as opposed to 1018 * dead?), we should be returning some kind of error (see 1019 * basicb-8a0 in testsuite). The symbolic case may be more 1020 * complicated. 1021 */ 1022 return DIFF_SAME; 1023 if (empty_files) 1024 return DIFF_ADDED; 1025 if (use_rev1 != NULL) 1026 { 1027 if (diff_rev1) 1028 { 1029 error (0, 0, 1030 "Tag %s refers to a dead (removed) revision in file `%s'.", 1031 diff_rev1, finfo->fullname); 1032 } 1033 else 1034 { 1035 error (0, 0, 1036 "Date %s refers to a dead (removed) revision in file `%s'.", 1037 diff_date1, finfo->fullname); 1038 } 1039 error (0, 0, 1040 "No comparison available. Pass `-N' to `%s diff'?", 1041 program_name); 1042 } 1043 else if (diff_rev1) 1044 error (0, 0, "tag %s is not in file %s", diff_rev1, 1045 finfo->fullname); 1046 else 1047 error (0, 0, "no revision for date %s in file %s", 1048 diff_date1, finfo->fullname); 1049 return DIFF_ERROR; 1050 } 1051 1052 assert( use_rev1 != NULL ); 1053 if( use_rev2 == NULL || RCS_isdead( vers->srcfile, use_rev2 ) ) 1054 { 1055 /* The second revision does not exist. If EMPTY_FILES is 1056 true, treat this as a removed file. Otherwise warn 1057 about the missing tag. */ 1058 if (empty_files) 1059 return DIFF_REMOVED; 1060 if( use_rev2 != NULL ) 1061 { 1062 if (diff_rev2) 1063 { 1064 error( 0, 0, 1065 "Tag %s refers to a dead (removed) revision in file `%s'.", 1066 diff_rev2, finfo->fullname ); 1067 } 1068 else 1069 { 1070 error( 0, 0, 1071 "Date %s refers to a dead (removed) revision in file `%s'.", 1072 diff_date2, finfo->fullname ); 1073 } 1074 error( 0, 0, 1075 "No comparison available. Pass `-N' to `%s diff'?", 1076 program_name ); 1077 } 1078 else if (diff_rev2) 1079 error (0, 0, "tag %s is not in file %s", diff_rev2, 1080 finfo->fullname); 1081 else 1082 error (0, 0, "no revision for date %s in file %s", 1083 diff_date2, finfo->fullname); 1084 return DIFF_ERROR; 1085 } 1086 /* Now, see if we really need to do the diff. We can't assume that the 1087 * files are different when the revs are. 1088 */ 1089 assert( use_rev2 != NULL ); 1090 if( strcmp (use_rev1, use_rev2) == 0 ) 1091 return DIFF_SAME; 1092 /* else fall through and do the diff */ 1093 } 1094 1095 /* If we had a r1/d1 & r2/d2, then at this point we must have a C3P0... 1096 * err... ok, then both rev1 & rev2 must have resolved to an existing, 1097 * live version due to if statement we just closed. 1098 */ 1099 assert (!(diff_rev2 || diff_date2) || (use_rev1 && use_rev2)); 1100 1101 if ((diff_rev1 || diff_date1) && 1102 (use_rev1 == NULL || RCS_isdead (vers->srcfile, use_rev1))) 1103 { 1104 /* The first revision does not exist, and no second revision 1105 was given. */ 1106 if (empty_files) 1107 { 1108 if (empty_file == DIFF_REMOVED) 1109 return DIFF_SAME; 1110 if( user_file_rev && use_rev2 == NULL ) 1111 use_rev2 = xstrdup( user_file_rev ); 1112 return DIFF_ADDED; 1113 } 1114 if( use_rev1 != NULL ) 1115 { 1116 if (diff_rev1) 1117 { 1118 error( 0, 0, 1119 "Tag %s refers to a dead (removed) revision in file `%s'.", 1120 diff_rev1, finfo->fullname ); 1121 } 1122 else 1123 { 1124 error( 0, 0, 1125 "Date %s refers to a dead (removed) revision in file `%s'.", 1126 diff_date1, finfo->fullname ); 1127 } 1128 error( 0, 0, 1129 "No comparison available. Pass `-N' to `%s diff'?", 1130 program_name ); 1131 } 1132 else if ( diff_rev1 ) 1133 error( 0, 0, "tag %s is not in file %s", diff_rev1, 1134 finfo->fullname ); 1135 else 1136 error( 0, 0, "no revision for date %s in file %s", 1137 diff_date1, finfo->fullname ); 1138 return DIFF_ERROR; 1139 } 1140 1141 assert( !diff_rev1 || use_rev1 ); 1142 1143 if (user_file_rev) 1144 { 1145 /* drop user_file_rev into first unused use_rev */ 1146 if (!use_rev1) 1147 use_rev1 = xstrdup (user_file_rev); 1148 else if (!use_rev2) 1149 use_rev2 = xstrdup (user_file_rev); 1150 /* and if not, it wasn't needed anyhow */ 1151 user_file_rev = NULL; 1152 } 1153 1154 /* Now, see if we really need to do the diff. We can't assume that the 1155 * files are different when the revs are. 1156 */ 1157 if( use_rev1 && use_rev2) 1158 { 1159 if (strcmp (use_rev1, use_rev2) == 0) 1160 return DIFF_SAME; 1161 /* Fall through and do the diff. */ 1162 } 1163 /* Don't want to do the timestamp check with both use_rev1 & use_rev2 set. 1164 * The timestamp check is just for the default case of diffing the 1165 * workspace file against its base revision. 1166 */ 1167 else if( use_rev1 == NULL 1168 || ( vers->vn_user != NULL 1169 && strcmp( use_rev1, vers->vn_user ) == 0 ) ) 1170 { 1171 if (empty_file == DIFF_DIFFERENT 1172 && vers->ts_user != NULL 1173 && strcmp (vers->ts_rcs, vers->ts_user) == 0 1174 && (!(*options) || strcmp (options, vers->options) == 0)) 1175 { 1176 return DIFF_SAME; 1177 } 1178 if (use_rev1 == NULL 1179 && (vers->vn_user[0] != '0' || vers->vn_user[1] != '\0')) 1180 { 1181 if (vers->vn_user[0] == '-') 1182 use_rev1 = xstrdup (vers->vn_user + 1); 1183 else 1184 use_rev1 = xstrdup (vers->vn_user); 1185 } 1186 } 1187 1188 /* If we already know that the file is being added or removed, 1189 then we don't want to do an actual file comparison here. */ 1190 if (empty_file != DIFF_DIFFERENT) 1191 return empty_file; 1192 1193 /* 1194 * Run a quick cmp to see if we should bother with a full diff. 1195 */ 1196 1197 retcode = RCS_cmp_file( vers->srcfile, use_rev1, rev1_cache, 1198 use_rev2, *options ? options : vers->options, 1199 finfo->file ); 1200 1201 return retcode == 0 ? DIFF_SAME : DIFF_DIFFERENT; 1202 } 1203