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