xref: /openbsd/gnu/usr.bin/cvs/src/diff.c (revision d7942398)
11e72d8d2Sderaadt /*
21e72d8d2Sderaadt  * Copyright (c) 1992, Brian Berliner and Jeff Polk
31e72d8d2Sderaadt  * Copyright (c) 1989-1992, Brian Berliner
41e72d8d2Sderaadt  *
51e72d8d2Sderaadt  * You may distribute under the terms of the GNU General Public License as
62286d8edStholo  * specified in the README file that comes with the CVS source distribution.
71e72d8d2Sderaadt  *
81e72d8d2Sderaadt  * Difference
91e72d8d2Sderaadt  *
101e72d8d2Sderaadt  * Run diff against versions in the repository.  Options that are specified are
111e72d8d2Sderaadt  * passed on directly to "rcsdiff".
121e72d8d2Sderaadt  *
131e72d8d2Sderaadt  * Without any file arguments, runs diff against all the currently modified
141e72d8d2Sderaadt  * files.
151e72d8d2Sderaadt  */
161e72d8d2Sderaadt 
171e72d8d2Sderaadt #include "cvs.h"
181e72d8d2Sderaadt 
1950bf276cStholo enum diff_file
2050bf276cStholo {
2150bf276cStholo     DIFF_ERROR,
2250bf276cStholo     DIFF_ADDED,
2350bf276cStholo     DIFF_REMOVED,
2450bf276cStholo     DIFF_DIFFERENT,
2550bf276cStholo     DIFF_SAME
2650bf276cStholo };
2750bf276cStholo 
2850bf276cStholo static Dtype diff_dirproc PROTO ((void *callerdat, char *dir,
2950bf276cStholo 				  char *pos_repos, char *update_dir,
3050bf276cStholo 				  List *entries));
3150bf276cStholo static int diff_filesdoneproc PROTO ((void *callerdat, int err,
3250bf276cStholo 				      char *repos, char *update_dir,
3350bf276cStholo 				      List *entries));
3450bf276cStholo static int diff_dirleaveproc PROTO ((void *callerdat, char *dir,
3550bf276cStholo 				     int err, char *update_dir,
3650bf276cStholo 				     List *entries));
3750bf276cStholo static enum diff_file diff_file_nodiff PROTO ((struct file_info *finfo,
3850bf276cStholo 					       Vers_TS *vers,
3950bf276cStholo 					       enum diff_file));
4050bf276cStholo static int diff_fileproc PROTO ((void *callerdat, struct file_info *finfo));
411e72d8d2Sderaadt static void diff_mark_errors PROTO((int err));
421e72d8d2Sderaadt 
43c71bc7e2Stholo 
44c71bc7e2Stholo /* Global variables.  Would be cleaner if we just put this stuff in a
45c71bc7e2Stholo    struct like log.c does.  */
46c71bc7e2Stholo 
47c71bc7e2Stholo /* Command line tags, from -r option.  Points into argv.  */
481e72d8d2Sderaadt static char *diff_rev1, *diff_rev2;
49c71bc7e2Stholo /* Command line dates, from -D option.  Malloc'd.  */
501e72d8d2Sderaadt static char *diff_date1, *diff_date2;
511e72d8d2Sderaadt static char *use_rev1, *use_rev2;
522286d8edStholo static int have_rev1_label, have_rev2_label;
531e72d8d2Sderaadt 
541e72d8d2Sderaadt /* Revision of the user file, if it is unchanged from something in the
551e72d8d2Sderaadt    repository and we want to use that fact.  */
561e72d8d2Sderaadt static char *user_file_rev;
571e72d8d2Sderaadt 
581e72d8d2Sderaadt static char *options;
59461cc63eStholo static char *opts;
60461cc63eStholo static size_t opts_allocated = 1;
611e72d8d2Sderaadt static int diff_errors;
621e72d8d2Sderaadt static int empty_files = 0;
631e72d8d2Sderaadt 
6450bf276cStholo /* FIXME: should be documenting all the options here.  They don't
6550bf276cStholo    perfectly match rcsdiff options (for example, we always support
6650bf276cStholo    --ifdef and --context, but rcsdiff only does if diff does).  */
671e72d8d2Sderaadt static const char *const diff_usage[] =
681e72d8d2Sderaadt {
692770ece5Stholo     "Usage: %s %s [-lNR] [rcsdiff-options]\n",
701e72d8d2Sderaadt     "    [[-r rev1 | -D date1] [-r rev2 | -D date2]] [files...] \n",
711e72d8d2Sderaadt     "\t-l\tLocal directory only, not recursive\n",
722770ece5Stholo     "\t-R\tProcess directories recursively.\n",
731e72d8d2Sderaadt     "\t-D d1\tDiff revision for date against working file.\n",
741e72d8d2Sderaadt     "\t-D d2\tDiff rev1/date1 against date2.\n",
751e72d8d2Sderaadt     "\t-N\tinclude diffs for added and removed files.\n",
761e72d8d2Sderaadt     "\t-r rev1\tDiff revision for rev1 against working file.\n",
771e72d8d2Sderaadt     "\t-r rev2\tDiff rev1/date1 against rev2.\n",
78780d15dfStholo     "\t--ifdef=arg\tOutput diffs in ifdef format.\n",
79780d15dfStholo     "(consult the documentation for your diff program for rcsdiff-options.\n",
80780d15dfStholo     "The most popular is -c for context diffs but there are many more).\n",
812286d8edStholo     "(Specify the --help global option for a list of other help options)\n",
821e72d8d2Sderaadt     NULL
831e72d8d2Sderaadt };
841e72d8d2Sderaadt 
8550bf276cStholo /* I copied this array directly out of diff.c in diffutils 2.7, after
8650bf276cStholo    removing the following entries, none of which seem relevant to use
8750bf276cStholo    with CVS:
8850bf276cStholo      --help
8950bf276cStholo      --version
9050bf276cStholo      --recursive
9150bf276cStholo      --unidirectional-new-file
9250bf276cStholo      --starting-file
9350bf276cStholo      --exclude
9450bf276cStholo      --exclude-from
9550bf276cStholo      --sdiff-merge-assist
9650bf276cStholo 
9750bf276cStholo    I changed the options which take optional arguments (--context and
9850bf276cStholo    --unified) to return a number rather than a letter, so that the
9950bf276cStholo    optional argument could be handled more easily.  I changed the
10050bf276cStholo    --paginate and --brief options to return a number, since -l and -q
10150bf276cStholo    mean something else to cvs diff.
10250bf276cStholo 
10350bf276cStholo    The numbers 129- that appear in the fourth element of some entries
10450bf276cStholo    tell the big switch in `diff' how to process those options. -- Ian
10550bf276cStholo 
10650bf276cStholo    The following options, which diff lists as "An alias, no longer
10750bf276cStholo    recommended" have been removed: --file-label --entire-new-file
10850bf276cStholo    --ascii --print.  */
10950bf276cStholo 
11050bf276cStholo static struct option const longopts[] =
11150bf276cStholo {
11250bf276cStholo     {"ignore-blank-lines", 0, 0, 'B'},
11350bf276cStholo     {"context", 2, 0, 143},
1142286d8edStholo     {"ifdef", 1, 0, 131},
11550bf276cStholo     {"show-function-line", 1, 0, 'F'},
11650bf276cStholo     {"speed-large-files", 0, 0, 'H'},
11750bf276cStholo     {"ignore-matching-lines", 1, 0, 'I'},
11850bf276cStholo     {"label", 1, 0, 'L'},
11950bf276cStholo     {"new-file", 0, 0, 'N'},
12043c1707eStholo     {"initial-tab", 0, 0, 'T'},
12150bf276cStholo     {"width", 1, 0, 'W'},
12250bf276cStholo     {"text", 0, 0, 'a'},
12350bf276cStholo     {"ignore-space-change", 0, 0, 'b'},
12450bf276cStholo     {"minimal", 0, 0, 'd'},
12550bf276cStholo     {"ed", 0, 0, 'e'},
12650bf276cStholo     {"forward-ed", 0, 0, 'f'},
12750bf276cStholo     {"ignore-case", 0, 0, 'i'},
12850bf276cStholo     {"paginate", 0, 0, 144},
12950bf276cStholo     {"rcs", 0, 0, 'n'},
13050bf276cStholo     {"show-c-function", 0, 0, 'p'},
13150bf276cStholo 
13250bf276cStholo     /* This is a potentially very useful option, except the output is so
13350bf276cStholo        silly.  It would be much better for it to look like "cvs rdiff -s"
13450bf276cStholo        which displays all the same info, minus quite a few lines of
13550bf276cStholo        extraneous garbage.  */
13650bf276cStholo     {"brief", 0, 0, 145},
13750bf276cStholo 
13850bf276cStholo     {"report-identical-files", 0, 0, 's'},
13950bf276cStholo     {"expand-tabs", 0, 0, 't'},
14050bf276cStholo     {"ignore-all-space", 0, 0, 'w'},
14143c1707eStholo     {"side-by-side", 0, 0, 'y'},
14250bf276cStholo     {"unified", 2, 0, 146},
14350bf276cStholo     {"left-column", 0, 0, 129},
14450bf276cStholo     {"suppress-common-lines", 0, 0, 130},
14550bf276cStholo     {"old-line-format", 1, 0, 132},
14650bf276cStholo     {"new-line-format", 1, 0, 133},
14750bf276cStholo     {"unchanged-line-format", 1, 0, 134},
14850bf276cStholo     {"line-format", 1, 0, 135},
14950bf276cStholo     {"old-group-format", 1, 0, 136},
15050bf276cStholo     {"new-group-format", 1, 0, 137},
15150bf276cStholo     {"unchanged-group-format", 1, 0, 138},
15250bf276cStholo     {"changed-group-format", 1, 0, 139},
15350bf276cStholo     {"horizon-lines", 1, 0, 140},
15450bf276cStholo     {"binary", 0, 0, 142},
15550bf276cStholo     {0, 0, 0, 0}
15650bf276cStholo };
15750bf276cStholo 
1582286d8edStholo /* CVS 1.9 and similar versions seemed to have pretty weird handling
1592286d8edStholo    of -y and -T.  In the cases where it called rcsdiff,
1602286d8edStholo    they would have the meanings mentioned below.  In the cases where it
1612286d8edStholo    called diff, they would have the meanings mentioned in "longopts".
1622286d8edStholo    Noone seems to have missed them, so I think the right thing to do is
1632286d8edStholo    just to remove the options altogether (which I have done).
1642286d8edStholo 
1652286d8edStholo    In the case of -z and -q, "cvs diff" did not accept them even back
1662286d8edStholo    when we called rcsdiff (at least, it hasn't accepted them
1672286d8edStholo    recently).
1682286d8edStholo 
1692286d8edStholo    In comparing rcsdiff to the new CVS implementation, I noticed that
1702286d8edStholo    the following rcsdiff flags are not handled by CVS diff:
1712286d8edStholo 
1722286d8edStholo 	   -y: perform diff even when the requested revisions are the
1732286d8edStholo 		   same revision number
1742286d8edStholo 	   -q: run quietly
1752286d8edStholo 	   -T: preserve modification time on the RCS file
1762286d8edStholo 	   -z: specify timezone for use in file labels
1772286d8edStholo 
1782286d8edStholo    I think these are not really relevant.  -y is undocumented even in
1792286d8edStholo    RCS 5.7, and seems like a minor change at best.  According to RCS
1802286d8edStholo    documentation, -T only applies when a RCS file has been modified
1812286d8edStholo    because of lock changes; doesn't CVS sidestep RCS's entire lock
1822286d8edStholo    structure?  -z seems to be unsupported by CVS diff, and has a
1832286d8edStholo    different meaning as a global option anyway.  (Adding it could be
1842286d8edStholo    a feature, but if it is left out for now, it should not break
1852286d8edStholo    anything.)  For the purposes of producing output, CVS diff appears
1862286d8edStholo    mostly to ignore -q.  Maybe this should be fixed, but I think it's
1872286d8edStholo    a larger issue than the changes included here.  */
1882286d8edStholo 
1891e72d8d2Sderaadt int
diff(argc,argv)1901e72d8d2Sderaadt diff (argc, argv)
1911e72d8d2Sderaadt     int argc;
1921e72d8d2Sderaadt     char **argv;
1931e72d8d2Sderaadt {
1941e72d8d2Sderaadt     char tmp[50];
1951e72d8d2Sderaadt     int c, err = 0;
1961e72d8d2Sderaadt     int local = 0;
1971e72d8d2Sderaadt     int which;
19850bf276cStholo     int option_index;
1991e72d8d2Sderaadt 
2001e72d8d2Sderaadt     if (argc == -1)
2011e72d8d2Sderaadt 	usage (diff_usage);
2021e72d8d2Sderaadt 
2032286d8edStholo     have_rev1_label = have_rev2_label = 0;
2042286d8edStholo 
2051e72d8d2Sderaadt     /*
2061e72d8d2Sderaadt      * Note that we catch all the valid arguments here, so that we can
2071e72d8d2Sderaadt      * intercept the -r arguments for doing revision diffs; and -l/-R for a
2081e72d8d2Sderaadt      * non-recursive/recursive diff.
2091e72d8d2Sderaadt      */
210461cc63eStholo 
211c71bc7e2Stholo     /* Clean out our global variables (multiroot can call us multiple
212c71bc7e2Stholo        times and the server can too, if the client sends several
213c71bc7e2Stholo        diff commands).  */
214461cc63eStholo     if (opts == NULL)
215461cc63eStholo     {
216461cc63eStholo 	opts_allocated = 1;
217461cc63eStholo 	opts = xmalloc (opts_allocated);
218461cc63eStholo     }
2191e72d8d2Sderaadt     opts[0] = '\0';
220c71bc7e2Stholo     diff_rev1 = NULL;
221c71bc7e2Stholo     diff_rev2 = NULL;
222c71bc7e2Stholo     diff_date1 = NULL;
223c71bc7e2Stholo     diff_date2 = NULL;
224461cc63eStholo 
2252770ece5Stholo     optind = 0;
22650bf276cStholo     while ((c = getopt_long (argc, argv,
22743c1707eStholo 	       "+abcdefhilnpstuwy0123456789BHNRTC:D:F:I:L:U:V:W:k:r:",
22850bf276cStholo 			     longopts, &option_index)) != -1)
2291e72d8d2Sderaadt     {
2301e72d8d2Sderaadt 	switch (c)
2311e72d8d2Sderaadt 	{
2321e72d8d2Sderaadt 	    case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
23350bf276cStholo 	    case 'h': case 'i': case 'n': case 'p': case 's': case 't':
23443c1707eStholo 	    case 'u': case 'w': case 'y':
23543c1707eStholo             case '0': case '1': case '2': case '3': case '4': case '5':
23643c1707eStholo             case '6': case '7': case '8': case '9':
23743c1707eStholo 	    case 'B': case 'H': case 'T':
2381e72d8d2Sderaadt 		(void) sprintf (tmp, " -%c", (char) c);
23943c1707eStholo 		allocate_and_strcat (&opts, &opts_allocated, tmp);
2401e72d8d2Sderaadt 		break;
2412286d8edStholo 	    case 'L':
2422286d8edStholo 		if (have_rev1_label++)
2432286d8edStholo 		    if (have_rev2_label++)
2442286d8edStholo 		    {
2452286d8edStholo 			error (0, 0, "extra -L arguments ignored");
2462286d8edStholo 			break;
2472286d8edStholo 		    }
2482286d8edStholo 
24943c1707eStholo 	        allocate_and_strcat (&opts, &opts_allocated, " -L");
25043c1707eStholo 	        allocate_and_strcat (&opts, &opts_allocated, optarg);
2512286d8edStholo 		break;
2522286d8edStholo 	    case 'C': case 'F': case 'I': case 'U': case 'V': case 'W':
25350bf276cStholo 		(void) sprintf (tmp, " -%c", (char) c);
25443c1707eStholo 		allocate_and_strcat (&opts, &opts_allocated, tmp);
25543c1707eStholo 		allocate_and_strcat (&opts, &opts_allocated, optarg);
25650bf276cStholo 		break;
2572286d8edStholo 	    case 131:
25850bf276cStholo 		/* --ifdef.  */
25943c1707eStholo 		allocate_and_strcat (&opts, &opts_allocated, " --ifdef=");
26043c1707eStholo 		allocate_and_strcat (&opts, &opts_allocated, optarg);
26150bf276cStholo 		break;
2622286d8edStholo 	    case 129: case 130:           case 132: case 133: case 134:
26350bf276cStholo 	    case 135: case 136: case 137: case 138: case 139: case 140:
26450bf276cStholo 	    case 141: case 142: case 143: case 144: case 145: case 146:
26543c1707eStholo 		allocate_and_strcat (&opts, &opts_allocated, " --");
26643c1707eStholo 		allocate_and_strcat (&opts, &opts_allocated,
267461cc63eStholo 				     longopts[option_index].name);
26850bf276cStholo 		if (longopts[option_index].has_arg == 1
26950bf276cStholo 		    || (longopts[option_index].has_arg == 2
27050bf276cStholo 			&& optarg != NULL))
27150bf276cStholo 		{
27243c1707eStholo 		    allocate_and_strcat (&opts, &opts_allocated, "=");
27343c1707eStholo 		    allocate_and_strcat (&opts, &opts_allocated, optarg);
27450bf276cStholo 		}
2751e72d8d2Sderaadt 		break;
2761e72d8d2Sderaadt 	    case 'R':
2771e72d8d2Sderaadt 		local = 0;
2781e72d8d2Sderaadt 		break;
2791e72d8d2Sderaadt 	    case 'l':
2801e72d8d2Sderaadt 		local = 1;
2811e72d8d2Sderaadt 		break;
2821e72d8d2Sderaadt 	    case 'k':
2831e72d8d2Sderaadt 		if (options)
2841e72d8d2Sderaadt 		    free (options);
2851e72d8d2Sderaadt 		options = RCS_check_kflag (optarg);
2861e72d8d2Sderaadt 		break;
2871e72d8d2Sderaadt 	    case 'r':
2881e72d8d2Sderaadt 		if (diff_rev2 != NULL || diff_date2 != NULL)
2891e72d8d2Sderaadt 		    error (1, 0,
2901e72d8d2Sderaadt 		       "no more than two revisions/dates can be specified");
2911e72d8d2Sderaadt 		if (diff_rev1 != NULL || diff_date1 != NULL)
2921e72d8d2Sderaadt 		    diff_rev2 = optarg;
2931e72d8d2Sderaadt 		else
2941e72d8d2Sderaadt 		    diff_rev1 = optarg;
2951e72d8d2Sderaadt 		break;
2961e72d8d2Sderaadt 	    case 'D':
2971e72d8d2Sderaadt 		if (diff_rev2 != NULL || diff_date2 != NULL)
2981e72d8d2Sderaadt 		    error (1, 0,
2991e72d8d2Sderaadt 		       "no more than two revisions/dates can be specified");
3001e72d8d2Sderaadt 		if (diff_rev1 != NULL || diff_date1 != NULL)
3011e72d8d2Sderaadt 		    diff_date2 = Make_Date (optarg);
3021e72d8d2Sderaadt 		else
3031e72d8d2Sderaadt 		    diff_date1 = Make_Date (optarg);
3041e72d8d2Sderaadt 		break;
3051e72d8d2Sderaadt 	    case 'N':
3061e72d8d2Sderaadt 		empty_files = 1;
3071e72d8d2Sderaadt 		break;
3081e72d8d2Sderaadt 	    case '?':
3091e72d8d2Sderaadt 	    default:
3101e72d8d2Sderaadt 		usage (diff_usage);
3111e72d8d2Sderaadt 		break;
3121e72d8d2Sderaadt 	}
3131e72d8d2Sderaadt     }
3141e72d8d2Sderaadt     argc -= optind;
3151e72d8d2Sderaadt     argv += optind;
3161e72d8d2Sderaadt 
3171e72d8d2Sderaadt     /* make sure options is non-null */
3181e72d8d2Sderaadt     if (!options)
3191e72d8d2Sderaadt 	options = xstrdup ("");
3201e72d8d2Sderaadt 
3211e72d8d2Sderaadt #ifdef CLIENT_SUPPORT
32243c1707eStholo     if (current_parsed_root->isremote) {
3231e72d8d2Sderaadt 	/* We're the client side.  Fire up the remote server.  */
3241e72d8d2Sderaadt 	start_server ();
3251e72d8d2Sderaadt 
3261e72d8d2Sderaadt 	ign_setup ();
3271e72d8d2Sderaadt 
3281e72d8d2Sderaadt 	if (local)
3291e72d8d2Sderaadt 	    send_arg("-l");
3301e72d8d2Sderaadt 	if (empty_files)
3311e72d8d2Sderaadt 	    send_arg("-N");
3321e72d8d2Sderaadt 	send_option_string (opts);
333780d15dfStholo 	if (options[0] != '\0')
334780d15dfStholo 	    send_arg (options);
3351e72d8d2Sderaadt 	if (diff_rev1)
3361e72d8d2Sderaadt 	    option_with_arg ("-r", diff_rev1);
3371e72d8d2Sderaadt 	if (diff_date1)
3381e72d8d2Sderaadt 	    client_senddate (diff_date1);
3391e72d8d2Sderaadt 	if (diff_rev2)
3401e72d8d2Sderaadt 	    option_with_arg ("-r", diff_rev2);
3411e72d8d2Sderaadt 	if (diff_date2)
3421e72d8d2Sderaadt 	    client_senddate (diff_date2);
3431e72d8d2Sderaadt 
3441e72d8d2Sderaadt 	/* Send the current files unless diffing two revs from the archive */
3451e72d8d2Sderaadt 	if (diff_rev2 == NULL && diff_date2 == NULL)
346b6c02222Stholo 	    send_files (argc, argv, local, 0, 0);
347b6c02222Stholo 	else
348b6c02222Stholo 	    send_files (argc, argv, local, 0, SEND_NO_CONTENTS);
3491e72d8d2Sderaadt 
350c71bc7e2Stholo 	send_file_names (argc, argv, SEND_EXPAND_WILD);
351c71bc7e2Stholo 
35213571821Stholo 	send_to_server ("diff\012", 0);
3531e72d8d2Sderaadt         err = get_responses_and_close ();
3541e72d8d2Sderaadt 	free (options);
355c71bc7e2Stholo 	options = NULL;
3561e72d8d2Sderaadt 	return (err);
3571e72d8d2Sderaadt     }
3581e72d8d2Sderaadt #endif
3591e72d8d2Sderaadt 
36013571821Stholo     if (diff_rev1 != NULL)
36113571821Stholo 	tag_check_valid (diff_rev1, argc, argv, local, 0, "");
36213571821Stholo     if (diff_rev2 != NULL)
36313571821Stholo 	tag_check_valid (diff_rev2, argc, argv, local, 0, "");
36413571821Stholo 
3651e72d8d2Sderaadt     which = W_LOCAL;
36650bf276cStholo     if (diff_rev1 != NULL || diff_date1 != NULL)
3671e72d8d2Sderaadt 	which |= W_REPOS | W_ATTIC;
3681e72d8d2Sderaadt 
3691e72d8d2Sderaadt     wrap_setup ();
3701e72d8d2Sderaadt 
3711e72d8d2Sderaadt     /* start the recursion processor */
3721e72d8d2Sderaadt     err = start_recursion (diff_fileproc, diff_filesdoneproc, diff_dirproc,
37350bf276cStholo 			   diff_dirleaveproc, NULL, argc, argv, local,
37450bf276cStholo 			   which, 0, 1, (char *) NULL, 1);
3751e72d8d2Sderaadt 
3761e72d8d2Sderaadt     /* clean up */
3771e72d8d2Sderaadt     free (options);
378c71bc7e2Stholo     options = NULL;
379c71bc7e2Stholo 
380c71bc7e2Stholo     if (diff_date1 != NULL)
381c71bc7e2Stholo 	free (diff_date1);
382c71bc7e2Stholo     if (diff_date2 != NULL)
383c71bc7e2Stholo 	free (diff_date2);
384c71bc7e2Stholo 
3851e72d8d2Sderaadt     return (err);
3861e72d8d2Sderaadt }
3871e72d8d2Sderaadt 
3881e72d8d2Sderaadt /*
3891e72d8d2Sderaadt  * Do a file diff
3901e72d8d2Sderaadt  */
3911e72d8d2Sderaadt /* ARGSUSED */
3921e72d8d2Sderaadt static int
diff_fileproc(callerdat,finfo)39350bf276cStholo diff_fileproc (callerdat, finfo)
39450bf276cStholo     void *callerdat;
395c26070a5Stholo     struct file_info *finfo;
3961e72d8d2Sderaadt {
3971e72d8d2Sderaadt     int status, err = 2;		/* 2 == trouble, like rcsdiff */
3981e72d8d2Sderaadt     Vers_TS *vers;
39950bf276cStholo     enum diff_file empty_file = DIFF_DIFFERENT;
40050bf276cStholo     char *tmp;
4011e72d8d2Sderaadt     char *tocvsPath;
402461cc63eStholo     char *fname;
40343c1707eStholo     char *label1;
40443c1707eStholo     char *label2;
4051e72d8d2Sderaadt 
4062286d8edStholo     /* Initialize these solely to avoid warnings from gcc -Wall about
4072286d8edStholo        variables that might be used uninitialized.  */
4082286d8edStholo     tmp = NULL;
4092286d8edStholo     fname = NULL;
4102286d8edStholo 
4111e72d8d2Sderaadt     user_file_rev = 0;
41250bf276cStholo     vers = Version_TS (finfo, NULL, NULL, NULL, 1, 0);
4131e72d8d2Sderaadt 
4141e72d8d2Sderaadt     if (diff_rev2 != NULL || diff_date2 != NULL)
4151e72d8d2Sderaadt     {
4161e72d8d2Sderaadt 	/* Skip all the following checks regarding the user file; we're
4171e72d8d2Sderaadt 	   not using it.  */
4181e72d8d2Sderaadt     }
4191e72d8d2Sderaadt     else if (vers->vn_user == NULL)
4201e72d8d2Sderaadt     {
42150bf276cStholo 	/* The file does not exist in the working directory.  */
42250bf276cStholo 	if ((diff_rev1 != NULL || diff_date1 != NULL)
42350bf276cStholo 	    && vers->srcfile != NULL)
42450bf276cStholo 	{
42550bf276cStholo 	    /* The file does exist in the repository.  */
42650bf276cStholo 	    if (empty_files)
42750bf276cStholo 		empty_file = DIFF_REMOVED;
42850bf276cStholo 	    else
42950bf276cStholo 	    {
43050bf276cStholo 		int exists;
43150bf276cStholo 
43250bf276cStholo 		exists = 0;
43350bf276cStholo 		/* special handling for TAG_HEAD */
43450bf276cStholo 		if (diff_rev1 && strcmp (diff_rev1, TAG_HEAD) == 0)
435b6f6614eStholo 		{
436b6f6614eStholo 		    char *head =
437b6f6614eStholo 			(vers->vn_rcs == NULL
438b6f6614eStholo 			 ? NULL
439b6f6614eStholo 			 : RCS_branch_head (vers->srcfile, vers->vn_rcs));
440b6f6614eStholo 		    exists = head != NULL;
441b6f6614eStholo 		    if (head != NULL)
442b6f6614eStholo 			free (head);
443b6f6614eStholo 		}
44450bf276cStholo 		else
44550bf276cStholo 		{
44650bf276cStholo 		    Vers_TS *xvers;
44750bf276cStholo 
44850bf276cStholo 		    xvers = Version_TS (finfo, NULL, diff_rev1, diff_date1,
44950bf276cStholo 					1, 0);
45050bf276cStholo 		    exists = xvers->vn_rcs != NULL;
45150bf276cStholo 		    freevers_ts (&xvers);
45250bf276cStholo 		}
45350bf276cStholo 		if (exists)
45450bf276cStholo 		    error (0, 0,
45550bf276cStholo 			   "%s no longer exists, no comparison available",
45650bf276cStholo 			   finfo->fullname);
45750bf276cStholo 		freevers_ts (&vers);
45850bf276cStholo 		diff_mark_errors (err);
45950bf276cStholo 		return (err);
46050bf276cStholo 	    }
46150bf276cStholo 	}
46250bf276cStholo 	else
46350bf276cStholo 	{
464c2c61682Stholo 	    error (0, 0, "I know nothing about %s", finfo->fullname);
4651e72d8d2Sderaadt 	    freevers_ts (&vers);
4661e72d8d2Sderaadt 	    diff_mark_errors (err);
4671e72d8d2Sderaadt 	    return (err);
4681e72d8d2Sderaadt 	}
46950bf276cStholo     }
4701e72d8d2Sderaadt     else if (vers->vn_user[0] == '0' && vers->vn_user[1] == '\0')
4711e72d8d2Sderaadt     {
4721e72d8d2Sderaadt 	if (empty_files)
4731e72d8d2Sderaadt 	    empty_file = DIFF_ADDED;
4741e72d8d2Sderaadt 	else
4751e72d8d2Sderaadt 	{
476c2c61682Stholo 	    error (0, 0, "%s is a new entry, no comparison available",
477c2c61682Stholo 		   finfo->fullname);
4781e72d8d2Sderaadt 	    freevers_ts (&vers);
4791e72d8d2Sderaadt 	    diff_mark_errors (err);
4801e72d8d2Sderaadt 	    return (err);
4811e72d8d2Sderaadt 	}
4821e72d8d2Sderaadt     }
4831e72d8d2Sderaadt     else if (vers->vn_user[0] == '-')
4841e72d8d2Sderaadt     {
4851e72d8d2Sderaadt 	if (empty_files)
4861e72d8d2Sderaadt 	    empty_file = DIFF_REMOVED;
4871e72d8d2Sderaadt 	else
4881e72d8d2Sderaadt 	{
489c2c61682Stholo 	    error (0, 0, "%s was removed, no comparison available",
490c2c61682Stholo 		   finfo->fullname);
4911e72d8d2Sderaadt 	    freevers_ts (&vers);
4921e72d8d2Sderaadt 	    diff_mark_errors (err);
4931e72d8d2Sderaadt 	    return (err);
4941e72d8d2Sderaadt 	}
4951e72d8d2Sderaadt     }
4961e72d8d2Sderaadt     else
4971e72d8d2Sderaadt     {
4981e72d8d2Sderaadt 	if (vers->vn_rcs == NULL && vers->srcfile == NULL)
4991e72d8d2Sderaadt 	{
500c2c61682Stholo 	    error (0, 0, "cannot find revision control file for %s",
501c2c61682Stholo 		   finfo->fullname);
5021e72d8d2Sderaadt 	    freevers_ts (&vers);
5031e72d8d2Sderaadt 	    diff_mark_errors (err);
5041e72d8d2Sderaadt 	    return (err);
5051e72d8d2Sderaadt 	}
5061e72d8d2Sderaadt 	else
5071e72d8d2Sderaadt 	{
5081e72d8d2Sderaadt 	    if (vers->ts_user == NULL)
5091e72d8d2Sderaadt 	    {
510c2c61682Stholo 		error (0, 0, "cannot find %s", finfo->fullname);
5111e72d8d2Sderaadt 		freevers_ts (&vers);
5121e72d8d2Sderaadt 		diff_mark_errors (err);
5131e72d8d2Sderaadt 		return (err);
5141e72d8d2Sderaadt 	    }
5151e72d8d2Sderaadt 	    else if (!strcmp (vers->ts_user, vers->ts_rcs))
5161e72d8d2Sderaadt 	    {
5171e72d8d2Sderaadt 		/* The user file matches some revision in the repository
5181e72d8d2Sderaadt 		   Diff against the repository (for remote CVS, we might not
5191e72d8d2Sderaadt 		   have a copy of the user file around).  */
5201e72d8d2Sderaadt 		user_file_rev = vers->vn_user;
5211e72d8d2Sderaadt 	    }
5221e72d8d2Sderaadt 	}
5231e72d8d2Sderaadt     }
5241e72d8d2Sderaadt 
52550bf276cStholo     empty_file = diff_file_nodiff (finfo, vers, empty_file);
52650bf276cStholo     if (empty_file == DIFF_SAME || empty_file == DIFF_ERROR)
52750bf276cStholo     {
52850bf276cStholo 	freevers_ts (&vers);
52950bf276cStholo 	if (empty_file == DIFF_SAME)
5302770ece5Stholo 	{
5312770ece5Stholo 	    /* In the server case, would be nice to send a "Checked-in"
5322770ece5Stholo 	       response, so that the client can rewrite its timestamp.
5332770ece5Stholo 	       server_checked_in by itself isn't the right thing (it
5342770ece5Stholo 	       needs a server_register), but I'm not sure what is.
5352770ece5Stholo 	       It isn't clear to me how "cvs status" handles this (that
5362770ece5Stholo 	       is, for a client which sends Modified not Is-modified to
5372770ece5Stholo 	       "cvs status"), but it does.  */
53850bf276cStholo 	    return (0);
5392770ece5Stholo 	}
54050bf276cStholo 	else
54150bf276cStholo 	{
54250bf276cStholo 	    diff_mark_errors (err);
54350bf276cStholo 	    return (err);
54450bf276cStholo 	}
54550bf276cStholo     }
54650bf276cStholo 
54750bf276cStholo     if (empty_file == DIFF_DIFFERENT)
54850bf276cStholo     {
54950bf276cStholo 	int dead1, dead2;
55050bf276cStholo 
55150bf276cStholo 	if (use_rev1 == NULL)
55250bf276cStholo 	    dead1 = 0;
55350bf276cStholo 	else
55450bf276cStholo 	    dead1 = RCS_isdead (vers->srcfile, use_rev1);
55550bf276cStholo 	if (use_rev2 == NULL)
55650bf276cStholo 	    dead2 = 0;
55750bf276cStholo 	else
55850bf276cStholo 	    dead2 = RCS_isdead (vers->srcfile, use_rev2);
55950bf276cStholo 
56050bf276cStholo 	if (dead1 && dead2)
5611e72d8d2Sderaadt 	{
5621e72d8d2Sderaadt 	    freevers_ts (&vers);
5631e72d8d2Sderaadt 	    return (0);
5641e72d8d2Sderaadt 	}
56550bf276cStholo 	else if (dead1)
56650bf276cStholo 	{
56750bf276cStholo 	    if (empty_files)
56850bf276cStholo 	        empty_file = DIFF_ADDED;
56950bf276cStholo 	    else
57050bf276cStholo 	    {
57150bf276cStholo 		error (0, 0, "%s is a new entry, no comparison available",
57250bf276cStholo 		       finfo->fullname);
57350bf276cStholo 		freevers_ts (&vers);
57450bf276cStholo 		diff_mark_errors (err);
57550bf276cStholo 		return (err);
57650bf276cStholo 	    }
57750bf276cStholo 	}
57850bf276cStholo 	else if (dead2)
57950bf276cStholo 	{
58050bf276cStholo 	    if (empty_files)
58150bf276cStholo 		empty_file = DIFF_REMOVED;
58250bf276cStholo 	    else
58350bf276cStholo 	    {
58450bf276cStholo 		error (0, 0, "%s was removed, no comparison available",
58550bf276cStholo 		       finfo->fullname);
58650bf276cStholo 		freevers_ts (&vers);
58750bf276cStholo 		diff_mark_errors (err);
58850bf276cStholo 		return (err);
58950bf276cStholo 	    }
59050bf276cStholo 	}
59150bf276cStholo     }
5921e72d8d2Sderaadt 
5931e72d8d2Sderaadt     /* Output an "Index:" line for patch to use */
5942286d8edStholo     cvs_output ("Index: ", 0);
5952286d8edStholo     cvs_output (finfo->fullname, 0);
5962286d8edStholo     cvs_output ("\n", 1);
5971e72d8d2Sderaadt 
598c26070a5Stholo     tocvsPath = wrap_tocvs_process_file(finfo->file);
5991e72d8d2Sderaadt     if (tocvsPath)
6001e72d8d2Sderaadt     {
6011e72d8d2Sderaadt 	/* Backup the current version of the file to CVS/,,filename */
602461cc63eStholo 	fname = xmalloc (strlen (finfo->file)
603461cc63eStholo 			 + sizeof CVSADM
604461cc63eStholo 			 + sizeof CVSPREFIX
605461cc63eStholo 			 + 10);
606c26070a5Stholo 	sprintf(fname,"%s/%s%s",CVSADM, CVSPREFIX, finfo->file);
6071e72d8d2Sderaadt 	if (unlink_file_dir (fname) < 0)
60813571821Stholo 	    if (! existence_error (errno))
609461cc63eStholo 		error (1, errno, "cannot remove %s", fname);
610c26070a5Stholo 	rename_file (finfo->file, fname);
6111e72d8d2Sderaadt 	/* Copy the wrapped file to the current directory then go to work */
612c26070a5Stholo 	copy_file (tocvsPath, finfo->file);
6131e72d8d2Sderaadt     }
6141e72d8d2Sderaadt 
61543c1707eStholo     /* Set up file labels appropriate for compatibility with the Larry Wall
61643c1707eStholo      * implementation of patch if the user didn't specify.  This is irrelevant
61743c1707eStholo      * according to the POSIX.2 specification.
61843c1707eStholo      */
61943c1707eStholo     label1 = NULL;
62043c1707eStholo     label2 = NULL;
62143c1707eStholo     if (!have_rev1_label)
62243c1707eStholo     {
62343c1707eStholo 	if (empty_file == DIFF_ADDED)
62443c1707eStholo 	    label1 =
62543c1707eStholo 		make_file_label (DEVNULL, NULL, NULL);
62643c1707eStholo 	else
62743c1707eStholo 	    label1 =
62843c1707eStholo 		make_file_label (finfo->fullname, use_rev1, vers ? vers->srcfile : NULL);
62943c1707eStholo     }
63043c1707eStholo 
63143c1707eStholo     if (!have_rev2_label)
63243c1707eStholo     {
63343c1707eStholo 	if (empty_file == DIFF_REMOVED)
63443c1707eStholo 	    label2 =
63543c1707eStholo 		make_file_label (DEVNULL, NULL, NULL);
63643c1707eStholo 	else
63743c1707eStholo 	    label2 =
63843c1707eStholo 		make_file_label (finfo->fullname, use_rev2, vers ? vers->srcfile : NULL);
63943c1707eStholo     }
64043c1707eStholo 
6411e72d8d2Sderaadt     if (empty_file == DIFF_ADDED || empty_file == DIFF_REMOVED)
6421e72d8d2Sderaadt     {
64343c1707eStholo 	/* This is fullname, not file, possibly despite the POSIX.2
64443c1707eStholo 	 * specification, because that's the way all the Larry Wall
64543c1707eStholo 	 * implementations of patch (are there other implementations?) want
64643c1707eStholo 	 * things and the POSIX.2 spec appears to leave room for this.
64743c1707eStholo 	 */
6482286d8edStholo 	cvs_output ("\
6492286d8edStholo ===================================================================\n\
6502286d8edStholo RCS file: ", 0);
65143c1707eStholo 	cvs_output (finfo->fullname, 0);
6522286d8edStholo 	cvs_output ("\n", 1);
6532286d8edStholo 
6542286d8edStholo 	cvs_output ("diff -N ", 0);
65543c1707eStholo 	cvs_output (finfo->fullname, 0);
6562286d8edStholo 	cvs_output ("\n", 1);
6571e72d8d2Sderaadt 
6581e72d8d2Sderaadt 	if (empty_file == DIFF_ADDED)
6591e72d8d2Sderaadt 	{
66050bf276cStholo 	    if (use_rev2 == NULL)
66143c1707eStholo 		status = diff_exec (DEVNULL, finfo->file, label1, label2, opts, RUN_TTY);
66250bf276cStholo 	    else
66350bf276cStholo 	    {
66450bf276cStholo 		int retcode;
66550bf276cStholo 
66650bf276cStholo 		tmp = cvs_temp_name ();
66750bf276cStholo 		retcode = RCS_checkout (vers->srcfile, (char *) NULL,
66850bf276cStholo 					use_rev2, (char *) NULL,
66950bf276cStholo 					(*options
67050bf276cStholo 					 ? options
67150bf276cStholo 					 : vers->options),
672b6c02222Stholo 					tmp, (RCSCHECKOUTPROC) NULL,
673b6c02222Stholo 					(void *) NULL);
6745e617892Stholo 		if (retcode != 0)
67550bf276cStholo 		{
6765e617892Stholo 		    diff_mark_errors (err);
6775e617892Stholo 		    return err;
67850bf276cStholo 		}
67950bf276cStholo 
68043c1707eStholo 		status = diff_exec (DEVNULL, tmp, label1, label2, opts, RUN_TTY);
68150bf276cStholo 	    }
6821e72d8d2Sderaadt 	}
6831e72d8d2Sderaadt 	else
6841e72d8d2Sderaadt 	{
685c26070a5Stholo 	    int retcode;
686c26070a5Stholo 
68750bf276cStholo 	    tmp = cvs_temp_name ();
68850bf276cStholo 	    retcode = RCS_checkout (vers->srcfile, (char *) NULL,
68950bf276cStholo 				    use_rev1, (char *) NULL,
69050bf276cStholo 				    *options ? options : vers->options,
691b6c02222Stholo 				    tmp, (RCSCHECKOUTPROC) NULL,
692b6c02222Stholo 				    (void *) NULL);
6935e617892Stholo 	    if (retcode != 0)
6941e72d8d2Sderaadt 	    {
6955e617892Stholo 		diff_mark_errors (err);
6965e617892Stholo 		return err;
6971e72d8d2Sderaadt 	    }
6981e72d8d2Sderaadt 
69943c1707eStholo 	    status = diff_exec (tmp, DEVNULL, label1, label2, opts, RUN_TTY);
7001e72d8d2Sderaadt 	}
7011e72d8d2Sderaadt     }
7021e72d8d2Sderaadt     else
7031e72d8d2Sderaadt     {
7042286d8edStholo 	status = RCS_exec_rcsdiff (vers->srcfile, opts,
7052286d8edStholo 				   *options ? options : vers->options,
7062286d8edStholo 				   use_rev1, use_rev2,
7072286d8edStholo 				   label1, label2,
7082286d8edStholo 				   finfo->file);
7092286d8edStholo 
7102286d8edStholo 	if (label1) free (label1);
7112286d8edStholo 	if (label2) free (label2);
7121e72d8d2Sderaadt     }
7131e72d8d2Sderaadt 
7142286d8edStholo     switch (status)
7151e72d8d2Sderaadt     {
7161e72d8d2Sderaadt 	case -1:			/* fork failed */
7172286d8edStholo 	    error (1, errno, "fork failed while diffing %s",
7181e72d8d2Sderaadt 		   vers->srcfile->path);
7191e72d8d2Sderaadt 	case 0:				/* everything ok */
7201e72d8d2Sderaadt 	    err = 0;
7211e72d8d2Sderaadt 	    break;
7221e72d8d2Sderaadt 	default:			/* other error */
7231e72d8d2Sderaadt 	    err = status;
7241e72d8d2Sderaadt 	    break;
7251e72d8d2Sderaadt     }
7261e72d8d2Sderaadt 
7271e72d8d2Sderaadt     if (tocvsPath)
7281e72d8d2Sderaadt     {
729c26070a5Stholo 	if (unlink_file_dir (finfo->file) < 0)
73013571821Stholo 	    if (! existence_error (errno))
731c26070a5Stholo 		error (1, errno, "cannot remove %s", finfo->file);
7321e72d8d2Sderaadt 
733c26070a5Stholo 	rename_file (fname, finfo->file);
7341e72d8d2Sderaadt 	if (unlink_file (tocvsPath) < 0)
735461cc63eStholo 	    error (1, errno, "cannot remove %s", tocvsPath);
736461cc63eStholo 	free (fname);
7371e72d8d2Sderaadt     }
7381e72d8d2Sderaadt 
73950bf276cStholo     if (empty_file == DIFF_REMOVED
74050bf276cStholo 	|| (empty_file == DIFF_ADDED && use_rev2 != NULL))
74150bf276cStholo     {
7425e617892Stholo 	if (CVS_UNLINK (tmp) < 0)
7435e617892Stholo 	    error (0, errno, "cannot remove %s", tmp);
74450bf276cStholo 	free (tmp);
74550bf276cStholo     }
7461e72d8d2Sderaadt 
7471e72d8d2Sderaadt     freevers_ts (&vers);
7481e72d8d2Sderaadt     diff_mark_errors (err);
7491e72d8d2Sderaadt     return (err);
7501e72d8d2Sderaadt }
7511e72d8d2Sderaadt 
7521e72d8d2Sderaadt /*
7531e72d8d2Sderaadt  * Remember the exit status for each file.
7541e72d8d2Sderaadt  */
7551e72d8d2Sderaadt static void
diff_mark_errors(err)7561e72d8d2Sderaadt diff_mark_errors (err)
7571e72d8d2Sderaadt     int err;
7581e72d8d2Sderaadt {
7591e72d8d2Sderaadt     if (err > diff_errors)
7601e72d8d2Sderaadt 	diff_errors = err;
7611e72d8d2Sderaadt }
7621e72d8d2Sderaadt 
7631e72d8d2Sderaadt /*
7641e72d8d2Sderaadt  * Print a warm fuzzy message when we enter a dir
76513571821Stholo  *
76613571821Stholo  * Don't try to diff directories that don't exist! -- DW
7671e72d8d2Sderaadt  */
7681e72d8d2Sderaadt /* ARGSUSED */
7691e72d8d2Sderaadt static Dtype
diff_dirproc(callerdat,dir,pos_repos,update_dir,entries)77050bf276cStholo diff_dirproc (callerdat, dir, pos_repos, update_dir, entries)
77150bf276cStholo     void *callerdat;
7721e72d8d2Sderaadt     char *dir;
7731e72d8d2Sderaadt     char *pos_repos;
7741e72d8d2Sderaadt     char *update_dir;
77550bf276cStholo     List *entries;
7761e72d8d2Sderaadt {
7771e72d8d2Sderaadt     /* XXX - check for dirs we don't want to process??? */
77813571821Stholo 
77913571821Stholo     /* YES ... for instance dirs that don't exist!!! -- DW */
78013571821Stholo     if (!isdir (dir))
78113571821Stholo 	return (R_SKIP_ALL);
78213571821Stholo 
7831e72d8d2Sderaadt     if (!quiet)
7841e72d8d2Sderaadt 	error (0, 0, "Diffing %s", update_dir);
7851e72d8d2Sderaadt     return (R_PROCESS);
7861e72d8d2Sderaadt }
7871e72d8d2Sderaadt 
7881e72d8d2Sderaadt /*
7891e72d8d2Sderaadt  * Concoct the proper exit status - done with files
7901e72d8d2Sderaadt  */
7911e72d8d2Sderaadt /* ARGSUSED */
7921e72d8d2Sderaadt static int
diff_filesdoneproc(callerdat,err,repos,update_dir,entries)79350bf276cStholo diff_filesdoneproc (callerdat, err, repos, update_dir, entries)
79450bf276cStholo     void *callerdat;
7951e72d8d2Sderaadt     int err;
7961e72d8d2Sderaadt     char *repos;
7971e72d8d2Sderaadt     char *update_dir;
79850bf276cStholo     List *entries;
7991e72d8d2Sderaadt {
8001e72d8d2Sderaadt     return (diff_errors);
8011e72d8d2Sderaadt }
8021e72d8d2Sderaadt 
8031e72d8d2Sderaadt /*
8041e72d8d2Sderaadt  * Concoct the proper exit status - leaving directories
8051e72d8d2Sderaadt  */
8061e72d8d2Sderaadt /* ARGSUSED */
8071e72d8d2Sderaadt static int
diff_dirleaveproc(callerdat,dir,err,update_dir,entries)80850bf276cStholo diff_dirleaveproc (callerdat, dir, err, update_dir, entries)
80950bf276cStholo     void *callerdat;
8101e72d8d2Sderaadt     char *dir;
8111e72d8d2Sderaadt     int err;
8121e72d8d2Sderaadt     char *update_dir;
81350bf276cStholo     List *entries;
8141e72d8d2Sderaadt {
8151e72d8d2Sderaadt     return (diff_errors);
8161e72d8d2Sderaadt }
8171e72d8d2Sderaadt 
8181e72d8d2Sderaadt /*
81950bf276cStholo  * verify that a file is different
8201e72d8d2Sderaadt  */
82150bf276cStholo static enum diff_file
diff_file_nodiff(finfo,vers,empty_file)82250bf276cStholo diff_file_nodiff (finfo, vers, empty_file)
82350bf276cStholo     struct file_info *finfo;
8241e72d8d2Sderaadt     Vers_TS *vers;
82550bf276cStholo     enum diff_file empty_file;
8261e72d8d2Sderaadt {
8271e72d8d2Sderaadt     Vers_TS *xvers;
828c26070a5Stholo     int retcode;
8291e72d8d2Sderaadt 
8301e72d8d2Sderaadt     /* free up any old use_rev* variables and reset 'em */
8311e72d8d2Sderaadt     if (use_rev1)
8321e72d8d2Sderaadt 	free (use_rev1);
8331e72d8d2Sderaadt     if (use_rev2)
8341e72d8d2Sderaadt 	free (use_rev2);
8351e72d8d2Sderaadt     use_rev1 = use_rev2 = (char *) NULL;
8361e72d8d2Sderaadt 
8371e72d8d2Sderaadt     if (diff_rev1 || diff_date1)
8381e72d8d2Sderaadt     {
8391e72d8d2Sderaadt 	/* special handling for TAG_HEAD */
8401e72d8d2Sderaadt 	if (diff_rev1 && strcmp (diff_rev1, TAG_HEAD) == 0)
841b6f6614eStholo 	    use_rev1 = ((vers->vn_rcs == NULL || vers->srcfile == NULL)
842b6f6614eStholo 			? NULL
843b6f6614eStholo 			: RCS_branch_head (vers->srcfile, vers->vn_rcs));
8441e72d8d2Sderaadt 	else
8451e72d8d2Sderaadt 	{
84650bf276cStholo 	    xvers = Version_TS (finfo, NULL, diff_rev1, diff_date1, 1, 0);
84750bf276cStholo 	    if (xvers->vn_rcs != NULL)
8481e72d8d2Sderaadt 		use_rev1 = xstrdup (xvers->vn_rcs);
8491e72d8d2Sderaadt 	    freevers_ts (&xvers);
8501e72d8d2Sderaadt 	}
8511e72d8d2Sderaadt     }
8521e72d8d2Sderaadt     if (diff_rev2 || diff_date2)
8531e72d8d2Sderaadt     {
8541e72d8d2Sderaadt 	/* special handling for TAG_HEAD */
8551e72d8d2Sderaadt 	if (diff_rev2 && strcmp (diff_rev2, TAG_HEAD) == 0)
856b6f6614eStholo 	    use_rev2 = ((vers->vn_rcs == NULL || vers->srcfile == NULL)
857b6f6614eStholo 			? NULL
858b6f6614eStholo 			: RCS_branch_head (vers->srcfile, vers->vn_rcs));
8591e72d8d2Sderaadt 	else
8601e72d8d2Sderaadt 	{
86150bf276cStholo 	    xvers = Version_TS (finfo, NULL, diff_rev2, diff_date2, 1, 0);
86250bf276cStholo 	    if (xvers->vn_rcs != NULL)
8631e72d8d2Sderaadt 		use_rev2 = xstrdup (xvers->vn_rcs);
8641e72d8d2Sderaadt 	    freevers_ts (&xvers);
8651e72d8d2Sderaadt 	}
8661e72d8d2Sderaadt 
86750bf276cStholo 	if (use_rev1 == NULL)
86850bf276cStholo 	{
86950bf276cStholo 	    /* The first revision does not exist.  If EMPTY_FILES is
87050bf276cStholo                true, treat this as an added file.  Otherwise, warn
87150bf276cStholo                about the missing tag.  */
872*d7942398Ssturm 	    if (use_rev2 == NULL || RCS_isdead( vers->srcfile, use_rev2 ) )
873780d15dfStholo 		/* At least in the case where DIFF_REV1 and DIFF_REV2
874780d15dfStholo 		   are both numeric, we should be returning some kind
875780d15dfStholo 		   of error (see basicb-8a0 in testsuite).  The symbolic
876780d15dfStholo 		   case may be more complicated.  */
87750bf276cStholo 		return DIFF_SAME;
87850bf276cStholo 	    else if (empty_files)
87950bf276cStholo 		return DIFF_ADDED;
88050bf276cStholo 	    else if (diff_rev1)
88150bf276cStholo 		error (0, 0, "tag %s is not in file %s", diff_rev1,
88250bf276cStholo 		       finfo->fullname);
88350bf276cStholo 	    else
88450bf276cStholo 		error (0, 0, "no revision for date %s in file %s",
88550bf276cStholo 		       diff_date1, finfo->fullname);
88650bf276cStholo 	    return DIFF_ERROR;
88750bf276cStholo 	}
88850bf276cStholo 
88950bf276cStholo 	if (use_rev2 == NULL)
89050bf276cStholo 	{
89150bf276cStholo 	    /* The second revision does not exist.  If EMPTY_FILES is
89250bf276cStholo                true, treat this as a removed file.  Otherwise warn
89350bf276cStholo                about the missing tag.  */
89450bf276cStholo 	    if (empty_files)
89550bf276cStholo 		return DIFF_REMOVED;
89650bf276cStholo 	    else if (diff_rev2)
89750bf276cStholo 		error (0, 0, "tag %s is not in file %s", diff_rev2,
89850bf276cStholo 		       finfo->fullname);
89950bf276cStholo 	    else
90050bf276cStholo 		error (0, 0, "no revision for date %s in file %s",
90150bf276cStholo 		       diff_date2, finfo->fullname);
90250bf276cStholo 	    return DIFF_ERROR;
90350bf276cStholo 	}
90450bf276cStholo 
9051e72d8d2Sderaadt 	/* now, see if we really need to do the diff */
90650bf276cStholo 	if (strcmp (use_rev1, use_rev2) == 0)
90750bf276cStholo 	    return DIFF_SAME;
90850bf276cStholo 	else
90950bf276cStholo 	    return DIFF_DIFFERENT;
91050bf276cStholo     }
91150bf276cStholo 
91250bf276cStholo     if ((diff_rev1 || diff_date1) && use_rev1 == NULL)
91350bf276cStholo     {
91450bf276cStholo 	/* The first revision does not exist, and no second revision
91550bf276cStholo            was given.  */
91650bf276cStholo 	if (empty_files)
91750bf276cStholo 	{
91850bf276cStholo 	    if (empty_file == DIFF_REMOVED)
91950bf276cStholo 		return DIFF_SAME;
92050bf276cStholo 	    else
92150bf276cStholo 	    {
92250bf276cStholo 		if (user_file_rev && use_rev2 == NULL)
92350bf276cStholo 		    use_rev2 = xstrdup (user_file_rev);
92450bf276cStholo 		return DIFF_ADDED;
9251e72d8d2Sderaadt 	    }
9261e72d8d2Sderaadt 	}
92750bf276cStholo 	else
92850bf276cStholo 	{
92950bf276cStholo 	    if (diff_rev1)
93050bf276cStholo 		error (0, 0, "tag %s is not in file %s", diff_rev1,
93150bf276cStholo 		       finfo->fullname);
93250bf276cStholo 	    else
93350bf276cStholo 		error (0, 0, "no revision for date %s in file %s",
93450bf276cStholo 		       diff_date1, finfo->fullname);
93550bf276cStholo 	    return DIFF_ERROR;
93650bf276cStholo 	}
93750bf276cStholo     }
93850bf276cStholo 
9391e72d8d2Sderaadt     if (user_file_rev)
9401e72d8d2Sderaadt     {
9411e72d8d2Sderaadt         /* drop user_file_rev into first unused use_rev */
9421e72d8d2Sderaadt         if (!use_rev1)
9431e72d8d2Sderaadt 	    use_rev1 = xstrdup (user_file_rev);
9441e72d8d2Sderaadt 	else if (!use_rev2)
9451e72d8d2Sderaadt 	    use_rev2 = xstrdup (user_file_rev);
9461e72d8d2Sderaadt 	/* and if not, it wasn't needed anyhow */
9471e72d8d2Sderaadt 	user_file_rev = 0;
9481e72d8d2Sderaadt     }
9491e72d8d2Sderaadt 
9501e72d8d2Sderaadt     /* now, see if we really need to do the diff */
9511e72d8d2Sderaadt     if (use_rev1 && use_rev2)
9521e72d8d2Sderaadt     {
95350bf276cStholo 	if (strcmp (use_rev1, use_rev2) == 0)
95450bf276cStholo 	    return DIFF_SAME;
95550bf276cStholo 	else
95650bf276cStholo 	    return DIFF_DIFFERENT;
9571e72d8d2Sderaadt     }
958461cc63eStholo 
95950bf276cStholo     if (use_rev1 == NULL
96050bf276cStholo 	|| (vers->vn_user != NULL && strcmp (use_rev1, vers->vn_user) == 0))
9611e72d8d2Sderaadt     {
962461cc63eStholo 	if (empty_file == DIFF_DIFFERENT
963461cc63eStholo 	    && vers->ts_user != NULL
964461cc63eStholo 	    && strcmp (vers->ts_rcs, vers->ts_user) == 0
965461cc63eStholo 	    && (!(*options) || strcmp (options, vers->options) == 0))
9661e72d8d2Sderaadt 	{
96750bf276cStholo 	    return DIFF_SAME;
9681e72d8d2Sderaadt 	}
969461cc63eStholo 	if (use_rev1 == NULL
970461cc63eStholo 	    && (vers->vn_user[0] != '0' || vers->vn_user[1] != '\0'))
971461cc63eStholo 	{
972461cc63eStholo 	    if (vers->vn_user[0] == '-')
973461cc63eStholo 		use_rev1 = xstrdup (vers->vn_user + 1);
974461cc63eStholo 	    else
9751e72d8d2Sderaadt 		use_rev1 = xstrdup (vers->vn_user);
9761e72d8d2Sderaadt 	}
977461cc63eStholo     }
9781e72d8d2Sderaadt 
97950bf276cStholo     /* If we already know that the file is being added or removed,
98050bf276cStholo        then we don't want to do an actual file comparison here.  */
98150bf276cStholo     if (empty_file != DIFF_DIFFERENT)
98250bf276cStholo 	return empty_file;
98350bf276cStholo 
9841e72d8d2Sderaadt     /*
9851e72d8d2Sderaadt      * with 0 or 1 -r option specified, run a quick diff to see if we
9861e72d8d2Sderaadt      * should bother with it at all.
9871e72d8d2Sderaadt      */
988b6c02222Stholo 
989b6c02222Stholo     retcode = RCS_cmp_file (vers->srcfile, use_rev1,
99050bf276cStholo 			    *options ? options : vers->options,
991b6c02222Stholo 			    finfo->file);
992b6c02222Stholo 
993b6c02222Stholo     return retcode == 0 ? DIFF_SAME : DIFF_DIFFERENT;
9941e72d8d2Sderaadt }
995