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, 148}, 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, 147}, 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 static void strcat_and_allocate PROTO ((char **, size_t *, const char *)); 190 191 /* *STR is a pointer to a malloc'd string. *LENP is its allocated 192 length. Add SRC to the end of it, reallocating if necessary. */ 193 static void 194 strcat_and_allocate (str, lenp, src) 195 char **str; 196 size_t *lenp; 197 const char *src; 198 { 199 size_t new_size; 200 201 new_size = strlen (*str) + strlen (src) + 1; 202 if (*str == NULL || new_size >= *lenp) 203 { 204 while (new_size >= *lenp) 205 *lenp *= 2; 206 *str = xrealloc (*str, *lenp); 207 } 208 strcat (*str, src); 209 } 210 211 int 212 diff (argc, argv) 213 int argc; 214 char **argv; 215 { 216 char tmp[50]; 217 int c, err = 0; 218 int local = 0; 219 int which; 220 int option_index; 221 222 if (argc == -1) 223 usage (diff_usage); 224 225 have_rev1_label = have_rev2_label = 0; 226 227 /* 228 * Note that we catch all the valid arguments here, so that we can 229 * intercept the -r arguments for doing revision diffs; and -l/-R for a 230 * non-recursive/recursive diff. 231 */ 232 233 /* Clean out our global variables (multiroot can call us multiple 234 times and the server can too, if the client sends several 235 diff commands). */ 236 if (opts == NULL) 237 { 238 opts_allocated = 1; 239 opts = xmalloc (opts_allocated); 240 } 241 opts[0] = '\0'; 242 diff_rev1 = NULL; 243 diff_rev2 = NULL; 244 diff_date1 = NULL; 245 diff_date2 = NULL; 246 247 optind = 0; 248 while ((c = getopt_long (argc, argv, 249 "+abcdefhilnpstuw0123456789BHNRC:D:F:I:L:U:V:W:k:r:", 250 longopts, &option_index)) != -1) 251 { 252 switch (c) 253 { 254 case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': 255 case 'h': case 'i': case 'n': case 'p': case 's': case 't': 256 case 'u': case 'w': case '0': case '1': case '2': 257 case '3': case '4': case '5': case '6': case '7': case '8': 258 case '9': case 'B': case 'H': 259 (void) sprintf (tmp, " -%c", (char) c); 260 strcat_and_allocate (&opts, &opts_allocated, tmp); 261 break; 262 case 'L': 263 if (have_rev1_label++) 264 if (have_rev2_label++) 265 { 266 error (0, 0, "extra -L arguments ignored"); 267 break; 268 } 269 270 strcat_and_allocate (&opts, &opts_allocated, " -L"); 271 strcat_and_allocate (&opts, &opts_allocated, optarg); 272 break; 273 case 'C': case 'F': case 'I': case 'U': case 'V': case 'W': 274 (void) sprintf (tmp, " -%c", (char) c); 275 strcat_and_allocate (&opts, &opts_allocated, tmp); 276 strcat_and_allocate (&opts, &opts_allocated, optarg); 277 break; 278 case 131: 279 /* --ifdef. */ 280 strcat_and_allocate (&opts, &opts_allocated, " --ifdef="); 281 strcat_and_allocate (&opts, &opts_allocated, optarg); 282 break; 283 case 129: case 130: case 132: case 133: case 134: 284 case 135: case 136: case 137: case 138: case 139: case 140: 285 case 141: case 142: case 143: case 144: case 145: case 146: 286 case 147: case 148: 287 strcat_and_allocate (&opts, &opts_allocated, " --"); 288 strcat_and_allocate (&opts, &opts_allocated, 289 longopts[option_index].name); 290 if (longopts[option_index].has_arg == 1 291 || (longopts[option_index].has_arg == 2 292 && optarg != NULL)) 293 { 294 strcat_and_allocate (&opts, &opts_allocated, "="); 295 strcat_and_allocate (&opts, &opts_allocated, optarg); 296 } 297 break; 298 case 'R': 299 local = 0; 300 break; 301 case 'l': 302 local = 1; 303 break; 304 case 'k': 305 if (options) 306 free (options); 307 options = RCS_check_kflag (optarg); 308 break; 309 case 'r': 310 if (diff_rev2 != NULL || diff_date2 != NULL) 311 error (1, 0, 312 "no more than two revisions/dates can be specified"); 313 if (diff_rev1 != NULL || diff_date1 != NULL) 314 diff_rev2 = optarg; 315 else 316 diff_rev1 = optarg; 317 break; 318 case 'D': 319 if (diff_rev2 != NULL || diff_date2 != NULL) 320 error (1, 0, 321 "no more than two revisions/dates can be specified"); 322 if (diff_rev1 != NULL || diff_date1 != NULL) 323 diff_date2 = Make_Date (optarg); 324 else 325 diff_date1 = Make_Date (optarg); 326 break; 327 case 'N': 328 empty_files = 1; 329 break; 330 case '?': 331 default: 332 usage (diff_usage); 333 break; 334 } 335 } 336 argc -= optind; 337 argv += optind; 338 339 /* make sure options is non-null */ 340 if (!options) 341 options = xstrdup (""); 342 343 #ifdef CLIENT_SUPPORT 344 if (client_active) { 345 /* We're the client side. Fire up the remote server. */ 346 start_server (); 347 348 ign_setup (); 349 350 if (local) 351 send_arg("-l"); 352 if (empty_files) 353 send_arg("-N"); 354 send_option_string (opts); 355 if (options[0] != '\0') 356 send_arg (options); 357 if (diff_rev1) 358 option_with_arg ("-r", diff_rev1); 359 if (diff_date1) 360 client_senddate (diff_date1); 361 if (diff_rev2) 362 option_with_arg ("-r", diff_rev2); 363 if (diff_date2) 364 client_senddate (diff_date2); 365 366 /* Send the current files unless diffing two revs from the archive */ 367 if (diff_rev2 == NULL && diff_date2 == NULL) 368 send_files (argc, argv, local, 0, 0); 369 else 370 send_files (argc, argv, local, 0, SEND_NO_CONTENTS); 371 372 send_file_names (argc, argv, SEND_EXPAND_WILD); 373 374 send_to_server ("diff\012", 0); 375 err = get_responses_and_close (); 376 free (options); 377 options = NULL; 378 return (err); 379 } 380 #endif 381 382 if (diff_rev1 != NULL) 383 tag_check_valid (diff_rev1, argc, argv, local, 0, ""); 384 if (diff_rev2 != NULL) 385 tag_check_valid (diff_rev2, argc, argv, local, 0, ""); 386 387 which = W_LOCAL; 388 if (diff_rev1 != NULL || diff_date1 != NULL) 389 which |= W_REPOS | W_ATTIC; 390 391 wrap_setup (); 392 393 /* start the recursion processor */ 394 err = start_recursion (diff_fileproc, diff_filesdoneproc, diff_dirproc, 395 diff_dirleaveproc, NULL, argc, argv, local, 396 which, 0, 1, (char *) NULL, 1); 397 398 /* clean up */ 399 free (options); 400 options = NULL; 401 402 if (diff_date1 != NULL) 403 free (diff_date1); 404 if (diff_date2 != NULL) 405 free (diff_date2); 406 407 return (err); 408 } 409 410 /* 411 * Do a file diff 412 */ 413 /* ARGSUSED */ 414 static int 415 diff_fileproc (callerdat, finfo) 416 void *callerdat; 417 struct file_info *finfo; 418 { 419 int status, err = 2; /* 2 == trouble, like rcsdiff */ 420 Vers_TS *vers; 421 enum diff_file empty_file = DIFF_DIFFERENT; 422 char *tmp; 423 char *tocvsPath; 424 char *fname; 425 426 /* Initialize these solely to avoid warnings from gcc -Wall about 427 variables that might be used uninitialized. */ 428 tmp = NULL; 429 fname = NULL; 430 431 user_file_rev = 0; 432 vers = Version_TS (finfo, NULL, NULL, NULL, 1, 0); 433 434 if (diff_rev2 != NULL || diff_date2 != NULL) 435 { 436 /* Skip all the following checks regarding the user file; we're 437 not using it. */ 438 } 439 else if (vers->vn_user == NULL) 440 { 441 /* The file does not exist in the working directory. */ 442 if ((diff_rev1 != NULL || diff_date1 != NULL) 443 && vers->srcfile != NULL) 444 { 445 /* The file does exist in the repository. */ 446 if (empty_files) 447 empty_file = DIFF_REMOVED; 448 else 449 { 450 int exists; 451 452 exists = 0; 453 /* special handling for TAG_HEAD */ 454 if (diff_rev1 && strcmp (diff_rev1, TAG_HEAD) == 0) 455 { 456 char *head = 457 (vers->vn_rcs == NULL 458 ? NULL 459 : RCS_branch_head (vers->srcfile, vers->vn_rcs)); 460 exists = head != NULL; 461 if (head != NULL) 462 free (head); 463 } 464 else 465 { 466 Vers_TS *xvers; 467 468 xvers = Version_TS (finfo, NULL, diff_rev1, diff_date1, 469 1, 0); 470 exists = xvers->vn_rcs != NULL; 471 freevers_ts (&xvers); 472 } 473 if (exists) 474 error (0, 0, 475 "%s no longer exists, no comparison available", 476 finfo->fullname); 477 freevers_ts (&vers); 478 diff_mark_errors (err); 479 return (err); 480 } 481 } 482 else 483 { 484 error (0, 0, "I know nothing about %s", finfo->fullname); 485 freevers_ts (&vers); 486 diff_mark_errors (err); 487 return (err); 488 } 489 } 490 else if (vers->vn_user[0] == '0' && vers->vn_user[1] == '\0') 491 { 492 if (empty_files) 493 empty_file = DIFF_ADDED; 494 else 495 { 496 error (0, 0, "%s is a new entry, no comparison available", 497 finfo->fullname); 498 freevers_ts (&vers); 499 diff_mark_errors (err); 500 return (err); 501 } 502 } 503 else if (vers->vn_user[0] == '-') 504 { 505 if (empty_files) 506 empty_file = DIFF_REMOVED; 507 else 508 { 509 error (0, 0, "%s was removed, no comparison available", 510 finfo->fullname); 511 freevers_ts (&vers); 512 diff_mark_errors (err); 513 return (err); 514 } 515 } 516 else 517 { 518 if (vers->vn_rcs == NULL && vers->srcfile == NULL) 519 { 520 error (0, 0, "cannot find revision control file for %s", 521 finfo->fullname); 522 freevers_ts (&vers); 523 diff_mark_errors (err); 524 return (err); 525 } 526 else 527 { 528 if (vers->ts_user == NULL) 529 { 530 error (0, 0, "cannot find %s", finfo->fullname); 531 freevers_ts (&vers); 532 diff_mark_errors (err); 533 return (err); 534 } 535 else if (!strcmp (vers->ts_user, vers->ts_rcs)) 536 { 537 /* The user file matches some revision in the repository 538 Diff against the repository (for remote CVS, we might not 539 have a copy of the user file around). */ 540 user_file_rev = vers->vn_user; 541 } 542 } 543 } 544 545 empty_file = diff_file_nodiff (finfo, vers, empty_file); 546 if (empty_file == DIFF_SAME || empty_file == DIFF_ERROR) 547 { 548 freevers_ts (&vers); 549 if (empty_file == DIFF_SAME) 550 { 551 /* In the server case, would be nice to send a "Checked-in" 552 response, so that the client can rewrite its timestamp. 553 server_checked_in by itself isn't the right thing (it 554 needs a server_register), but I'm not sure what is. 555 It isn't clear to me how "cvs status" handles this (that 556 is, for a client which sends Modified not Is-modified to 557 "cvs status"), but it does. */ 558 return (0); 559 } 560 else 561 { 562 diff_mark_errors (err); 563 return (err); 564 } 565 } 566 567 if (empty_file == DIFF_DIFFERENT) 568 { 569 int dead1, dead2; 570 571 if (use_rev1 == NULL) 572 dead1 = 0; 573 else 574 dead1 = RCS_isdead (vers->srcfile, use_rev1); 575 if (use_rev2 == NULL) 576 dead2 = 0; 577 else 578 dead2 = RCS_isdead (vers->srcfile, use_rev2); 579 580 if (dead1 && dead2) 581 { 582 freevers_ts (&vers); 583 return (0); 584 } 585 else if (dead1) 586 { 587 if (empty_files) 588 empty_file = DIFF_ADDED; 589 else 590 { 591 error (0, 0, "%s is a new entry, no comparison available", 592 finfo->fullname); 593 freevers_ts (&vers); 594 diff_mark_errors (err); 595 return (err); 596 } 597 } 598 else if (dead2) 599 { 600 if (empty_files) 601 empty_file = DIFF_REMOVED; 602 else 603 { 604 error (0, 0, "%s was removed, no comparison available", 605 finfo->fullname); 606 freevers_ts (&vers); 607 diff_mark_errors (err); 608 return (err); 609 } 610 } 611 } 612 613 /* Output an "Index:" line for patch to use */ 614 cvs_output ("Index: ", 0); 615 cvs_output (finfo->fullname, 0); 616 cvs_output ("\n", 1); 617 618 tocvsPath = wrap_tocvs_process_file(finfo->file); 619 if (tocvsPath) 620 { 621 /* Backup the current version of the file to CVS/,,filename */ 622 fname = xmalloc (strlen (finfo->file) 623 + sizeof CVSADM 624 + sizeof CVSPREFIX 625 + 10); 626 sprintf(fname,"%s/%s%s",CVSADM, CVSPREFIX, finfo->file); 627 if (unlink_file_dir (fname) < 0) 628 if (! existence_error (errno)) 629 error (1, errno, "cannot remove %s", fname); 630 rename_file (finfo->file, fname); 631 /* Copy the wrapped file to the current directory then go to work */ 632 copy_file (tocvsPath, finfo->file); 633 } 634 635 if (empty_file == DIFF_ADDED || empty_file == DIFF_REMOVED) 636 { 637 /* This is file, not fullname, because it is the "Index:" line which 638 is supposed to contain the directory. */ 639 cvs_output ("\ 640 ===================================================================\n\ 641 RCS file: ", 0); 642 cvs_output (finfo->file, 0); 643 cvs_output ("\n", 1); 644 645 cvs_output ("diff -N ", 0); 646 cvs_output (finfo->file, 0); 647 cvs_output ("\n", 1); 648 649 if (empty_file == DIFF_ADDED) 650 { 651 if (use_rev2 == NULL) 652 status = diff_exec (DEVNULL, finfo->file, opts, RUN_TTY); 653 else 654 { 655 int retcode; 656 657 tmp = cvs_temp_name (); 658 retcode = RCS_checkout (vers->srcfile, (char *) NULL, 659 use_rev2, (char *) NULL, 660 (*options 661 ? options 662 : vers->options), 663 tmp, (RCSCHECKOUTPROC) NULL, 664 (void *) NULL); 665 if (retcode != 0) 666 { 667 diff_mark_errors (err); 668 return err; 669 } 670 671 status = diff_exec (DEVNULL, tmp, opts, RUN_TTY); 672 } 673 } 674 else 675 { 676 int retcode; 677 678 tmp = cvs_temp_name (); 679 retcode = RCS_checkout (vers->srcfile, (char *) NULL, 680 use_rev1, (char *) NULL, 681 *options ? options : vers->options, 682 tmp, (RCSCHECKOUTPROC) NULL, 683 (void *) NULL); 684 if (retcode != 0) 685 { 686 diff_mark_errors (err); 687 return err; 688 } 689 690 status = diff_exec (tmp, DEVNULL, opts, RUN_TTY); 691 } 692 } 693 else 694 { 695 char *label1 = NULL; 696 char *label2 = NULL; 697 698 if (!have_rev1_label) 699 label1 = 700 make_file_label (finfo->fullname, use_rev1, vers->srcfile); 701 702 if (!have_rev2_label) 703 label2 = 704 make_file_label (finfo->fullname, use_rev2, vers->srcfile); 705 706 status = RCS_exec_rcsdiff (vers->srcfile, opts, 707 *options ? options : vers->options, 708 use_rev1, use_rev2, 709 label1, label2, 710 finfo->file); 711 712 if (label1) free (label1); 713 if (label2) free (label2); 714 } 715 716 switch (status) 717 { 718 case -1: /* fork failed */ 719 error (1, errno, "fork failed while diffing %s", 720 vers->srcfile->path); 721 case 0: /* everything ok */ 722 err = 0; 723 break; 724 default: /* other error */ 725 err = status; 726 break; 727 } 728 729 if (tocvsPath) 730 { 731 if (unlink_file_dir (finfo->file) < 0) 732 if (! existence_error (errno)) 733 error (1, errno, "cannot remove %s", finfo->file); 734 735 rename_file (fname, finfo->file); 736 if (unlink_file (tocvsPath) < 0) 737 error (1, errno, "cannot remove %s", tocvsPath); 738 free (fname); 739 } 740 741 if (empty_file == DIFF_REMOVED 742 || (empty_file == DIFF_ADDED && use_rev2 != NULL)) 743 { 744 if (CVS_UNLINK (tmp) < 0) 745 error (0, errno, "cannot remove %s", tmp); 746 free (tmp); 747 } 748 749 freevers_ts (&vers); 750 diff_mark_errors (err); 751 return (err); 752 } 753 754 /* 755 * Remember the exit status for each file. 756 */ 757 static void 758 diff_mark_errors (err) 759 int err; 760 { 761 if (err > diff_errors) 762 diff_errors = err; 763 } 764 765 /* 766 * Print a warm fuzzy message when we enter a dir 767 * 768 * Don't try to diff directories that don't exist! -- DW 769 */ 770 /* ARGSUSED */ 771 static Dtype 772 diff_dirproc (callerdat, dir, pos_repos, update_dir, entries) 773 void *callerdat; 774 char *dir; 775 char *pos_repos; 776 char *update_dir; 777 List *entries; 778 { 779 /* XXX - check for dirs we don't want to process??? */ 780 781 /* YES ... for instance dirs that don't exist!!! -- DW */ 782 if (!isdir (dir)) 783 return (R_SKIP_ALL); 784 785 if (!quiet) 786 error (0, 0, "Diffing %s", update_dir); 787 return (R_PROCESS); 788 } 789 790 /* 791 * Concoct the proper exit status - done with files 792 */ 793 /* ARGSUSED */ 794 static int 795 diff_filesdoneproc (callerdat, err, repos, update_dir, entries) 796 void *callerdat; 797 int err; 798 char *repos; 799 char *update_dir; 800 List *entries; 801 { 802 return (diff_errors); 803 } 804 805 /* 806 * Concoct the proper exit status - leaving directories 807 */ 808 /* ARGSUSED */ 809 static int 810 diff_dirleaveproc (callerdat, dir, err, update_dir, entries) 811 void *callerdat; 812 char *dir; 813 int err; 814 char *update_dir; 815 List *entries; 816 { 817 return (diff_errors); 818 } 819 820 /* 821 * verify that a file is different 822 */ 823 static enum diff_file 824 diff_file_nodiff (finfo, vers, empty_file) 825 struct file_info *finfo; 826 Vers_TS *vers; 827 enum diff_file empty_file; 828 { 829 Vers_TS *xvers; 830 int retcode; 831 832 /* free up any old use_rev* variables and reset 'em */ 833 if (use_rev1) 834 free (use_rev1); 835 if (use_rev2) 836 free (use_rev2); 837 use_rev1 = use_rev2 = (char *) NULL; 838 839 if (diff_rev1 || diff_date1) 840 { 841 /* special handling for TAG_HEAD */ 842 if (diff_rev1 && strcmp (diff_rev1, TAG_HEAD) == 0) 843 use_rev1 = ((vers->vn_rcs == NULL || vers->srcfile == NULL) 844 ? NULL 845 : RCS_branch_head (vers->srcfile, vers->vn_rcs)); 846 else 847 { 848 xvers = Version_TS (finfo, NULL, diff_rev1, diff_date1, 1, 0); 849 if (xvers->vn_rcs != NULL) 850 use_rev1 = xstrdup (xvers->vn_rcs); 851 freevers_ts (&xvers); 852 } 853 } 854 if (diff_rev2 || diff_date2) 855 { 856 /* special handling for TAG_HEAD */ 857 if (diff_rev2 && strcmp (diff_rev2, TAG_HEAD) == 0) 858 use_rev2 = ((vers->vn_rcs == NULL || vers->srcfile == NULL) 859 ? NULL 860 : RCS_branch_head (vers->srcfile, vers->vn_rcs)); 861 else 862 { 863 xvers = Version_TS (finfo, NULL, diff_rev2, diff_date2, 1, 0); 864 if (xvers->vn_rcs != NULL) 865 use_rev2 = xstrdup (xvers->vn_rcs); 866 freevers_ts (&xvers); 867 } 868 869 if (use_rev1 == NULL) 870 { 871 /* The first revision does not exist. If EMPTY_FILES is 872 true, treat this as an added file. Otherwise, warn 873 about the missing tag. */ 874 if (use_rev2 == NULL) 875 /* At least in the case where DIFF_REV1 and DIFF_REV2 876 are both numeric, we should be returning some kind 877 of error (see basicb-8a0 in testsuite). The symbolic 878 case may be more complicated. */ 879 return DIFF_SAME; 880 else if (empty_files) 881 return DIFF_ADDED; 882 else if (diff_rev1) 883 error (0, 0, "tag %s is not in file %s", diff_rev1, 884 finfo->fullname); 885 else 886 error (0, 0, "no revision for date %s in file %s", 887 diff_date1, finfo->fullname); 888 return DIFF_ERROR; 889 } 890 891 if (use_rev2 == NULL) 892 { 893 /* The second revision does not exist. If EMPTY_FILES is 894 true, treat this as a removed file. Otherwise warn 895 about the missing tag. */ 896 if (empty_files) 897 return DIFF_REMOVED; 898 else if (diff_rev2) 899 error (0, 0, "tag %s is not in file %s", diff_rev2, 900 finfo->fullname); 901 else 902 error (0, 0, "no revision for date %s in file %s", 903 diff_date2, finfo->fullname); 904 return DIFF_ERROR; 905 } 906 907 /* now, see if we really need to do the diff */ 908 if (strcmp (use_rev1, use_rev2) == 0) 909 return DIFF_SAME; 910 else 911 return DIFF_DIFFERENT; 912 } 913 914 if ((diff_rev1 || diff_date1) && use_rev1 == NULL) 915 { 916 /* The first revision does not exist, and no second revision 917 was given. */ 918 if (empty_files) 919 { 920 if (empty_file == DIFF_REMOVED) 921 return DIFF_SAME; 922 else 923 { 924 if (user_file_rev && use_rev2 == NULL) 925 use_rev2 = xstrdup (user_file_rev); 926 return DIFF_ADDED; 927 } 928 } 929 else 930 { 931 if (diff_rev1) 932 error (0, 0, "tag %s is not in file %s", diff_rev1, 933 finfo->fullname); 934 else 935 error (0, 0, "no revision for date %s in file %s", 936 diff_date1, finfo->fullname); 937 return DIFF_ERROR; 938 } 939 } 940 941 if (user_file_rev) 942 { 943 /* drop user_file_rev into first unused use_rev */ 944 if (!use_rev1) 945 use_rev1 = xstrdup (user_file_rev); 946 else if (!use_rev2) 947 use_rev2 = xstrdup (user_file_rev); 948 /* and if not, it wasn't needed anyhow */ 949 user_file_rev = 0; 950 } 951 952 /* now, see if we really need to do the diff */ 953 if (use_rev1 && use_rev2) 954 { 955 if (strcmp (use_rev1, use_rev2) == 0) 956 return DIFF_SAME; 957 else 958 return DIFF_DIFFERENT; 959 } 960 961 if (use_rev1 == NULL 962 || (vers->vn_user != NULL && strcmp (use_rev1, vers->vn_user) == 0)) 963 { 964 if (empty_file == DIFF_DIFFERENT 965 && vers->ts_user != NULL 966 && strcmp (vers->ts_rcs, vers->ts_user) == 0 967 && (!(*options) || strcmp (options, vers->options) == 0)) 968 { 969 return DIFF_SAME; 970 } 971 if (use_rev1 == NULL 972 && (vers->vn_user[0] != '0' || vers->vn_user[1] != '\0')) 973 { 974 if (vers->vn_user[0] == '-') 975 use_rev1 = xstrdup (vers->vn_user + 1); 976 else 977 use_rev1 = xstrdup (vers->vn_user); 978 } 979 } 980 981 /* If we already know that the file is being added or removed, 982 then we don't want to do an actual file comparison here. */ 983 if (empty_file != DIFF_DIFFERENT) 984 return empty_file; 985 986 /* 987 * with 0 or 1 -r option specified, run a quick diff to see if we 988 * should bother with it at all. 989 */ 990 991 retcode = RCS_cmp_file (vers->srcfile, use_rev1, 992 *options ? options : vers->options, 993 finfo->file); 994 995 return retcode == 0 ? DIFF_SAME : DIFF_DIFFERENT; 996 } 997