1 /* 2 * Copyright (c) 1992, Brian Berliner and Jeff Polk 3 * Copyright (c) 1989-1992, Brian Berliner 4 * 5 * You may distribute under the terms of the GNU General Public License as 6 * specified in the README file that comes with the CVS source distribution. 7 * 8 * Difference 9 * 10 * Run diff against versions in the repository. Options that are specified are 11 * passed on directly to "rcsdiff". 12 * 13 * Without any file arguments, runs diff against all the currently modified 14 * files. 15 */ 16 17 #include "cvs.h" 18 19 enum diff_file 20 { 21 DIFF_ERROR, 22 DIFF_ADDED, 23 DIFF_REMOVED, 24 DIFF_DIFFERENT, 25 DIFF_SAME 26 }; 27 28 static Dtype diff_dirproc PROTO ((void *callerdat, char *dir, 29 char *pos_repos, char *update_dir, 30 List *entries)); 31 static int diff_filesdoneproc PROTO ((void *callerdat, int err, 32 char *repos, char *update_dir, 33 List *entries)); 34 static int diff_dirleaveproc PROTO ((void *callerdat, char *dir, 35 int err, char *update_dir, 36 List *entries)); 37 static enum diff_file diff_file_nodiff PROTO ((struct file_info *finfo, 38 Vers_TS *vers, 39 enum diff_file)); 40 static int diff_fileproc PROTO ((void *callerdat, struct file_info *finfo)); 41 static void diff_mark_errors PROTO((int err)); 42 43 44 /* Global variables. Would be cleaner if we just put this stuff in a 45 struct like log.c does. */ 46 47 /* Command line tags, from -r option. Points into argv. */ 48 static char *diff_rev1, *diff_rev2; 49 /* Command line dates, from -D option. Malloc'd. */ 50 static char *diff_date1, *diff_date2; 51 static char *use_rev1, *use_rev2; 52 static int have_rev1_label, have_rev2_label; 53 54 /* Revision of the user file, if it is unchanged from something in the 55 repository and we want to use that fact. */ 56 static char *user_file_rev; 57 58 static char *options; 59 static char *opts; 60 static size_t opts_allocated = 1; 61 static int diff_errors; 62 static int empty_files = 0; 63 64 /* FIXME: should be documenting all the options here. They don't 65 perfectly match rcsdiff options (for example, we always support 66 --ifdef and --context, but rcsdiff only does if diff does). */ 67 static const char *const diff_usage[] = 68 { 69 "Usage: %s %s [-lNR] [rcsdiff-options]\n", 70 " [[-r rev1 | -D date1] [-r rev2 | -D date2]] [files...] \n", 71 "\t-l\tLocal directory only, not recursive\n", 72 "\t-R\tProcess directories recursively.\n", 73 "\t-D d1\tDiff revision for date against working file.\n", 74 "\t-D d2\tDiff rev1/date1 against date2.\n", 75 "\t-N\tinclude diffs for added and removed files.\n", 76 "\t-r rev1\tDiff revision for rev1 against working file.\n", 77 "\t-r rev2\tDiff rev1/date1 against rev2.\n", 78 "\t--ifdef=arg\tOutput diffs in ifdef format.\n", 79 "(consult the documentation for your diff program for rcsdiff-options.\n", 80 "The most popular is -c for context diffs but there are many more).\n", 81 "(Specify the --help global option for a list of other help options)\n", 82 NULL 83 }; 84 85 /* I copied this array directly out of diff.c in diffutils 2.7, after 86 removing the following entries, none of which seem relevant to use 87 with CVS: 88 --help 89 --version 90 --recursive 91 --unidirectional-new-file 92 --starting-file 93 --exclude 94 --exclude-from 95 --sdiff-merge-assist 96 97 I changed the options which take optional arguments (--context and 98 --unified) to return a number rather than a letter, so that the 99 optional argument could be handled more easily. I changed the 100 --paginate and --brief options to return a number, since -l and -q 101 mean something else to cvs diff. 102 103 The numbers 129- that appear in the fourth element of some entries 104 tell the big switch in `diff' how to process those options. -- Ian 105 106 The following options, which diff lists as "An alias, no longer 107 recommended" have been removed: --file-label --entire-new-file 108 --ascii --print. */ 109 110 static struct option const longopts[] = 111 { 112 {"ignore-blank-lines", 0, 0, 'B'}, 113 {"context", 2, 0, 143}, 114 {"ifdef", 1, 0, 131}, 115 {"show-function-line", 1, 0, 'F'}, 116 {"speed-large-files", 0, 0, 'H'}, 117 {"ignore-matching-lines", 1, 0, 'I'}, 118 {"label", 1, 0, 'L'}, 119 {"new-file", 0, 0, 'N'}, 120 {"initial-tab", 0, 0, 'T'}, 121 {"width", 1, 0, 'W'}, 122 {"text", 0, 0, 'a'}, 123 {"ignore-space-change", 0, 0, 'b'}, 124 {"minimal", 0, 0, 'd'}, 125 {"ed", 0, 0, 'e'}, 126 {"forward-ed", 0, 0, 'f'}, 127 {"ignore-case", 0, 0, 'i'}, 128 {"paginate", 0, 0, 144}, 129 {"rcs", 0, 0, 'n'}, 130 {"show-c-function", 0, 0, 'p'}, 131 132 /* This is a potentially very useful option, except the output is so 133 silly. It would be much better for it to look like "cvs rdiff -s" 134 which displays all the same info, minus quite a few lines of 135 extraneous garbage. */ 136 {"brief", 0, 0, 145}, 137 138 {"report-identical-files", 0, 0, 's'}, 139 {"expand-tabs", 0, 0, 't'}, 140 {"ignore-all-space", 0, 0, 'w'}, 141 {"side-by-side", 0, 0, 'y'}, 142 {"unified", 2, 0, 146}, 143 {"left-column", 0, 0, 129}, 144 {"suppress-common-lines", 0, 0, 130}, 145 {"old-line-format", 1, 0, 132}, 146 {"new-line-format", 1, 0, 133}, 147 {"unchanged-line-format", 1, 0, 134}, 148 {"line-format", 1, 0, 135}, 149 {"old-group-format", 1, 0, 136}, 150 {"new-group-format", 1, 0, 137}, 151 {"unchanged-group-format", 1, 0, 138}, 152 {"changed-group-format", 1, 0, 139}, 153 {"horizon-lines", 1, 0, 140}, 154 {"binary", 0, 0, 142}, 155 {0, 0, 0, 0} 156 }; 157 158 /* CVS 1.9 and similar versions seemed to have pretty weird handling 159 of -y and -T. In the cases where it called rcsdiff, 160 they would have the meanings mentioned below. In the cases where it 161 called diff, they would have the meanings mentioned in "longopts". 162 Noone seems to have missed them, so I think the right thing to do is 163 just to remove the options altogether (which I have done). 164 165 In the case of -z and -q, "cvs diff" did not accept them even back 166 when we called rcsdiff (at least, it hasn't accepted them 167 recently). 168 169 In comparing rcsdiff to the new CVS implementation, I noticed that 170 the following rcsdiff flags are not handled by CVS diff: 171 172 -y: perform diff even when the requested revisions are the 173 same revision number 174 -q: run quietly 175 -T: preserve modification time on the RCS file 176 -z: specify timezone for use in file labels 177 178 I think these are not really relevant. -y is undocumented even in 179 RCS 5.7, and seems like a minor change at best. According to RCS 180 documentation, -T only applies when a RCS file has been modified 181 because of lock changes; doesn't CVS sidestep RCS's entire lock 182 structure? -z seems to be unsupported by CVS diff, and has a 183 different meaning as a global option anyway. (Adding it could be 184 a feature, but if it is left out for now, it should not break 185 anything.) For the purposes of producing output, CVS diff appears 186 mostly to ignore -q. Maybe this should be fixed, but I think it's 187 a larger issue than the changes included here. */ 188 189 int 190 diff (argc, argv) 191 int argc; 192 char **argv; 193 { 194 char tmp[50]; 195 int c, err = 0; 196 int local = 0; 197 int which; 198 int option_index; 199 200 if (argc == -1) 201 usage (diff_usage); 202 203 have_rev1_label = have_rev2_label = 0; 204 205 /* 206 * Note that we catch all the valid arguments here, so that we can 207 * intercept the -r arguments for doing revision diffs; and -l/-R for a 208 * non-recursive/recursive diff. 209 */ 210 211 /* Clean out our global variables (multiroot can call us multiple 212 times and the server can too, if the client sends several 213 diff commands). */ 214 if (opts == NULL) 215 { 216 opts_allocated = 1; 217 opts = xmalloc (opts_allocated); 218 } 219 opts[0] = '\0'; 220 diff_rev1 = NULL; 221 diff_rev2 = NULL; 222 diff_date1 = NULL; 223 diff_date2 = NULL; 224 225 optind = 0; 226 while ((c = getopt_long (argc, argv, 227 "+abcdefhilnpstuwy0123456789BHNRTC:D:F:I:L:U:V:W:k:r:", 228 longopts, &option_index)) != -1) 229 { 230 switch (c) 231 { 232 case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': 233 case 'h': case 'i': case 'n': case 'p': case 's': case 't': 234 case 'u': case 'w': case 'y': 235 case '0': case '1': case '2': case '3': case '4': case '5': 236 case '6': case '7': case '8': case '9': 237 case 'B': case 'H': case 'T': 238 (void) sprintf (tmp, " -%c", (char) c); 239 allocate_and_strcat (&opts, &opts_allocated, tmp); 240 break; 241 case 'L': 242 if (have_rev1_label++) 243 if (have_rev2_label++) 244 { 245 error (0, 0, "extra -L arguments ignored"); 246 break; 247 } 248 249 allocate_and_strcat (&opts, &opts_allocated, " -L"); 250 allocate_and_strcat (&opts, &opts_allocated, optarg); 251 break; 252 case 'C': case 'F': case 'I': case 'U': case 'V': case 'W': 253 (void) sprintf (tmp, " -%c", (char) c); 254 allocate_and_strcat (&opts, &opts_allocated, tmp); 255 allocate_and_strcat (&opts, &opts_allocated, optarg); 256 break; 257 case 131: 258 /* --ifdef. */ 259 allocate_and_strcat (&opts, &opts_allocated, " --ifdef="); 260 allocate_and_strcat (&opts, &opts_allocated, optarg); 261 break; 262 case 129: case 130: case 132: case 133: case 134: 263 case 135: case 136: case 137: case 138: case 139: case 140: 264 case 141: case 142: case 143: case 144: case 145: case 146: 265 allocate_and_strcat (&opts, &opts_allocated, " --"); 266 allocate_and_strcat (&opts, &opts_allocated, 267 longopts[option_index].name); 268 if (longopts[option_index].has_arg == 1 269 || (longopts[option_index].has_arg == 2 270 && optarg != NULL)) 271 { 272 allocate_and_strcat (&opts, &opts_allocated, "="); 273 allocate_and_strcat (&opts, &opts_allocated, optarg); 274 } 275 break; 276 case 'R': 277 local = 0; 278 break; 279 case 'l': 280 local = 1; 281 break; 282 case 'k': 283 if (options) 284 free (options); 285 options = RCS_check_kflag (optarg); 286 break; 287 case 'r': 288 if (diff_rev2 != NULL || diff_date2 != NULL) 289 error (1, 0, 290 "no more than two revisions/dates can be specified"); 291 if (diff_rev1 != NULL || diff_date1 != NULL) 292 diff_rev2 = optarg; 293 else 294 diff_rev1 = optarg; 295 break; 296 case 'D': 297 if (diff_rev2 != NULL || diff_date2 != NULL) 298 error (1, 0, 299 "no more than two revisions/dates can be specified"); 300 if (diff_rev1 != NULL || diff_date1 != NULL) 301 diff_date2 = Make_Date (optarg); 302 else 303 diff_date1 = Make_Date (optarg); 304 break; 305 case 'N': 306 empty_files = 1; 307 break; 308 case '?': 309 default: 310 usage (diff_usage); 311 break; 312 } 313 } 314 argc -= optind; 315 argv += optind; 316 317 /* make sure options is non-null */ 318 if (!options) 319 options = xstrdup (""); 320 321 #ifdef CLIENT_SUPPORT 322 if (current_parsed_root->isremote) { 323 /* We're the client side. Fire up the remote server. */ 324 start_server (); 325 326 ign_setup (); 327 328 if (local) 329 send_arg("-l"); 330 if (empty_files) 331 send_arg("-N"); 332 send_option_string (opts); 333 if (options[0] != '\0') 334 send_arg (options); 335 if (diff_rev1) 336 option_with_arg ("-r", diff_rev1); 337 if (diff_date1) 338 client_senddate (diff_date1); 339 if (diff_rev2) 340 option_with_arg ("-r", diff_rev2); 341 if (diff_date2) 342 client_senddate (diff_date2); 343 344 /* Send the current files unless diffing two revs from the archive */ 345 if (diff_rev2 == NULL && diff_date2 == NULL) 346 send_files (argc, argv, local, 0, 0); 347 else 348 send_files (argc, argv, local, 0, SEND_NO_CONTENTS); 349 350 send_file_names (argc, argv, SEND_EXPAND_WILD); 351 352 send_to_server ("diff\012", 0); 353 err = get_responses_and_close (); 354 free (options); 355 options = NULL; 356 return (err); 357 } 358 #endif 359 360 if (diff_rev1 != NULL) 361 tag_check_valid (diff_rev1, argc, argv, local, 0, ""); 362 if (diff_rev2 != NULL) 363 tag_check_valid (diff_rev2, argc, argv, local, 0, ""); 364 365 which = W_LOCAL; 366 if (diff_rev1 != NULL || diff_date1 != NULL) 367 which |= W_REPOS | W_ATTIC; 368 369 wrap_setup (); 370 371 /* start the recursion processor */ 372 err = start_recursion (diff_fileproc, diff_filesdoneproc, diff_dirproc, 373 diff_dirleaveproc, NULL, argc, argv, local, 374 which, 0, 1, (char *) NULL, 1); 375 376 /* clean up */ 377 free (options); 378 options = NULL; 379 380 if (diff_date1 != NULL) 381 free (diff_date1); 382 if (diff_date2 != NULL) 383 free (diff_date2); 384 385 return (err); 386 } 387 388 /* 389 * Do a file diff 390 */ 391 /* ARGSUSED */ 392 static int 393 diff_fileproc (callerdat, finfo) 394 void *callerdat; 395 struct file_info *finfo; 396 { 397 int status, err = 2; /* 2 == trouble, like rcsdiff */ 398 Vers_TS *vers; 399 enum diff_file empty_file = DIFF_DIFFERENT; 400 char *tmp; 401 char *tocvsPath; 402 char *fname; 403 char *label1; 404 char *label2; 405 406 /* Initialize these solely to avoid warnings from gcc -Wall about 407 variables that might be used uninitialized. */ 408 tmp = NULL; 409 fname = NULL; 410 411 user_file_rev = 0; 412 vers = Version_TS (finfo, NULL, NULL, NULL, 1, 0); 413 414 if (diff_rev2 != NULL || diff_date2 != NULL) 415 { 416 /* Skip all the following checks regarding the user file; we're 417 not using it. */ 418 } 419 else if (vers->vn_user == NULL) 420 { 421 /* The file does not exist in the working directory. */ 422 if ((diff_rev1 != NULL || diff_date1 != NULL) 423 && vers->srcfile != NULL) 424 { 425 /* The file does exist in the repository. */ 426 if (empty_files) 427 empty_file = DIFF_REMOVED; 428 else 429 { 430 int exists; 431 432 exists = 0; 433 /* special handling for TAG_HEAD */ 434 if (diff_rev1 && strcmp (diff_rev1, TAG_HEAD) == 0) 435 { 436 char *head = 437 (vers->vn_rcs == NULL 438 ? NULL 439 : RCS_branch_head (vers->srcfile, vers->vn_rcs)); 440 exists = head != NULL; 441 if (head != NULL) 442 free (head); 443 } 444 else 445 { 446 Vers_TS *xvers; 447 448 xvers = Version_TS (finfo, NULL, diff_rev1, diff_date1, 449 1, 0); 450 exists = xvers->vn_rcs != NULL; 451 freevers_ts (&xvers); 452 } 453 if (exists) 454 error (0, 0, 455 "%s no longer exists, no comparison available", 456 finfo->fullname); 457 freevers_ts (&vers); 458 diff_mark_errors (err); 459 return (err); 460 } 461 } 462 else 463 { 464 error (0, 0, "I know nothing about %s", finfo->fullname); 465 freevers_ts (&vers); 466 diff_mark_errors (err); 467 return (err); 468 } 469 } 470 else if (vers->vn_user[0] == '0' && vers->vn_user[1] == '\0') 471 { 472 if (empty_files) 473 empty_file = DIFF_ADDED; 474 else 475 { 476 error (0, 0, "%s is a new entry, no comparison available", 477 finfo->fullname); 478 freevers_ts (&vers); 479 diff_mark_errors (err); 480 return (err); 481 } 482 } 483 else if (vers->vn_user[0] == '-') 484 { 485 if (empty_files) 486 empty_file = DIFF_REMOVED; 487 else 488 { 489 error (0, 0, "%s was removed, no comparison available", 490 finfo->fullname); 491 freevers_ts (&vers); 492 diff_mark_errors (err); 493 return (err); 494 } 495 } 496 else 497 { 498 if (vers->vn_rcs == NULL && vers->srcfile == NULL) 499 { 500 error (0, 0, "cannot find revision control file for %s", 501 finfo->fullname); 502 freevers_ts (&vers); 503 diff_mark_errors (err); 504 return (err); 505 } 506 else 507 { 508 if (vers->ts_user == NULL) 509 { 510 error (0, 0, "cannot find %s", finfo->fullname); 511 freevers_ts (&vers); 512 diff_mark_errors (err); 513 return (err); 514 } 515 else if (!strcmp (vers->ts_user, vers->ts_rcs)) 516 { 517 /* The user file matches some revision in the repository 518 Diff against the repository (for remote CVS, we might not 519 have a copy of the user file around). */ 520 user_file_rev = vers->vn_user; 521 } 522 } 523 } 524 525 empty_file = diff_file_nodiff (finfo, vers, empty_file); 526 if (empty_file == DIFF_SAME || empty_file == DIFF_ERROR) 527 { 528 freevers_ts (&vers); 529 if (empty_file == DIFF_SAME) 530 { 531 /* In the server case, would be nice to send a "Checked-in" 532 response, so that the client can rewrite its timestamp. 533 server_checked_in by itself isn't the right thing (it 534 needs a server_register), but I'm not sure what is. 535 It isn't clear to me how "cvs status" handles this (that 536 is, for a client which sends Modified not Is-modified to 537 "cvs status"), but it does. */ 538 return (0); 539 } 540 else 541 { 542 diff_mark_errors (err); 543 return (err); 544 } 545 } 546 547 if (empty_file == DIFF_DIFFERENT) 548 { 549 int dead1, dead2; 550 551 if (use_rev1 == NULL) 552 dead1 = 0; 553 else 554 dead1 = RCS_isdead (vers->srcfile, use_rev1); 555 if (use_rev2 == NULL) 556 dead2 = 0; 557 else 558 dead2 = RCS_isdead (vers->srcfile, use_rev2); 559 560 if (dead1 && dead2) 561 { 562 freevers_ts (&vers); 563 return (0); 564 } 565 else if (dead1) 566 { 567 if (empty_files) 568 empty_file = DIFF_ADDED; 569 else 570 { 571 error (0, 0, "%s is a new entry, no comparison available", 572 finfo->fullname); 573 freevers_ts (&vers); 574 diff_mark_errors (err); 575 return (err); 576 } 577 } 578 else if (dead2) 579 { 580 if (empty_files) 581 empty_file = DIFF_REMOVED; 582 else 583 { 584 error (0, 0, "%s was removed, no comparison available", 585 finfo->fullname); 586 freevers_ts (&vers); 587 diff_mark_errors (err); 588 return (err); 589 } 590 } 591 } 592 593 /* Output an "Index:" line for patch to use */ 594 cvs_output ("Index: ", 0); 595 cvs_output (finfo->fullname, 0); 596 cvs_output ("\n", 1); 597 598 tocvsPath = wrap_tocvs_process_file(finfo->file); 599 if (tocvsPath) 600 { 601 /* Backup the current version of the file to CVS/,,filename */ 602 fname = xmalloc (strlen (finfo->file) 603 + sizeof CVSADM 604 + sizeof CVSPREFIX 605 + 10); 606 sprintf(fname,"%s/%s%s",CVSADM, CVSPREFIX, finfo->file); 607 if (unlink_file_dir (fname) < 0) 608 if (! existence_error (errno)) 609 error (1, errno, "cannot remove %s", fname); 610 rename_file (finfo->file, fname); 611 /* Copy the wrapped file to the current directory then go to work */ 612 copy_file (tocvsPath, finfo->file); 613 } 614 615 /* Set up file labels appropriate for compatibility with the Larry Wall 616 * implementation of patch if the user didn't specify. This is irrelevant 617 * according to the POSIX.2 specification. 618 */ 619 label1 = NULL; 620 label2 = NULL; 621 if (!have_rev1_label) 622 { 623 if (empty_file == DIFF_ADDED) 624 label1 = 625 make_file_label (DEVNULL, NULL, NULL); 626 else 627 label1 = 628 make_file_label (finfo->fullname, use_rev1, vers ? vers->srcfile : NULL); 629 } 630 631 if (!have_rev2_label) 632 { 633 if (empty_file == DIFF_REMOVED) 634 label2 = 635 make_file_label (DEVNULL, NULL, NULL); 636 else 637 label2 = 638 make_file_label (finfo->fullname, use_rev2, vers ? vers->srcfile : NULL); 639 } 640 641 if (empty_file == DIFF_ADDED || empty_file == DIFF_REMOVED) 642 { 643 /* This is fullname, not file, possibly despite the POSIX.2 644 * specification, because that's the way all the Larry Wall 645 * implementations of patch (are there other implementations?) want 646 * things and the POSIX.2 spec appears to leave room for this. 647 */ 648 cvs_output ("\ 649 ===================================================================\n\ 650 RCS file: ", 0); 651 cvs_output (finfo->fullname, 0); 652 cvs_output ("\n", 1); 653 654 cvs_output ("diff -N ", 0); 655 cvs_output (finfo->fullname, 0); 656 cvs_output ("\n", 1); 657 658 if (empty_file == DIFF_ADDED) 659 { 660 if (use_rev2 == NULL) 661 status = diff_exec (DEVNULL, finfo->file, label1, label2, opts, RUN_TTY); 662 else 663 { 664 int retcode; 665 666 tmp = cvs_temp_name (); 667 retcode = RCS_checkout (vers->srcfile, (char *) NULL, 668 use_rev2, (char *) NULL, 669 (*options 670 ? options 671 : vers->options), 672 tmp, (RCSCHECKOUTPROC) NULL, 673 (void *) NULL); 674 if (retcode != 0) 675 { 676 diff_mark_errors (err); 677 return err; 678 } 679 680 status = diff_exec (DEVNULL, tmp, label1, label2, opts, RUN_TTY); 681 } 682 } 683 else 684 { 685 int retcode; 686 687 tmp = cvs_temp_name (); 688 retcode = RCS_checkout (vers->srcfile, (char *) NULL, 689 use_rev1, (char *) NULL, 690 *options ? options : vers->options, 691 tmp, (RCSCHECKOUTPROC) NULL, 692 (void *) NULL); 693 if (retcode != 0) 694 { 695 diff_mark_errors (err); 696 return err; 697 } 698 699 status = diff_exec (tmp, DEVNULL, label1, label2, opts, RUN_TTY); 700 } 701 } 702 else 703 { 704 status = RCS_exec_rcsdiff (vers->srcfile, opts, 705 *options ? options : vers->options, 706 use_rev1, use_rev2, 707 label1, label2, 708 finfo->file); 709 710 if (label1) free (label1); 711 if (label2) free (label2); 712 } 713 714 switch (status) 715 { 716 case -1: /* fork failed */ 717 error (1, errno, "fork failed while diffing %s", 718 vers->srcfile->path); 719 case 0: /* everything ok */ 720 err = 0; 721 break; 722 default: /* other error */ 723 err = status; 724 break; 725 } 726 727 if (tocvsPath) 728 { 729 if (unlink_file_dir (finfo->file) < 0) 730 if (! existence_error (errno)) 731 error (1, errno, "cannot remove %s", finfo->file); 732 733 rename_file (fname, finfo->file); 734 if (unlink_file (tocvsPath) < 0) 735 error (1, errno, "cannot remove %s", tocvsPath); 736 free (fname); 737 } 738 739 if (empty_file == DIFF_REMOVED 740 || (empty_file == DIFF_ADDED && use_rev2 != NULL)) 741 { 742 if (CVS_UNLINK (tmp) < 0) 743 error (0, errno, "cannot remove %s", tmp); 744 free (tmp); 745 } 746 747 freevers_ts (&vers); 748 diff_mark_errors (err); 749 return (err); 750 } 751 752 /* 753 * Remember the exit status for each file. 754 */ 755 static void 756 diff_mark_errors (err) 757 int err; 758 { 759 if (err > diff_errors) 760 diff_errors = err; 761 } 762 763 /* 764 * Print a warm fuzzy message when we enter a dir 765 * 766 * Don't try to diff directories that don't exist! -- DW 767 */ 768 /* ARGSUSED */ 769 static Dtype 770 diff_dirproc (callerdat, dir, pos_repos, update_dir, entries) 771 void *callerdat; 772 char *dir; 773 char *pos_repos; 774 char *update_dir; 775 List *entries; 776 { 777 /* XXX - check for dirs we don't want to process??? */ 778 779 /* YES ... for instance dirs that don't exist!!! -- DW */ 780 if (!isdir (dir)) 781 return (R_SKIP_ALL); 782 783 if (!quiet) 784 error (0, 0, "Diffing %s", update_dir); 785 return (R_PROCESS); 786 } 787 788 /* 789 * Concoct the proper exit status - done with files 790 */ 791 /* ARGSUSED */ 792 static int 793 diff_filesdoneproc (callerdat, err, repos, update_dir, entries) 794 void *callerdat; 795 int err; 796 char *repos; 797 char *update_dir; 798 List *entries; 799 { 800 return (diff_errors); 801 } 802 803 /* 804 * Concoct the proper exit status - leaving directories 805 */ 806 /* ARGSUSED */ 807 static int 808 diff_dirleaveproc (callerdat, dir, err, update_dir, entries) 809 void *callerdat; 810 char *dir; 811 int err; 812 char *update_dir; 813 List *entries; 814 { 815 return (diff_errors); 816 } 817 818 /* 819 * verify that a file is different 820 */ 821 static enum diff_file 822 diff_file_nodiff (finfo, vers, empty_file) 823 struct file_info *finfo; 824 Vers_TS *vers; 825 enum diff_file empty_file; 826 { 827 Vers_TS *xvers; 828 int retcode; 829 830 /* free up any old use_rev* variables and reset 'em */ 831 if (use_rev1) 832 free (use_rev1); 833 if (use_rev2) 834 free (use_rev2); 835 use_rev1 = use_rev2 = (char *) NULL; 836 837 if (diff_rev1 || diff_date1) 838 { 839 /* special handling for TAG_HEAD */ 840 if (diff_rev1 && strcmp (diff_rev1, TAG_HEAD) == 0) 841 use_rev1 = ((vers->vn_rcs == NULL || vers->srcfile == NULL) 842 ? NULL 843 : RCS_branch_head (vers->srcfile, vers->vn_rcs)); 844 else 845 { 846 xvers = Version_TS (finfo, NULL, diff_rev1, diff_date1, 1, 0); 847 if (xvers->vn_rcs != NULL) 848 use_rev1 = xstrdup (xvers->vn_rcs); 849 freevers_ts (&xvers); 850 } 851 } 852 if (diff_rev2 || diff_date2) 853 { 854 /* special handling for TAG_HEAD */ 855 if (diff_rev2 && strcmp (diff_rev2, TAG_HEAD) == 0) 856 use_rev2 = ((vers->vn_rcs == NULL || vers->srcfile == NULL) 857 ? NULL 858 : RCS_branch_head (vers->srcfile, vers->vn_rcs)); 859 else 860 { 861 xvers = Version_TS (finfo, NULL, diff_rev2, diff_date2, 1, 0); 862 if (xvers->vn_rcs != NULL) 863 use_rev2 = xstrdup (xvers->vn_rcs); 864 freevers_ts (&xvers); 865 } 866 867 if (use_rev1 == NULL) 868 { 869 /* The first revision does not exist. If EMPTY_FILES is 870 true, treat this as an added file. Otherwise, warn 871 about the missing tag. */ 872 if (use_rev2 == NULL || RCS_isdead( vers->srcfile, use_rev2 ) ) 873 /* At least in the case where DIFF_REV1 and DIFF_REV2 874 are both numeric, we should be returning some kind 875 of error (see basicb-8a0 in testsuite). The symbolic 876 case may be more complicated. */ 877 return DIFF_SAME; 878 else if (empty_files) 879 return DIFF_ADDED; 880 else if (diff_rev1) 881 error (0, 0, "tag %s is not in file %s", diff_rev1, 882 finfo->fullname); 883 else 884 error (0, 0, "no revision for date %s in file %s", 885 diff_date1, finfo->fullname); 886 return DIFF_ERROR; 887 } 888 889 if (use_rev2 == NULL) 890 { 891 /* The second revision does not exist. If EMPTY_FILES is 892 true, treat this as a removed file. Otherwise warn 893 about the missing tag. */ 894 if (empty_files) 895 return DIFF_REMOVED; 896 else if (diff_rev2) 897 error (0, 0, "tag %s is not in file %s", diff_rev2, 898 finfo->fullname); 899 else 900 error (0, 0, "no revision for date %s in file %s", 901 diff_date2, finfo->fullname); 902 return DIFF_ERROR; 903 } 904 905 /* now, see if we really need to do the diff */ 906 if (strcmp (use_rev1, use_rev2) == 0) 907 return DIFF_SAME; 908 else 909 return DIFF_DIFFERENT; 910 } 911 912 if ((diff_rev1 || diff_date1) && use_rev1 == NULL) 913 { 914 /* The first revision does not exist, and no second revision 915 was given. */ 916 if (empty_files) 917 { 918 if (empty_file == DIFF_REMOVED) 919 return DIFF_SAME; 920 else 921 { 922 if (user_file_rev && use_rev2 == NULL) 923 use_rev2 = xstrdup (user_file_rev); 924 return DIFF_ADDED; 925 } 926 } 927 else 928 { 929 if (diff_rev1) 930 error (0, 0, "tag %s is not in file %s", diff_rev1, 931 finfo->fullname); 932 else 933 error (0, 0, "no revision for date %s in file %s", 934 diff_date1, finfo->fullname); 935 return DIFF_ERROR; 936 } 937 } 938 939 if (user_file_rev) 940 { 941 /* drop user_file_rev into first unused use_rev */ 942 if (!use_rev1) 943 use_rev1 = xstrdup (user_file_rev); 944 else if (!use_rev2) 945 use_rev2 = xstrdup (user_file_rev); 946 /* and if not, it wasn't needed anyhow */ 947 user_file_rev = 0; 948 } 949 950 /* now, see if we really need to do the diff */ 951 if (use_rev1 && use_rev2) 952 { 953 if (strcmp (use_rev1, use_rev2) == 0) 954 return DIFF_SAME; 955 else 956 return DIFF_DIFFERENT; 957 } 958 959 if (use_rev1 == NULL 960 || (vers->vn_user != NULL && strcmp (use_rev1, vers->vn_user) == 0)) 961 { 962 if (empty_file == DIFF_DIFFERENT 963 && vers->ts_user != NULL 964 && strcmp (vers->ts_rcs, vers->ts_user) == 0 965 && (!(*options) || strcmp (options, vers->options) == 0)) 966 { 967 return DIFF_SAME; 968 } 969 if (use_rev1 == NULL 970 && (vers->vn_user[0] != '0' || vers->vn_user[1] != '\0')) 971 { 972 if (vers->vn_user[0] == '-') 973 use_rev1 = xstrdup (vers->vn_user + 1); 974 else 975 use_rev1 = xstrdup (vers->vn_user); 976 } 977 } 978 979 /* If we already know that the file is being added or removed, 980 then we don't want to do an actual file comparison here. */ 981 if (empty_file != DIFF_DIFFERENT) 982 return empty_file; 983 984 /* 985 * with 0 or 1 -r option specified, run a quick diff to see if we 986 * should bother with it at all. 987 */ 988 989 retcode = RCS_cmp_file (vers->srcfile, use_rev1, 990 *options ? options : vers->options, 991 finfo->file); 992 993 return retcode == 0 ? DIFF_SAME : DIFF_DIFFERENT; 994 } 995