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