xref: /openbsd/gnu/usr.bin/cvs/src/diff.c (revision 07ea8d15)
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 1.4 kit.
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 static char *diff_rev1, *diff_rev2;
44 static char *diff_date1, *diff_date2;
45 static char *use_rev1, *use_rev2;
46 
47 #ifdef SERVER_SUPPORT
48 /* Revision of the user file, if it is unchanged from something in the
49    repository and we want to use that fact.  */
50 static char *user_file_rev;
51 #endif
52 
53 static char *options;
54 /* FIXME: arbitrary limit (security hole, if the client passes us
55    data which overflows it).  */
56 static char opts[PATH_MAX];
57 static int diff_errors;
58 static int empty_files = 0;
59 
60 /* FIXME: should be documenting all the options here.  They don't
61    perfectly match rcsdiff options (for example, we always support
62    --ifdef and --context, but rcsdiff only does if diff does).  */
63 static const char *const diff_usage[] =
64 {
65     "Usage: %s %s [-lN] [rcsdiff-options]\n",
66     "    [[-r rev1 | -D date1] [-r rev2 | -D date2]] [files...] \n",
67     "\t-l\tLocal directory only, not recursive\n",
68     "\t-D d1\tDiff revision for date against working file.\n",
69     "\t-D d2\tDiff rev1/date1 against date2.\n",
70     "\t-N\tinclude diffs for added and removed files.\n",
71     "\t-r rev1\tDiff revision for rev1 against working file.\n",
72     "\t-r rev2\tDiff rev1/date1 against rev2.\n",
73     NULL
74 };
75 
76 /* I copied this array directly out of diff.c in diffutils 2.7, after
77    removing the following entries, none of which seem relevant to use
78    with CVS:
79      --help
80      --version
81      --recursive
82      --unidirectional-new-file
83      --starting-file
84      --exclude
85      --exclude-from
86      --sdiff-merge-assist
87 
88    I changed the options which take optional arguments (--context and
89    --unified) to return a number rather than a letter, so that the
90    optional argument could be handled more easily.  I changed the
91    --paginate and --brief options to return a number, since -l and -q
92    mean something else to cvs diff.
93 
94    The numbers 129- that appear in the fourth element of some entries
95    tell the big switch in `diff' how to process those options. -- Ian
96 
97    The following options, which diff lists as "An alias, no longer
98    recommended" have been removed: --file-label --entire-new-file
99    --ascii --print.  */
100 
101 static struct option const longopts[] =
102 {
103     {"ignore-blank-lines", 0, 0, 'B'},
104     {"context", 2, 0, 143},
105     {"ifdef", 1, 0, 147},
106     {"show-function-line", 1, 0, 'F'},
107     {"speed-large-files", 0, 0, 'H'},
108     {"ignore-matching-lines", 1, 0, 'I'},
109     {"label", 1, 0, 'L'},
110     {"new-file", 0, 0, 'N'},
111     {"initial-tab", 0, 0, 'T'},
112     {"width", 1, 0, 'W'},
113     {"text", 0, 0, 'a'},
114     {"ignore-space-change", 0, 0, 'b'},
115     {"minimal", 0, 0, 'd'},
116     {"ed", 0, 0, 'e'},
117     {"forward-ed", 0, 0, 'f'},
118     {"ignore-case", 0, 0, 'i'},
119     {"paginate", 0, 0, 144},
120     {"rcs", 0, 0, 'n'},
121     {"show-c-function", 0, 0, 'p'},
122 
123     /* This is a potentially very useful option, except the output is so
124        silly.  It would be much better for it to look like "cvs rdiff -s"
125        which displays all the same info, minus quite a few lines of
126        extraneous garbage.  */
127     {"brief", 0, 0, 145},
128 
129     {"report-identical-files", 0, 0, 's'},
130     {"expand-tabs", 0, 0, 't'},
131     {"ignore-all-space", 0, 0, 'w'},
132     {"side-by-side", 0, 0, 'y'},
133     {"unified", 2, 0, 146},
134     {"left-column", 0, 0, 129},
135     {"suppress-common-lines", 0, 0, 130},
136     {"old-line-format", 1, 0, 132},
137     {"new-line-format", 1, 0, 133},
138     {"unchanged-line-format", 1, 0, 134},
139     {"line-format", 1, 0, 135},
140     {"old-group-format", 1, 0, 136},
141     {"new-group-format", 1, 0, 137},
142     {"unchanged-group-format", 1, 0, 138},
143     {"changed-group-format", 1, 0, 139},
144     {"horizon-lines", 1, 0, 140},
145     {"binary", 0, 0, 142},
146     {0, 0, 0, 0}
147 };
148 
149 int
150 diff (argc, argv)
151     int argc;
152     char **argv;
153 {
154     char tmp[50];
155     int c, err = 0;
156     int local = 0;
157     int which;
158     int option_index;
159 
160     if (argc == -1)
161 	usage (diff_usage);
162 
163     /*
164      * Note that we catch all the valid arguments here, so that we can
165      * intercept the -r arguments for doing revision diffs; and -l/-R for a
166      * non-recursive/recursive diff.
167      */
168 #ifdef SERVER_SUPPORT
169     /* Need to be able to do this command more than once (according to
170        the protocol spec, even if the current client doesn't use it).  */
171     opts[0] = '\0';
172 #endif
173     optind = 1;
174     while ((c = getopt_long (argc, argv,
175 	       "abcdefhilnpstuwy0123456789BHNRTC:D:F:I:L:U:V:W:k:r:",
176 			     longopts, &option_index)) != -1)
177     {
178 	switch (c)
179 	{
180 	    case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
181 	    case 'h': case 'i': case 'n': case 'p': case 's': case 't':
182 	    case 'u': case 'w': case 'y': case '0': case '1': case '2':
183 	    case '3': case '4': case '5': case '6': case '7': case '8':
184 	    case '9': case 'B': case 'H': case 'T':
185 		(void) sprintf (tmp, " -%c", (char) c);
186 		(void) strcat (opts, tmp);
187 		break;
188 	    case 'C': case 'F': case 'I': case 'L': case 'U': case 'V':
189 	    case 'W':
190 		(void) sprintf (tmp, " -%c", (char) c);
191 		strcat (opts, tmp);
192 		strcat (opts, optarg);
193 		break;
194 	    case 147:
195 		/* --ifdef.  */
196 		strcat (opts, " -D");
197 		strcat (opts, optarg);
198 		break;
199 	    case 129: case 130: case 131: case 132: case 133: case 134:
200 	    case 135: case 136: case 137: case 138: case 139: case 140:
201 	    case 141: case 142: case 143: case 144: case 145: case 146:
202 		strcat (opts, " --");
203 		strcat (opts, longopts[option_index].name);
204 		if (longopts[option_index].has_arg == 1
205 		    || (longopts[option_index].has_arg == 2
206 			&& optarg != NULL))
207 		{
208 		    strcat (opts, "=");
209 		    strcat (opts, optarg);
210 		}
211 		break;
212 	    case 'R':
213 		local = 0;
214 		break;
215 	    case 'l':
216 		local = 1;
217 		break;
218 	    case 'k':
219 		if (options)
220 		    free (options);
221 		options = RCS_check_kflag (optarg);
222 		break;
223 	    case 'r':
224 		if (diff_rev2 != NULL || diff_date2 != NULL)
225 		    error (1, 0,
226 		       "no more than two revisions/dates can be specified");
227 		if (diff_rev1 != NULL || diff_date1 != NULL)
228 		    diff_rev2 = optarg;
229 		else
230 		    diff_rev1 = optarg;
231 		break;
232 	    case 'D':
233 		if (diff_rev2 != NULL || diff_date2 != NULL)
234 		    error (1, 0,
235 		       "no more than two revisions/dates can be specified");
236 		if (diff_rev1 != NULL || diff_date1 != NULL)
237 		    diff_date2 = Make_Date (optarg);
238 		else
239 		    diff_date1 = Make_Date (optarg);
240 		break;
241 	    case 'N':
242 		empty_files = 1;
243 		break;
244 	    case '?':
245 	    default:
246 		usage (diff_usage);
247 		break;
248 	}
249     }
250     argc -= optind;
251     argv += optind;
252 
253     /* make sure options is non-null */
254     if (!options)
255 	options = xstrdup ("");
256 
257 #ifdef CLIENT_SUPPORT
258     if (client_active) {
259 	/* We're the client side.  Fire up the remote server.  */
260 	start_server ();
261 
262 	ign_setup ();
263 
264 	if (local)
265 	    send_arg("-l");
266 	if (empty_files)
267 	    send_arg("-N");
268 	send_option_string (opts);
269 	if (diff_rev1)
270 	    option_with_arg ("-r", diff_rev1);
271 	if (diff_date1)
272 	    client_senddate (diff_date1);
273 	if (diff_rev2)
274 	    option_with_arg ("-r", diff_rev2);
275 	if (diff_date2)
276 	    client_senddate (diff_date2);
277 
278 	send_file_names (argc, argv, SEND_EXPAND_WILD);
279 #if 0
280 	/* FIXME: We shouldn't have to send current files to diff two
281 	   revs, but it doesn't work yet and I haven't debugged it.
282 	   So send the files -- it's slower but it works.
283 	   gnu@cygnus.com Apr94 */
284 	/* Send the current files unless diffing two revs from the archive */
285 	if (diff_rev2 == NULL && diff_date2 == NULL)
286 #endif
287 	send_files (argc, argv, local, 0);
288 
289 	send_to_server ("diff\012", 0);
290         err = get_responses_and_close ();
291 	free (options);
292 	return (err);
293     }
294 #endif
295 
296     if (diff_rev1 != NULL)
297 	tag_check_valid (diff_rev1, argc, argv, local, 0, "");
298     if (diff_rev2 != NULL)
299 	tag_check_valid (diff_rev2, argc, argv, local, 0, "");
300 
301     which = W_LOCAL;
302     if (diff_rev1 != NULL || diff_date1 != NULL)
303 	which |= W_REPOS | W_ATTIC;
304 
305     wrap_setup ();
306 
307     /* start the recursion processor */
308     err = start_recursion (diff_fileproc, diff_filesdoneproc, diff_dirproc,
309 			   diff_dirleaveproc, NULL, argc, argv, local,
310 			   which, 0, 1, (char *) NULL, 1);
311 
312     /* clean up */
313     free (options);
314     return (err);
315 }
316 
317 /*
318  * Do a file diff
319  */
320 /* ARGSUSED */
321 static int
322 diff_fileproc (callerdat, finfo)
323     void *callerdat;
324     struct file_info *finfo;
325 {
326     int status, err = 2;		/* 2 == trouble, like rcsdiff */
327     Vers_TS *vers;
328     enum diff_file empty_file = DIFF_DIFFERENT;
329     char *tmp;
330     char *tocvsPath;
331     char fname[PATH_MAX];
332 
333 #ifdef SERVER_SUPPORT
334     user_file_rev = 0;
335 #endif
336     vers = Version_TS (finfo, NULL, NULL, NULL, 1, 0);
337 
338     if (diff_rev2 != NULL || diff_date2 != NULL)
339     {
340 	/* Skip all the following checks regarding the user file; we're
341 	   not using it.  */
342     }
343     else if (vers->vn_user == NULL)
344     {
345 	/* The file does not exist in the working directory.  */
346 	if ((diff_rev1 != NULL || diff_date1 != NULL)
347 	    && vers->srcfile != NULL)
348 	{
349 	    /* The file does exist in the repository.  */
350 	    if (empty_files)
351 		empty_file = DIFF_REMOVED;
352 	    else
353 	    {
354 		int exists;
355 
356 		exists = 0;
357 		/* special handling for TAG_HEAD */
358 		if (diff_rev1 && strcmp (diff_rev1, TAG_HEAD) == 0)
359 		    exists = vers->vn_rcs != NULL;
360 		else
361 		{
362 		    Vers_TS *xvers;
363 
364 		    xvers = Version_TS (finfo, NULL, diff_rev1, diff_date1,
365 					1, 0);
366 		    exists = xvers->vn_rcs != NULL;
367 		    freevers_ts (&xvers);
368 		}
369 		if (exists)
370 		    error (0, 0,
371 			   "%s no longer exists, no comparison available",
372 			   finfo->fullname);
373 		freevers_ts (&vers);
374 		diff_mark_errors (err);
375 		return (err);
376 	    }
377 	}
378 	else
379 	{
380 	    error (0, 0, "I know nothing about %s", finfo->fullname);
381 	    freevers_ts (&vers);
382 	    diff_mark_errors (err);
383 	    return (err);
384 	}
385     }
386     else if (vers->vn_user[0] == '0' && vers->vn_user[1] == '\0')
387     {
388 	if (empty_files)
389 	    empty_file = DIFF_ADDED;
390 	else
391 	{
392 	    error (0, 0, "%s is a new entry, no comparison available",
393 		   finfo->fullname);
394 	    freevers_ts (&vers);
395 	    diff_mark_errors (err);
396 	    return (err);
397 	}
398     }
399     else if (vers->vn_user[0] == '-')
400     {
401 	if (empty_files)
402 	    empty_file = DIFF_REMOVED;
403 	else
404 	{
405 	    error (0, 0, "%s was removed, no comparison available",
406 		   finfo->fullname);
407 	    freevers_ts (&vers);
408 	    diff_mark_errors (err);
409 	    return (err);
410 	}
411     }
412     else
413     {
414 	if (vers->vn_rcs == NULL && vers->srcfile == NULL)
415 	{
416 	    error (0, 0, "cannot find revision control file for %s",
417 		   finfo->fullname);
418 	    freevers_ts (&vers);
419 	    diff_mark_errors (err);
420 	    return (err);
421 	}
422 	else
423 	{
424 	    if (vers->ts_user == NULL)
425 	    {
426 		error (0, 0, "cannot find %s", finfo->fullname);
427 		freevers_ts (&vers);
428 		diff_mark_errors (err);
429 		return (err);
430 	    }
431 #ifdef SERVER_SUPPORT
432 	    else if (!strcmp (vers->ts_user, vers->ts_rcs))
433 	    {
434 		/* The user file matches some revision in the repository
435 		   Diff against the repository (for remote CVS, we might not
436 		   have a copy of the user file around).  */
437 		user_file_rev = vers->vn_user;
438 	    }
439 #endif
440 	}
441     }
442 
443     empty_file = diff_file_nodiff (finfo, vers, empty_file);
444     if (empty_file == DIFF_SAME || empty_file == DIFF_ERROR)
445     {
446 	freevers_ts (&vers);
447 	if (empty_file == DIFF_SAME)
448 	    return (0);
449 	else
450 	{
451 	    diff_mark_errors (err);
452 	    return (err);
453 	}
454     }
455 
456     if (empty_file == DIFF_DIFFERENT)
457     {
458 	int dead1, dead2;
459 
460 	if (use_rev1 == NULL)
461 	    dead1 = 0;
462 	else
463 	    dead1 = RCS_isdead (vers->srcfile, use_rev1);
464 	if (use_rev2 == NULL)
465 	    dead2 = 0;
466 	else
467 	    dead2 = RCS_isdead (vers->srcfile, use_rev2);
468 
469 	if (dead1 && dead2)
470 	{
471 	    freevers_ts (&vers);
472 	    return (0);
473 	}
474 	else if (dead1)
475 	{
476 	    if (empty_files)
477 	        empty_file = DIFF_ADDED;
478 	    else
479 	    {
480 		error (0, 0, "%s is a new entry, no comparison available",
481 		       finfo->fullname);
482 		freevers_ts (&vers);
483 		diff_mark_errors (err);
484 		return (err);
485 	    }
486 	}
487 	else if (dead2)
488 	{
489 	    if (empty_files)
490 		empty_file = DIFF_REMOVED;
491 	    else
492 	    {
493 		error (0, 0, "%s was removed, no comparison available",
494 		       finfo->fullname);
495 		freevers_ts (&vers);
496 		diff_mark_errors (err);
497 		return (err);
498 	    }
499 	}
500     }
501 
502     /* Output an "Index:" line for patch to use */
503     (void) fflush (stdout);
504     (void) printf ("Index: %s\n", finfo->fullname);
505     (void) fflush (stdout);
506 
507     tocvsPath = wrap_tocvs_process_file(finfo->file);
508     if (tocvsPath)
509     {
510 	/* Backup the current version of the file to CVS/,,filename */
511 	sprintf(fname,"%s/%s%s",CVSADM, CVSPREFIX, finfo->file);
512 	if (unlink_file_dir (fname) < 0)
513 	    if (! existence_error (errno))
514 		error (1, errno, "cannot remove %s", finfo->file);
515 	rename_file (finfo->file, fname);
516 	/* Copy the wrapped file to the current directory then go to work */
517 	copy_file (tocvsPath, finfo->file);
518     }
519 
520     if (empty_file == DIFF_ADDED || empty_file == DIFF_REMOVED)
521     {
522 	/* This is file, not fullname, because it is the "Index:" line which
523 	   is supposed to contain the directory.  */
524 	(void) printf ("===================================================================\nRCS file: %s\n",
525 		       finfo->file);
526 	(void) printf ("diff -N %s\n", finfo->file);
527 
528 	if (empty_file == DIFF_ADDED)
529 	{
530 	    if (use_rev2 == NULL)
531 		run_setup ("%s %s %s %s", DIFF, opts, DEVNULL, finfo->file);
532 	    else
533 	    {
534 		int retcode;
535 
536 		tmp = cvs_temp_name ();
537 		retcode = RCS_checkout (vers->srcfile, (char *) NULL,
538 					use_rev2, (char *) NULL,
539 					(*options
540 					 ? options
541 					 : vers->options),
542 					tmp);
543 		if (retcode == -1)
544 		{
545 		    (void) CVS_UNLINK (tmp);
546 		    error (1, errno, "fork failed during checkout of %s",
547 			   vers->srcfile->path);
548 		}
549 		/* FIXME: what if retcode > 0?  */
550 
551 		run_setup ("%s %s %s %s", DIFF, opts, DEVNULL, tmp);
552 	    }
553 	}
554 	else
555 	{
556 	    int retcode;
557 
558 	    tmp = cvs_temp_name ();
559 	    retcode = RCS_checkout (vers->srcfile, (char *) NULL,
560 				    use_rev1, (char *) NULL,
561 				    *options ? options : vers->options,
562 				    tmp);
563 	    if (retcode == -1)
564 	    {
565 		(void) CVS_UNLINK (tmp);
566 		error (1, errno, "fork failed during checkout of %s",
567 		       vers->srcfile->path);
568 	    }
569 	    /* FIXME: what if retcode > 0?  */
570 
571 	    run_setup ("%s %s %s %s", DIFF, opts, tmp, DEVNULL);
572 	}
573     }
574     else
575     {
576 	if (use_rev2)
577 	{
578 	    run_setup ("%s%s -x,v/ %s %s -r%s -r%s", Rcsbin, RCS_DIFF,
579 		       opts, *options ? options : vers->options,
580 		       use_rev1, use_rev2);
581 	}
582 	else
583 	{
584 	    run_setup ("%s%s -x,v/ %s %s -r%s", Rcsbin, RCS_DIFF, opts,
585 		       *options ? options : vers->options, use_rev1);
586 	}
587 	run_arg (vers->srcfile->path);
588     }
589 
590     switch ((status = run_exec (RUN_TTY, RUN_TTY, RUN_TTY,
591 	RUN_REALLY|RUN_COMBINED)))
592     {
593 	case -1:			/* fork failed */
594 	    error (1, errno, "fork failed during rcsdiff of %s",
595 		   vers->srcfile->path);
596 	case 0:				/* everything ok */
597 	    err = 0;
598 	    break;
599 	default:			/* other error */
600 	    err = status;
601 	    break;
602     }
603 
604     if (tocvsPath)
605     {
606 	if (unlink_file_dir (finfo->file) < 0)
607 	    if (! existence_error (errno))
608 		error (1, errno, "cannot remove %s", finfo->file);
609 
610 	rename_file (fname,finfo->file);
611 	if (unlink_file (tocvsPath) < 0)
612 	    error (1, errno, "cannot remove %s", finfo->file);
613     }
614 
615     if (empty_file == DIFF_REMOVED
616 	|| (empty_file == DIFF_ADDED && use_rev2 != NULL))
617     {
618 	(void) CVS_UNLINK (tmp);
619 	free (tmp);
620     }
621 
622     (void) fflush (stdout);
623     freevers_ts (&vers);
624     diff_mark_errors (err);
625     return (err);
626 }
627 
628 /*
629  * Remember the exit status for each file.
630  */
631 static void
632 diff_mark_errors (err)
633     int err;
634 {
635     if (err > diff_errors)
636 	diff_errors = err;
637 }
638 
639 /*
640  * Print a warm fuzzy message when we enter a dir
641  *
642  * Don't try to diff directories that don't exist! -- DW
643  */
644 /* ARGSUSED */
645 static Dtype
646 diff_dirproc (callerdat, dir, pos_repos, update_dir, entries)
647     void *callerdat;
648     char *dir;
649     char *pos_repos;
650     char *update_dir;
651     List *entries;
652 {
653     /* XXX - check for dirs we don't want to process??? */
654 
655     /* YES ... for instance dirs that don't exist!!! -- DW */
656     if (!isdir (dir) )
657       return (R_SKIP_ALL);
658 
659     if (!quiet)
660 	error (0, 0, "Diffing %s", update_dir);
661     return (R_PROCESS);
662 }
663 
664 /*
665  * Concoct the proper exit status - done with files
666  */
667 /* ARGSUSED */
668 static int
669 diff_filesdoneproc (callerdat, err, repos, update_dir, entries)
670     void *callerdat;
671     int err;
672     char *repos;
673     char *update_dir;
674     List *entries;
675 {
676     return (diff_errors);
677 }
678 
679 /*
680  * Concoct the proper exit status - leaving directories
681  */
682 /* ARGSUSED */
683 static int
684 diff_dirleaveproc (callerdat, dir, err, update_dir, entries)
685     void *callerdat;
686     char *dir;
687     int err;
688     char *update_dir;
689     List *entries;
690 {
691     return (diff_errors);
692 }
693 
694 /*
695  * verify that a file is different
696  */
697 static enum diff_file
698 diff_file_nodiff (finfo, vers, empty_file)
699     struct file_info *finfo;
700     Vers_TS *vers;
701     enum diff_file empty_file;
702 {
703     Vers_TS *xvers;
704     char *tmp;
705     int retcode;
706 
707     /* free up any old use_rev* variables and reset 'em */
708     if (use_rev1)
709 	free (use_rev1);
710     if (use_rev2)
711 	free (use_rev2);
712     use_rev1 = use_rev2 = (char *) NULL;
713 
714     if (diff_rev1 || diff_date1)
715     {
716 	/* special handling for TAG_HEAD */
717 	if (diff_rev1 && strcmp (diff_rev1, TAG_HEAD) == 0)
718 	    use_rev1 = xstrdup (vers->vn_rcs);
719 	else
720 	{
721 	    xvers = Version_TS (finfo, NULL, diff_rev1, diff_date1, 1, 0);
722 	    if (xvers->vn_rcs != NULL)
723 		use_rev1 = xstrdup (xvers->vn_rcs);
724 	    freevers_ts (&xvers);
725 	}
726     }
727     if (diff_rev2 || diff_date2)
728     {
729 	/* special handling for TAG_HEAD */
730 	if (diff_rev2 && strcmp (diff_rev2, TAG_HEAD) == 0)
731 	    use_rev2 = xstrdup (vers->vn_rcs);
732 	else
733 	{
734 	    xvers = Version_TS (finfo, NULL, diff_rev2, diff_date2, 1, 0);
735 	    if (xvers->vn_rcs != NULL)
736 		use_rev2 = xstrdup (xvers->vn_rcs);
737 	    freevers_ts (&xvers);
738 	}
739 
740 	if (use_rev1 == NULL)
741 	{
742 	    /* The first revision does not exist.  If EMPTY_FILES is
743                true, treat this as an added file.  Otherwise, warn
744                about the missing tag.  */
745 	    if (use_rev2 == NULL)
746 		return DIFF_SAME;
747 	    else if (empty_files)
748 		return DIFF_ADDED;
749 	    else if (diff_rev1)
750 		error (0, 0, "tag %s is not in file %s", diff_rev1,
751 		       finfo->fullname);
752 	    else
753 		error (0, 0, "no revision for date %s in file %s",
754 		       diff_date1, finfo->fullname);
755 	    return DIFF_ERROR;
756 	}
757 
758 	if (use_rev2 == NULL)
759 	{
760 	    /* The second revision does not exist.  If EMPTY_FILES is
761                true, treat this as a removed file.  Otherwise warn
762                about the missing tag.  */
763 	    if (empty_files)
764 		return DIFF_REMOVED;
765 	    else if (diff_rev2)
766 		error (0, 0, "tag %s is not in file %s", diff_rev2,
767 		       finfo->fullname);
768 	    else
769 		error (0, 0, "no revision for date %s in file %s",
770 		       diff_date2, finfo->fullname);
771 	    return DIFF_ERROR;
772 	}
773 
774 	/* now, see if we really need to do the diff */
775 	if (strcmp (use_rev1, use_rev2) == 0)
776 	    return DIFF_SAME;
777 	else
778 	    return DIFF_DIFFERENT;
779     }
780 
781     if ((diff_rev1 || diff_date1) && use_rev1 == NULL)
782     {
783 	/* The first revision does not exist, and no second revision
784            was given.  */
785 	if (empty_files)
786 	{
787 	    if (empty_file == DIFF_REMOVED)
788 		return DIFF_SAME;
789 	    else
790 	    {
791 #ifdef SERVER_SUPPORT
792 		if (user_file_rev && use_rev2 == NULL)
793 		    use_rev2 = xstrdup (user_file_rev);
794 #endif
795 		return DIFF_ADDED;
796 	    }
797 	}
798 	else
799 	{
800 	    if (diff_rev1)
801 		error (0, 0, "tag %s is not in file %s", diff_rev1,
802 		       finfo->fullname);
803 	    else
804 		error (0, 0, "no revision for date %s in file %s",
805 		       diff_date1, finfo->fullname);
806 	    return DIFF_ERROR;
807 	}
808     }
809 
810 #ifdef SERVER_SUPPORT
811     if (user_file_rev)
812     {
813         /* drop user_file_rev into first unused use_rev */
814         if (!use_rev1)
815 	  use_rev1 = xstrdup (user_file_rev);
816 	else if (!use_rev2)
817 	  use_rev2 = xstrdup (user_file_rev);
818 	/* and if not, it wasn't needed anyhow */
819 	user_file_rev = 0;
820     }
821 
822     /* now, see if we really need to do the diff */
823     if (use_rev1 && use_rev2)
824     {
825 	if (strcmp (use_rev1, use_rev2) == 0)
826 	    return DIFF_SAME;
827 	else
828 	    return DIFF_DIFFERENT;
829     }
830 #endif /* SERVER_SUPPORT */
831     if (use_rev1 == NULL
832 	|| (vers->vn_user != NULL && strcmp (use_rev1, vers->vn_user) == 0))
833     {
834 	if (strcmp (vers->ts_rcs, vers->ts_user) == 0 &&
835 	    (!(*options) || strcmp (options, vers->options) == 0))
836 	{
837 	    return DIFF_SAME;
838 	}
839 	if (use_rev1 == NULL)
840 	    use_rev1 = xstrdup (vers->vn_user);
841     }
842 
843     /* If we already know that the file is being added or removed,
844        then we don't want to do an actual file comparison here.  */
845     if (empty_file != DIFF_DIFFERENT)
846 	return empty_file;
847 
848     /*
849      * with 0 or 1 -r option specified, run a quick diff to see if we
850      * should bother with it at all.
851      */
852     tmp = cvs_temp_name ();
853     retcode = RCS_checkout (vers->srcfile, (char *) NULL, use_rev1,
854 			    (char *) NULL,
855 			    *options ? options : vers->options,
856 			    tmp);
857     switch (retcode)
858     {
859 	case 0:				/* everything ok */
860 	    if (xcmp (finfo->file, tmp) == 0)
861 	    {
862 		(void) CVS_UNLINK (tmp);
863 		free (tmp);
864 		return DIFF_SAME;
865 	    }
866 	    break;
867 	case -1:			/* fork failed */
868 	    (void) CVS_UNLINK (tmp);
869 	    error (1, errno, "fork failed during checkout of %s",
870 		   vers->srcfile->path);
871 	default:
872 	    break;
873     }
874     (void) CVS_UNLINK (tmp);
875     free (tmp);
876     return DIFF_DIFFERENT;
877 }
878