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