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 1.4 kit. 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 static char *diff_rev1, *diff_rev2; 44 static char *diff_date1, *diff_date2; 45 static char *use_rev1, *use_rev2; 46 47 #ifdef SERVER_SUPPORT 48 /* Revision of the user file, if it is unchanged from something in the 49 repository and we want to use that fact. */ 50 static char *user_file_rev; 51 #endif 52 53 static char *options; 54 /* FIXME: arbitrary limit (security hole, if the client passes us 55 data which overflows it). */ 56 static char opts[PATH_MAX]; 57 static int diff_errors; 58 static int empty_files = 0; 59 60 /* FIXME: should be documenting all the options here. They don't 61 perfectly match rcsdiff options (for example, we always support 62 --ifdef and --context, but rcsdiff only does if diff does). */ 63 static const char *const diff_usage[] = 64 { 65 "Usage: %s %s [-lN] [rcsdiff-options]\n", 66 " [[-r rev1 | -D date1] [-r rev2 | -D date2]] [files...] \n", 67 "\t-l\tLocal directory only, not recursive\n", 68 "\t-D d1\tDiff revision for date against working file.\n", 69 "\t-D d2\tDiff rev1/date1 against date2.\n", 70 "\t-N\tinclude diffs for added and removed files.\n", 71 "\t-r rev1\tDiff revision for rev1 against working file.\n", 72 "\t-r rev2\tDiff rev1/date1 against rev2.\n", 73 NULL 74 }; 75 76 /* I copied this array directly out of diff.c in diffutils 2.7, after 77 removing the following entries, none of which seem relevant to use 78 with CVS: 79 --help 80 --version 81 --recursive 82 --unidirectional-new-file 83 --starting-file 84 --exclude 85 --exclude-from 86 --sdiff-merge-assist 87 88 I changed the options which take optional arguments (--context and 89 --unified) to return a number rather than a letter, so that the 90 optional argument could be handled more easily. I changed the 91 --paginate and --brief options to return a number, since -l and -q 92 mean something else to cvs diff. 93 94 The numbers 129- that appear in the fourth element of some entries 95 tell the big switch in `diff' how to process those options. -- Ian 96 97 The following options, which diff lists as "An alias, no longer 98 recommended" have been removed: --file-label --entire-new-file 99 --ascii --print. */ 100 101 static struct option const longopts[] = 102 { 103 {"ignore-blank-lines", 0, 0, 'B'}, 104 {"context", 2, 0, 143}, 105 {"ifdef", 1, 0, 147}, 106 {"show-function-line", 1, 0, 'F'}, 107 {"speed-large-files", 0, 0, 'H'}, 108 {"ignore-matching-lines", 1, 0, 'I'}, 109 {"label", 1, 0, 'L'}, 110 {"new-file", 0, 0, 'N'}, 111 {"initial-tab", 0, 0, 'T'}, 112 {"width", 1, 0, 'W'}, 113 {"text", 0, 0, 'a'}, 114 {"ignore-space-change", 0, 0, 'b'}, 115 {"minimal", 0, 0, 'd'}, 116 {"ed", 0, 0, 'e'}, 117 {"forward-ed", 0, 0, 'f'}, 118 {"ignore-case", 0, 0, 'i'}, 119 {"paginate", 0, 0, 144}, 120 {"rcs", 0, 0, 'n'}, 121 {"show-c-function", 0, 0, 'p'}, 122 123 /* This is a potentially very useful option, except the output is so 124 silly. It would be much better for it to look like "cvs rdiff -s" 125 which displays all the same info, minus quite a few lines of 126 extraneous garbage. */ 127 {"brief", 0, 0, 145}, 128 129 {"report-identical-files", 0, 0, 's'}, 130 {"expand-tabs", 0, 0, 't'}, 131 {"ignore-all-space", 0, 0, 'w'}, 132 {"side-by-side", 0, 0, 'y'}, 133 {"unified", 2, 0, 146}, 134 {"left-column", 0, 0, 129}, 135 {"suppress-common-lines", 0, 0, 130}, 136 {"old-line-format", 1, 0, 132}, 137 {"new-line-format", 1, 0, 133}, 138 {"unchanged-line-format", 1, 0, 134}, 139 {"line-format", 1, 0, 135}, 140 {"old-group-format", 1, 0, 136}, 141 {"new-group-format", 1, 0, 137}, 142 {"unchanged-group-format", 1, 0, 138}, 143 {"changed-group-format", 1, 0, 139}, 144 {"horizon-lines", 1, 0, 140}, 145 {"binary", 0, 0, 142}, 146 {0, 0, 0, 0} 147 }; 148 149 int 150 diff (argc, argv) 151 int argc; 152 char **argv; 153 { 154 char tmp[50]; 155 int c, err = 0; 156 int local = 0; 157 int which; 158 int option_index; 159 160 if (argc == -1) 161 usage (diff_usage); 162 163 /* 164 * Note that we catch all the valid arguments here, so that we can 165 * intercept the -r arguments for doing revision diffs; and -l/-R for a 166 * non-recursive/recursive diff. 167 */ 168 #ifdef SERVER_SUPPORT 169 /* Need to be able to do this command more than once (according to 170 the protocol spec, even if the current client doesn't use it). */ 171 opts[0] = '\0'; 172 #endif 173 optind = 1; 174 while ((c = getopt_long (argc, argv, 175 "abcdefhilnpstuwy0123456789BHNRTC:D:F:I:L:U:V:W:k:r:", 176 longopts, &option_index)) != -1) 177 { 178 switch (c) 179 { 180 case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': 181 case 'h': case 'i': case 'n': case 'p': case 's': case 't': 182 case 'u': case 'w': case 'y': case '0': case '1': case '2': 183 case '3': case '4': case '5': case '6': case '7': case '8': 184 case '9': case 'B': case 'H': case 'T': 185 (void) sprintf (tmp, " -%c", (char) c); 186 (void) strcat (opts, tmp); 187 break; 188 case 'C': case 'F': case 'I': case 'L': case 'U': case 'V': 189 case 'W': 190 (void) sprintf (tmp, " -%c", (char) c); 191 strcat (opts, tmp); 192 strcat (opts, optarg); 193 break; 194 case 147: 195 /* --ifdef. */ 196 strcat (opts, " -D"); 197 strcat (opts, optarg); 198 break; 199 case 129: case 130: case 131: case 132: case 133: case 134: 200 case 135: case 136: case 137: case 138: case 139: case 140: 201 case 141: case 142: case 143: case 144: case 145: case 146: 202 strcat (opts, " --"); 203 strcat (opts, longopts[option_index].name); 204 if (longopts[option_index].has_arg == 1 205 || (longopts[option_index].has_arg == 2 206 && optarg != NULL)) 207 { 208 strcat (opts, "="); 209 strcat (opts, optarg); 210 } 211 break; 212 case 'R': 213 local = 0; 214 break; 215 case 'l': 216 local = 1; 217 break; 218 case 'k': 219 if (options) 220 free (options); 221 options = RCS_check_kflag (optarg); 222 break; 223 case 'r': 224 if (diff_rev2 != NULL || diff_date2 != NULL) 225 error (1, 0, 226 "no more than two revisions/dates can be specified"); 227 if (diff_rev1 != NULL || diff_date1 != NULL) 228 diff_rev2 = optarg; 229 else 230 diff_rev1 = optarg; 231 break; 232 case 'D': 233 if (diff_rev2 != NULL || diff_date2 != NULL) 234 error (1, 0, 235 "no more than two revisions/dates can be specified"); 236 if (diff_rev1 != NULL || diff_date1 != NULL) 237 diff_date2 = Make_Date (optarg); 238 else 239 diff_date1 = Make_Date (optarg); 240 break; 241 case 'N': 242 empty_files = 1; 243 break; 244 case '?': 245 default: 246 usage (diff_usage); 247 break; 248 } 249 } 250 argc -= optind; 251 argv += optind; 252 253 /* make sure options is non-null */ 254 if (!options) 255 options = xstrdup (""); 256 257 #ifdef CLIENT_SUPPORT 258 if (client_active) { 259 /* We're the client side. Fire up the remote server. */ 260 start_server (); 261 262 ign_setup (); 263 264 if (local) 265 send_arg("-l"); 266 if (empty_files) 267 send_arg("-N"); 268 send_option_string (opts); 269 if (diff_rev1) 270 option_with_arg ("-r", diff_rev1); 271 if (diff_date1) 272 client_senddate (diff_date1); 273 if (diff_rev2) 274 option_with_arg ("-r", diff_rev2); 275 if (diff_date2) 276 client_senddate (diff_date2); 277 278 send_file_names (argc, argv, SEND_EXPAND_WILD); 279 #if 0 280 /* FIXME: We shouldn't have to send current files to diff two 281 revs, but it doesn't work yet and I haven't debugged it. 282 So send the files -- it's slower but it works. 283 gnu@cygnus.com Apr94 */ 284 /* Send the current files unless diffing two revs from the archive */ 285 if (diff_rev2 == NULL && diff_date2 == NULL) 286 #endif 287 send_files (argc, argv, local, 0); 288 289 send_to_server ("diff\012", 0); 290 err = get_responses_and_close (); 291 free (options); 292 return (err); 293 } 294 #endif 295 296 if (diff_rev1 != NULL) 297 tag_check_valid (diff_rev1, argc, argv, local, 0, ""); 298 if (diff_rev2 != NULL) 299 tag_check_valid (diff_rev2, argc, argv, local, 0, ""); 300 301 which = W_LOCAL; 302 if (diff_rev1 != NULL || diff_date1 != NULL) 303 which |= W_REPOS | W_ATTIC; 304 305 wrap_setup (); 306 307 /* start the recursion processor */ 308 err = start_recursion (diff_fileproc, diff_filesdoneproc, diff_dirproc, 309 diff_dirleaveproc, NULL, argc, argv, local, 310 which, 0, 1, (char *) NULL, 1); 311 312 /* clean up */ 313 free (options); 314 return (err); 315 } 316 317 /* 318 * Do a file diff 319 */ 320 /* ARGSUSED */ 321 static int 322 diff_fileproc (callerdat, finfo) 323 void *callerdat; 324 struct file_info *finfo; 325 { 326 int status, err = 2; /* 2 == trouble, like rcsdiff */ 327 Vers_TS *vers; 328 enum diff_file empty_file = DIFF_DIFFERENT; 329 char *tmp; 330 char *tocvsPath; 331 char fname[PATH_MAX]; 332 333 #ifdef SERVER_SUPPORT 334 user_file_rev = 0; 335 #endif 336 vers = Version_TS (finfo, NULL, NULL, NULL, 1, 0); 337 338 if (diff_rev2 != NULL || diff_date2 != NULL) 339 { 340 /* Skip all the following checks regarding the user file; we're 341 not using it. */ 342 } 343 else if (vers->vn_user == NULL) 344 { 345 /* The file does not exist in the working directory. */ 346 if ((diff_rev1 != NULL || diff_date1 != NULL) 347 && vers->srcfile != NULL) 348 { 349 /* The file does exist in the repository. */ 350 if (empty_files) 351 empty_file = DIFF_REMOVED; 352 else 353 { 354 int exists; 355 356 exists = 0; 357 /* special handling for TAG_HEAD */ 358 if (diff_rev1 && strcmp (diff_rev1, TAG_HEAD) == 0) 359 exists = vers->vn_rcs != NULL; 360 else 361 { 362 Vers_TS *xvers; 363 364 xvers = Version_TS (finfo, NULL, diff_rev1, diff_date1, 365 1, 0); 366 exists = xvers->vn_rcs != NULL; 367 freevers_ts (&xvers); 368 } 369 if (exists) 370 error (0, 0, 371 "%s no longer exists, no comparison available", 372 finfo->fullname); 373 freevers_ts (&vers); 374 diff_mark_errors (err); 375 return (err); 376 } 377 } 378 else 379 { 380 error (0, 0, "I know nothing about %s", finfo->fullname); 381 freevers_ts (&vers); 382 diff_mark_errors (err); 383 return (err); 384 } 385 } 386 else if (vers->vn_user[0] == '0' && vers->vn_user[1] == '\0') 387 { 388 if (empty_files) 389 empty_file = DIFF_ADDED; 390 else 391 { 392 error (0, 0, "%s is a new entry, no comparison available", 393 finfo->fullname); 394 freevers_ts (&vers); 395 diff_mark_errors (err); 396 return (err); 397 } 398 } 399 else if (vers->vn_user[0] == '-') 400 { 401 if (empty_files) 402 empty_file = DIFF_REMOVED; 403 else 404 { 405 error (0, 0, "%s was removed, no comparison available", 406 finfo->fullname); 407 freevers_ts (&vers); 408 diff_mark_errors (err); 409 return (err); 410 } 411 } 412 else 413 { 414 if (vers->vn_rcs == NULL && vers->srcfile == NULL) 415 { 416 error (0, 0, "cannot find revision control file for %s", 417 finfo->fullname); 418 freevers_ts (&vers); 419 diff_mark_errors (err); 420 return (err); 421 } 422 else 423 { 424 if (vers->ts_user == NULL) 425 { 426 error (0, 0, "cannot find %s", finfo->fullname); 427 freevers_ts (&vers); 428 diff_mark_errors (err); 429 return (err); 430 } 431 #ifdef SERVER_SUPPORT 432 else if (!strcmp (vers->ts_user, vers->ts_rcs)) 433 { 434 /* The user file matches some revision in the repository 435 Diff against the repository (for remote CVS, we might not 436 have a copy of the user file around). */ 437 user_file_rev = vers->vn_user; 438 } 439 #endif 440 } 441 } 442 443 empty_file = diff_file_nodiff (finfo, vers, empty_file); 444 if (empty_file == DIFF_SAME || empty_file == DIFF_ERROR) 445 { 446 freevers_ts (&vers); 447 if (empty_file == DIFF_SAME) 448 return (0); 449 else 450 { 451 diff_mark_errors (err); 452 return (err); 453 } 454 } 455 456 if (empty_file == DIFF_DIFFERENT) 457 { 458 int dead1, dead2; 459 460 if (use_rev1 == NULL) 461 dead1 = 0; 462 else 463 dead1 = RCS_isdead (vers->srcfile, use_rev1); 464 if (use_rev2 == NULL) 465 dead2 = 0; 466 else 467 dead2 = RCS_isdead (vers->srcfile, use_rev2); 468 469 if (dead1 && dead2) 470 { 471 freevers_ts (&vers); 472 return (0); 473 } 474 else if (dead1) 475 { 476 if (empty_files) 477 empty_file = DIFF_ADDED; 478 else 479 { 480 error (0, 0, "%s is a new entry, no comparison available", 481 finfo->fullname); 482 freevers_ts (&vers); 483 diff_mark_errors (err); 484 return (err); 485 } 486 } 487 else if (dead2) 488 { 489 if (empty_files) 490 empty_file = DIFF_REMOVED; 491 else 492 { 493 error (0, 0, "%s was removed, no comparison available", 494 finfo->fullname); 495 freevers_ts (&vers); 496 diff_mark_errors (err); 497 return (err); 498 } 499 } 500 } 501 502 /* Output an "Index:" line for patch to use */ 503 (void) fflush (stdout); 504 (void) printf ("Index: %s\n", finfo->fullname); 505 (void) fflush (stdout); 506 507 tocvsPath = wrap_tocvs_process_file(finfo->file); 508 if (tocvsPath) 509 { 510 /* Backup the current version of the file to CVS/,,filename */ 511 sprintf(fname,"%s/%s%s",CVSADM, CVSPREFIX, finfo->file); 512 if (unlink_file_dir (fname) < 0) 513 if (! existence_error (errno)) 514 error (1, errno, "cannot remove %s", finfo->file); 515 rename_file (finfo->file, fname); 516 /* Copy the wrapped file to the current directory then go to work */ 517 copy_file (tocvsPath, finfo->file); 518 } 519 520 if (empty_file == DIFF_ADDED || empty_file == DIFF_REMOVED) 521 { 522 /* This is file, not fullname, because it is the "Index:" line which 523 is supposed to contain the directory. */ 524 (void) printf ("===================================================================\nRCS file: %s\n", 525 finfo->file); 526 (void) printf ("diff -N %s\n", finfo->file); 527 528 if (empty_file == DIFF_ADDED) 529 { 530 if (use_rev2 == NULL) 531 run_setup ("%s %s %s %s", DIFF, opts, DEVNULL, finfo->file); 532 else 533 { 534 int retcode; 535 536 tmp = cvs_temp_name (); 537 retcode = RCS_checkout (vers->srcfile, (char *) NULL, 538 use_rev2, (char *) NULL, 539 (*options 540 ? options 541 : vers->options), 542 tmp); 543 if (retcode == -1) 544 { 545 (void) CVS_UNLINK (tmp); 546 error (1, errno, "fork failed during checkout of %s", 547 vers->srcfile->path); 548 } 549 /* FIXME: what if retcode > 0? */ 550 551 run_setup ("%s %s %s %s", DIFF, opts, DEVNULL, tmp); 552 } 553 } 554 else 555 { 556 int retcode; 557 558 tmp = cvs_temp_name (); 559 retcode = RCS_checkout (vers->srcfile, (char *) NULL, 560 use_rev1, (char *) NULL, 561 *options ? options : vers->options, 562 tmp); 563 if (retcode == -1) 564 { 565 (void) CVS_UNLINK (tmp); 566 error (1, errno, "fork failed during checkout of %s", 567 vers->srcfile->path); 568 } 569 /* FIXME: what if retcode > 0? */ 570 571 run_setup ("%s %s %s %s", DIFF, opts, tmp, DEVNULL); 572 } 573 } 574 else 575 { 576 if (use_rev2) 577 { 578 run_setup ("%s%s -x,v/ %s %s -r%s -r%s", Rcsbin, RCS_DIFF, 579 opts, *options ? options : vers->options, 580 use_rev1, use_rev2); 581 } 582 else 583 { 584 run_setup ("%s%s -x,v/ %s %s -r%s", Rcsbin, RCS_DIFF, opts, 585 *options ? options : vers->options, use_rev1); 586 } 587 run_arg (vers->srcfile->path); 588 } 589 590 switch ((status = run_exec (RUN_TTY, RUN_TTY, RUN_TTY, 591 RUN_REALLY|RUN_COMBINED))) 592 { 593 case -1: /* fork failed */ 594 error (1, errno, "fork failed during rcsdiff of %s", 595 vers->srcfile->path); 596 case 0: /* everything ok */ 597 err = 0; 598 break; 599 default: /* other error */ 600 err = status; 601 break; 602 } 603 604 if (tocvsPath) 605 { 606 if (unlink_file_dir (finfo->file) < 0) 607 if (! existence_error (errno)) 608 error (1, errno, "cannot remove %s", finfo->file); 609 610 rename_file (fname,finfo->file); 611 if (unlink_file (tocvsPath) < 0) 612 error (1, errno, "cannot remove %s", finfo->file); 613 } 614 615 if (empty_file == DIFF_REMOVED 616 || (empty_file == DIFF_ADDED && use_rev2 != NULL)) 617 { 618 (void) CVS_UNLINK (tmp); 619 free (tmp); 620 } 621 622 (void) fflush (stdout); 623 freevers_ts (&vers); 624 diff_mark_errors (err); 625 return (err); 626 } 627 628 /* 629 * Remember the exit status for each file. 630 */ 631 static void 632 diff_mark_errors (err) 633 int err; 634 { 635 if (err > diff_errors) 636 diff_errors = err; 637 } 638 639 /* 640 * Print a warm fuzzy message when we enter a dir 641 * 642 * Don't try to diff directories that don't exist! -- DW 643 */ 644 /* ARGSUSED */ 645 static Dtype 646 diff_dirproc (callerdat, dir, pos_repos, update_dir, entries) 647 void *callerdat; 648 char *dir; 649 char *pos_repos; 650 char *update_dir; 651 List *entries; 652 { 653 /* XXX - check for dirs we don't want to process??? */ 654 655 /* YES ... for instance dirs that don't exist!!! -- DW */ 656 if (!isdir (dir) ) 657 return (R_SKIP_ALL); 658 659 if (!quiet) 660 error (0, 0, "Diffing %s", update_dir); 661 return (R_PROCESS); 662 } 663 664 /* 665 * Concoct the proper exit status - done with files 666 */ 667 /* ARGSUSED */ 668 static int 669 diff_filesdoneproc (callerdat, err, repos, update_dir, entries) 670 void *callerdat; 671 int err; 672 char *repos; 673 char *update_dir; 674 List *entries; 675 { 676 return (diff_errors); 677 } 678 679 /* 680 * Concoct the proper exit status - leaving directories 681 */ 682 /* ARGSUSED */ 683 static int 684 diff_dirleaveproc (callerdat, dir, err, update_dir, entries) 685 void *callerdat; 686 char *dir; 687 int err; 688 char *update_dir; 689 List *entries; 690 { 691 return (diff_errors); 692 } 693 694 /* 695 * verify that a file is different 696 */ 697 static enum diff_file 698 diff_file_nodiff (finfo, vers, empty_file) 699 struct file_info *finfo; 700 Vers_TS *vers; 701 enum diff_file empty_file; 702 { 703 Vers_TS *xvers; 704 char *tmp; 705 int retcode; 706 707 /* free up any old use_rev* variables and reset 'em */ 708 if (use_rev1) 709 free (use_rev1); 710 if (use_rev2) 711 free (use_rev2); 712 use_rev1 = use_rev2 = (char *) NULL; 713 714 if (diff_rev1 || diff_date1) 715 { 716 /* special handling for TAG_HEAD */ 717 if (diff_rev1 && strcmp (diff_rev1, TAG_HEAD) == 0) 718 use_rev1 = xstrdup (vers->vn_rcs); 719 else 720 { 721 xvers = Version_TS (finfo, NULL, diff_rev1, diff_date1, 1, 0); 722 if (xvers->vn_rcs != NULL) 723 use_rev1 = xstrdup (xvers->vn_rcs); 724 freevers_ts (&xvers); 725 } 726 } 727 if (diff_rev2 || diff_date2) 728 { 729 /* special handling for TAG_HEAD */ 730 if (diff_rev2 && strcmp (diff_rev2, TAG_HEAD) == 0) 731 use_rev2 = xstrdup (vers->vn_rcs); 732 else 733 { 734 xvers = Version_TS (finfo, NULL, diff_rev2, diff_date2, 1, 0); 735 if (xvers->vn_rcs != NULL) 736 use_rev2 = xstrdup (xvers->vn_rcs); 737 freevers_ts (&xvers); 738 } 739 740 if (use_rev1 == NULL) 741 { 742 /* The first revision does not exist. If EMPTY_FILES is 743 true, treat this as an added file. Otherwise, warn 744 about the missing tag. */ 745 if (use_rev2 == NULL) 746 return DIFF_SAME; 747 else if (empty_files) 748 return DIFF_ADDED; 749 else if (diff_rev1) 750 error (0, 0, "tag %s is not in file %s", diff_rev1, 751 finfo->fullname); 752 else 753 error (0, 0, "no revision for date %s in file %s", 754 diff_date1, finfo->fullname); 755 return DIFF_ERROR; 756 } 757 758 if (use_rev2 == NULL) 759 { 760 /* The second revision does not exist. If EMPTY_FILES is 761 true, treat this as a removed file. Otherwise warn 762 about the missing tag. */ 763 if (empty_files) 764 return DIFF_REMOVED; 765 else if (diff_rev2) 766 error (0, 0, "tag %s is not in file %s", diff_rev2, 767 finfo->fullname); 768 else 769 error (0, 0, "no revision for date %s in file %s", 770 diff_date2, finfo->fullname); 771 return DIFF_ERROR; 772 } 773 774 /* now, see if we really need to do the diff */ 775 if (strcmp (use_rev1, use_rev2) == 0) 776 return DIFF_SAME; 777 else 778 return DIFF_DIFFERENT; 779 } 780 781 if ((diff_rev1 || diff_date1) && use_rev1 == NULL) 782 { 783 /* The first revision does not exist, and no second revision 784 was given. */ 785 if (empty_files) 786 { 787 if (empty_file == DIFF_REMOVED) 788 return DIFF_SAME; 789 else 790 { 791 #ifdef SERVER_SUPPORT 792 if (user_file_rev && use_rev2 == NULL) 793 use_rev2 = xstrdup (user_file_rev); 794 #endif 795 return DIFF_ADDED; 796 } 797 } 798 else 799 { 800 if (diff_rev1) 801 error (0, 0, "tag %s is not in file %s", diff_rev1, 802 finfo->fullname); 803 else 804 error (0, 0, "no revision for date %s in file %s", 805 diff_date1, finfo->fullname); 806 return DIFF_ERROR; 807 } 808 } 809 810 #ifdef SERVER_SUPPORT 811 if (user_file_rev) 812 { 813 /* drop user_file_rev into first unused use_rev */ 814 if (!use_rev1) 815 use_rev1 = xstrdup (user_file_rev); 816 else if (!use_rev2) 817 use_rev2 = xstrdup (user_file_rev); 818 /* and if not, it wasn't needed anyhow */ 819 user_file_rev = 0; 820 } 821 822 /* now, see if we really need to do the diff */ 823 if (use_rev1 && use_rev2) 824 { 825 if (strcmp (use_rev1, use_rev2) == 0) 826 return DIFF_SAME; 827 else 828 return DIFF_DIFFERENT; 829 } 830 #endif /* SERVER_SUPPORT */ 831 if (use_rev1 == NULL 832 || (vers->vn_user != NULL && strcmp (use_rev1, vers->vn_user) == 0)) 833 { 834 if (strcmp (vers->ts_rcs, vers->ts_user) == 0 && 835 (!(*options) || strcmp (options, vers->options) == 0)) 836 { 837 return DIFF_SAME; 838 } 839 if (use_rev1 == NULL) 840 use_rev1 = xstrdup (vers->vn_user); 841 } 842 843 /* If we already know that the file is being added or removed, 844 then we don't want to do an actual file comparison here. */ 845 if (empty_file != DIFF_DIFFERENT) 846 return empty_file; 847 848 /* 849 * with 0 or 1 -r option specified, run a quick diff to see if we 850 * should bother with it at all. 851 */ 852 tmp = cvs_temp_name (); 853 retcode = RCS_checkout (vers->srcfile, (char *) NULL, use_rev1, 854 (char *) NULL, 855 *options ? options : vers->options, 856 tmp); 857 switch (retcode) 858 { 859 case 0: /* everything ok */ 860 if (xcmp (finfo->file, tmp) == 0) 861 { 862 (void) CVS_UNLINK (tmp); 863 free (tmp); 864 return DIFF_SAME; 865 } 866 break; 867 case -1: /* fork failed */ 868 (void) CVS_UNLINK (tmp); 869 error (1, errno, "fork failed during checkout of %s", 870 vers->srcfile->path); 871 default: 872 break; 873 } 874 (void) CVS_UNLINK (tmp); 875 free (tmp); 876 return DIFF_DIFFERENT; 877 } 878