PageRenderTime 93ms CodeModel.GetById 17ms app.highlight 67ms RepoModel.GetById 1ms app.codeStats 1ms

/contrib/cvs/src/diff.c

https://bitbucket.org/freebsd/freebsd-head/
C | 1178 lines | 853 code | 89 blank | 236 comment | 285 complexity | beb7b1984df956e56f3e50f1e42cf293 MD5 | raw file
   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}