2 * Copyright (c) 1992, Brian Berliner and Jeff Polk
3 * Copyright (c) 1989-1992, Brian Berliner
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.
10 * Run diff against versions in the repository. Options that are specified are
11 * passed on directly to "rcsdiff".
13 * Without any file arguments, runs diff against all the currently modified
28 static Dtype diff_dirproc (void *callerdat, const char *dir,
29 const char *pos_repos, const char *update_dir,
31 static int diff_filesdoneproc (void *callerdat, int err,
32 const char *repos, const char *update_dir,
34 static int diff_dirleaveproc (void *callerdat, const char *dir,
35 int err, const char *update_dir,
37 static enum diff_file diff_file_nodiff (struct file_info *finfo, Vers_TS *vers,
38 enum diff_file, char **rev1_cache );
39 static int diff_fileproc (void *callerdat, struct file_info *finfo);
40 static void diff_mark_errors (int err);
43 /* Global variables. Would be cleaner if we just put this stuff in a
44 struct like log.c does. */
46 /* Command line tags, from -r option. Points into argv. */
47 static char *diff_rev1, *diff_rev2;
48 /* Command line dates, from -D option. Malloc'd. */
49 static char *diff_date1, *diff_date2;
50 static char *use_rev1, *use_rev2;
51 static int have_rev1_label, have_rev2_label;
53 /* Revision of the user file, if it is unchanged from something in the
54 repository and we want to use that fact. */
55 static char *user_file_rev;
59 static size_t opts_allocated = 1;
60 static int diff_errors;
61 static int empty_files = 0;
63 static const char *const diff_usage[] =
65 "Usage: %s %s [-lR] [-k kopt] [format_options]\n",
66 " [[-r rev1 | -D date1] [-r rev2 | -D date2]] [files...] \n",
67 "\t-l\tLocal directory only, not recursive\n",
68 "\t-R\tProcess directories recursively.\n",
69 "\t-k kopt\tSpecify keyword expansion mode.\n",
70 "\t-D d1\tDiff revision for date against working file.\n",
71 "\t-D d2\tDiff rev1/date1 against date2.\n",
72 "\t-r rev1\tDiff revision for rev1 against working file.\n",
73 "\t-r rev2\tDiff rev1/date1 against rev2.\n",
74 "\nformat_options:\n",
75 " -i --ignore-case Consider upper- and lower-case to be the same.\n",
76 " -w --ignore-all-space Ignore all white space.\n",
77 " -b --ignore-space-change Ignore changes in the amount of white space.\n",
78 " -B --ignore-blank-lines Ignore changes whose lines are all blank.\n",
79 " -I RE --ignore-matching-lines=RE Ignore changes whose lines all match RE.\n",
80 " --binary Read and write data in binary mode.\n",
81 " -a --text Treat all files as text.\n\n",
82 " -c -C NUM --context[=NUM] Output NUM (default 2) lines of copied context.\n",
83 " -u -U NUM --unified[=NUM] Output NUM (default 2) lines of unified context.\n",
84 " -NUM Use NUM context lines.\n",
85 " -L LABEL --label LABEL Use LABEL instead of file name.\n",
86 " -p --show-c-function Show which C function each change is in.\n",
87 " -F RE --show-function-line=RE Show the most recent line matching RE.\n",
88 " --brief Output only whether files differ.\n",
89 " -e --ed Output an ed script.\n",
90 " -f --forward-ed Output something like an ed script in forward order.\n",
91 " -n --rcs Output an RCS format diff.\n",
92 " -y --side-by-side Output in two columns.\n",
93 " -W NUM --width=NUM Output at most NUM (default 130) characters per line.\n",
94 " --left-column Output only the left column of common lines.\n",
95 " --suppress-common-lines Do not output common lines.\n",
96 " --ifdef=NAME Output merged file to show `#ifdef NAME' diffs.\n",
97 " --GTYPE-group-format=GFMT Similar, but format GTYPE input groups with GFMT.\n",
98 " --line-format=LFMT Similar, but format all input lines with LFMT.\n",
99 " --LTYPE-line-format=LFMT Similar, but format LTYPE input lines with LFMT.\n",
100 " LTYPE is `old', `new', or `unchanged'. GTYPE is LTYPE or `changed'.\n",
101 " GFMT may contain:\n",
102 " %%< lines from FILE1\n",
103 " %%> lines from FILE2\n",
104 " %%= lines common to FILE1 and FILE2\n",
105 " %%[-][WIDTH][.[PREC]]{doxX}LETTER printf-style spec for LETTER\n",
106 " LETTERs are as follows for new group, lower case for old group:\n",
107 " F first line number\n",
108 " L last line number\n",
109 " N number of lines = L-F+1\n",
112 " LFMT may contain:\n",
113 " %%L contents of line\n",
114 " %%l contents of line, excluding any trailing newline\n",
115 " %%[-][WIDTH][.[PREC]]{doxX}n printf-style spec for input line number\n",
116 " Either GFMT or LFMT may contain:\n",
118 " %%c'C' the single character C\n",
119 " %%c'\\OOO' the character with octal code OOO\n\n",
120 " -t --expand-tabs Expand tabs to spaces in output.\n",
121 " -T --initial-tab Make tabs line up by prepending a tab.\n\n",
122 " -N --new-file Treat absent files as empty.\n",
123 " -s --report-identical-files Report when two files are the same.\n",
124 " --horizon-lines=NUM Keep NUM lines of the common prefix and suffix.\n",
125 " -d --minimal Try hard to find a smaller set of changes.\n",
126 " -H --speed-large-files Assume large files and many scattered small changes.\n",
127 "\n(Specify the --help global option for a list of other help options)\n",
131 /* I copied this array directly out of diff.c in diffutils 2.7, after
132 removing the following entries, none of which seem relevant to use
137 --unidirectional-new-file (-P)
142 --paginate (-l) (doesn't work with library callbacks)
144 I changed the options which take optional arguments (--context and
145 --unified) to return a number rather than a letter, so that the
146 optional argument could be handled more easily. I changed the
147 --brief and --ifdef options to return numbers, since -q and -D mean
148 something else to cvs diff.
150 The numbers 129- that appear in the fourth element of some entries
151 tell the big switch in `diff' how to process those options. -- Ian
153 The following options, which diff lists as "An alias, no longer
154 recommended" have been removed: --file-label --entire-new-file
157 static struct option const longopts[] =
159 {"ignore-blank-lines", 0, 0, 'B'},
160 {"context", 2, 0, 143},
161 {"ifdef", 1, 0, 131},
162 {"show-function-line", 1, 0, 'F'},
163 {"speed-large-files", 0, 0, 'H'},
164 {"ignore-matching-lines", 1, 0, 'I'},
165 {"label", 1, 0, 'L'},
166 {"new-file", 0, 0, 'N'},
167 {"initial-tab", 0, 0, 'T'},
168 {"width", 1, 0, 'W'},
170 {"ignore-space-change", 0, 0, 'b'},
171 {"minimal", 0, 0, 'd'},
173 {"forward-ed", 0, 0, 'f'},
174 {"ignore-case", 0, 0, 'i'},
176 {"show-c-function", 0, 0, 'p'},
178 /* This is a potentially very useful option, except the output is so
179 silly. It would be much better for it to look like "cvs rdiff -s"
180 which displays all the same info, minus quite a few lines of
181 extraneous garbage. */
182 {"brief", 0, 0, 145},
184 {"report-identical-files", 0, 0, 's'},
185 {"expand-tabs", 0, 0, 't'},
186 {"ignore-all-space", 0, 0, 'w'},
187 {"side-by-side", 0, 0, 'y'},
188 {"unified", 2, 0, 146},
189 {"left-column", 0, 0, 129},
190 {"suppress-common-lines", 0, 0, 130},
191 {"old-line-format", 1, 0, 132},
192 {"new-line-format", 1, 0, 133},
193 {"unchanged-line-format", 1, 0, 134},
194 {"line-format", 1, 0, 135},
195 {"old-group-format", 1, 0, 136},
196 {"new-group-format", 1, 0, 137},
197 {"unchanged-group-format", 1, 0, 138},
198 {"changed-group-format", 1, 0, 139},
199 {"horizon-lines", 1, 0, 140},
200 {"binary", 0, 0, 142},
204 /* CVS 1.9 and similar versions seemed to have pretty weird handling
205 of -y and -T. In the cases where it called rcsdiff,
206 they would have the meanings mentioned below. In the cases where it
207 called diff, they would have the meanings mentioned in "longopts".
208 Noone seems to have missed them, so I think the right thing to do is
209 just to remove the options altogether (which I have done).
211 In the case of -z and -q, "cvs diff" did not accept them even back
212 when we called rcsdiff (at least, it hasn't accepted them
215 In comparing rcsdiff to the new CVS implementation, I noticed that
216 the following rcsdiff flags are not handled by CVS diff:
218 -y: perform diff even when the requested revisions are the
221 -T: preserve modification time on the RCS file
222 -z: specify timezone for use in file labels
224 I think these are not really relevant. -y is undocumented even in
225 RCS 5.7, and seems like a minor change at best. According to RCS
226 documentation, -T only applies when a RCS file has been modified
227 because of lock changes; doesn't CVS sidestep RCS's entire lock
228 structure? -z seems to be unsupported by CVS diff, and has a
229 different meaning as a global option anyway. (Adding it could be
230 a feature, but if it is left out for now, it should not break
231 anything.) For the purposes of producing output, CVS diff appears
232 mostly to ignore -q. Maybe this should be fixed, but I think it's
233 a larger issue than the changes included here. */
236 diff (int argc, char **argv)
247 have_rev1_label = have_rev2_label = 0;
250 * Note that we catch all the valid arguments here, so that we can
251 * intercept the -r arguments for doing revision diffs; and -l/-R for a
252 * non-recursive/recursive diff.
255 /* Clean out our global variables (multiroot can call us multiple
256 times and the server can too, if the client sends several
261 opts = xmalloc (opts_allocated);
270 /* FIXME: This should really be allocating an argv to be passed to diff
271 * later rather than strcatting onto the opts variable. We have some
272 * handling routines that can already handle most of the argc/argv
273 * maintenance for us and currently, if anyone were to attempt to pass a
274 * quoted string in here, it would be split on spaces and tabs on its way
277 while ((c = getopt_long (argc, argv,
278 "+abcdefhilnpstuwy0123456789BHNRTC:D:F:I:L:U:W:k:r:",
279 longopts, &option_index)) != -1)
284 xrealloc_and_strcat (&opts, &opts_allocated, " --side-by-side");
286 case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
287 case 'h': case 'i': case 'n': case 'p': case 's': case 't':
289 case '0': case '1': case '2': case '3': case '4': case '5':
290 case '6': case '7': case '8': case '9':
291 case 'B': case 'H': case 'T':
292 (void) sprintf (tmp, " -%c", (char) c);
293 xrealloc_and_strcat (&opts, &opts_allocated, tmp);
296 if (have_rev1_label++)
297 if (have_rev2_label++)
299 error (0, 0, "extra -L arguments ignored");
303 xrealloc_and_strcat (&opts, &opts_allocated, " -L");
304 xrealloc_and_strcat (&opts, &opts_allocated, optarg);
306 case 'C': case 'F': case 'I': case 'U': case 'W':
307 (void) sprintf (tmp, " -%c", (char) c);
308 xrealloc_and_strcat (&opts, &opts_allocated, tmp);
309 xrealloc_and_strcat (&opts, &opts_allocated, optarg);
313 xrealloc_and_strcat (&opts, &opts_allocated, " --ifdef=");
314 xrealloc_and_strcat (&opts, &opts_allocated, optarg);
316 case 129: case 130: case 132: case 133: case 134:
317 case 135: case 136: case 137: case 138: case 139: case 140:
318 case 141: case 142: case 143: case 145: case 146:
319 xrealloc_and_strcat (&opts, &opts_allocated, " --");
320 xrealloc_and_strcat (&opts, &opts_allocated,
321 longopts[option_index].name);
322 if (longopts[option_index].has_arg == 1
323 || (longopts[option_index].has_arg == 2
326 xrealloc_and_strcat (&opts, &opts_allocated, "=");
327 xrealloc_and_strcat (&opts, &opts_allocated, optarg);
339 options = RCS_check_kflag (optarg);
342 if (diff_rev2 != NULL || diff_date2 != NULL)
344 "no more than two revisions/dates can be specified");
345 if (diff_rev1 != NULL || diff_date1 != NULL)
351 if (diff_rev2 != NULL || diff_date2 != NULL)
353 "no more than two revisions/dates can be specified");
354 if (diff_rev1 != NULL || diff_date1 != NULL)
355 diff_date2 = Make_Date (optarg);
357 diff_date1 = Make_Date (optarg);
371 /* make sure options is non-null */
373 options = xstrdup ("");
375 #ifdef CLIENT_SUPPORT
376 if (current_parsed_root->isremote) {
377 /* We're the client side. Fire up the remote server. */
386 send_option_string (opts);
387 if (options[0] != '\0')
390 option_with_arg ("-r", diff_rev1);
392 client_senddate (diff_date1);
394 option_with_arg ("-r", diff_rev2);
396 client_senddate (diff_date2);
399 /* Send the current files unless diffing two revs from the archive */
400 if (diff_rev2 == NULL && diff_date2 == NULL)
401 send_files (argc, argv, local, 0, 0);
403 send_files (argc, argv, local, 0, SEND_NO_CONTENTS);
405 send_file_names (argc, argv, SEND_EXPAND_WILD);
407 send_to_server ("diff\012", 0);
408 err = get_responses_and_close ();
415 if (diff_rev1 != NULL)
416 tag_check_valid (diff_rev1, argc, argv, local, 0, "");
417 if (diff_rev2 != NULL)
418 tag_check_valid (diff_rev2, argc, argv, local, 0, "");
421 if (diff_rev1 != NULL || diff_date1 != NULL)
422 which |= W_REPOS | W_ATTIC;
426 /* start the recursion processor */
427 err = start_recursion
428 ( diff_fileproc, diff_filesdoneproc, diff_dirproc,
429 diff_dirleaveproc, NULL, argc, argv, local,
430 which, 0, CVS_LOCK_READ, (char *) NULL, 1, (char *) NULL );
436 if (diff_date1 != NULL)
438 if (diff_date2 != NULL)
449 diff_fileproc (void *callerdat, struct file_info *finfo)
451 int status, err = 2; /* 2 == trouble, like rcsdiff */
453 enum diff_file empty_file = DIFF_DIFFERENT;
455 char *tocvsPath = NULL;
459 char *rev1_cache = NULL;
462 vers = Version_TS (finfo, NULL, NULL, NULL, 1, 0);
464 if (diff_rev2 != NULL || diff_date2 != NULL)
466 /* Skip all the following checks regarding the user file; we're
469 else if (vers->vn_user == NULL)
471 /* The file does not exist in the working directory. */
472 if ((diff_rev1 != NULL || diff_date1 != NULL)
473 && vers->srcfile != NULL)
475 /* The file does exist in the repository. */
477 empty_file = DIFF_REMOVED;
483 /* special handling for TAG_HEAD */
484 if (diff_rev1 && strcmp (diff_rev1, TAG_HEAD) == 0)
487 (vers->vn_rcs == NULL
489 : RCS_branch_head (vers->srcfile, vers->vn_rcs));
490 exists = head != NULL && !RCS_isdead(vers->srcfile, head);
498 xvers = Version_TS (finfo, NULL, diff_rev1, diff_date1,
500 exists = xvers->vn_rcs != NULL && !RCS_isdead(xvers->srcfile, xvers->vn_rcs);
501 freevers_ts (&xvers);
505 "%s no longer exists, no comparison available",
512 error (0, 0, "I know nothing about %s", finfo->fullname);
516 else if (vers->vn_user[0] == '0' && vers->vn_user[1] == '\0')
518 /* The file was added locally. */
521 if (vers->srcfile != NULL)
523 /* The file does exist in the repository. */
525 if ((diff_rev1 != NULL || diff_date1 != NULL))
527 /* special handling for TAG_HEAD */
528 if (diff_rev1 && strcmp (diff_rev1, TAG_HEAD) == 0)
531 (vers->vn_rcs == NULL
533 : RCS_branch_head (vers->srcfile, vers->vn_rcs));
534 exists = head != NULL && !RCS_isdead(vers->srcfile, head);
542 xvers = Version_TS (finfo, NULL, diff_rev1, diff_date1,
544 exists = xvers->vn_rcs != NULL
545 && !RCS_isdead (xvers->srcfile, xvers->vn_rcs);
546 freevers_ts (&xvers);
551 /* The file was added locally, but an RCS archive exists. Our
552 * base revision must be dead.
554 /* No need to set, exists = 0, here. That's the default. */
559 /* If we got here, then either the RCS archive does not exist or
560 * the relevant revision is dead.
563 empty_file = DIFF_ADDED;
566 error (0, 0, "%s is a new entry, no comparison available",
572 else if (vers->vn_user[0] == '-')
575 empty_file = DIFF_REMOVED;
578 error (0, 0, "%s was removed, no comparison available",
585 if (vers->vn_rcs == NULL && vers->srcfile == NULL)
587 error (0, 0, "cannot find revision control file for %s",
593 if (vers->ts_user == NULL)
595 error (0, 0, "cannot find %s", finfo->fullname);
598 else if (!strcmp (vers->ts_user, vers->ts_rcs))
600 /* The user file matches some revision in the repository
601 Diff against the repository (for remote CVS, we might not
602 have a copy of the user file around). */
603 user_file_rev = vers->vn_user;
608 empty_file = diff_file_nodiff( finfo, vers, empty_file, &rev1_cache );
609 if( empty_file == DIFF_SAME )
611 /* In the server case, would be nice to send a "Checked-in"
612 response, so that the client can rewrite its timestamp.
613 server_checked_in by itself isn't the right thing (it
614 needs a server_register), but I'm not sure what is.
615 It isn't clear to me how "cvs status" handles this (that
616 is, for a client which sends Modified not Is-modified to
617 "cvs status"), but it does. */
621 else if( empty_file == DIFF_ERROR )
624 /* Output an "Index:" line for patch to use */
625 cvs_output ("Index: ", 0);
626 cvs_output (finfo->fullname, 0);
627 cvs_output ("\n", 1);
629 tocvsPath = wrap_tocvs_process_file(finfo->file);
630 if( tocvsPath != NULL )
632 /* Backup the current version of the file to CVS/,,filename */
633 fname = xmalloc (strlen (finfo->file)
637 sprintf(fname,"%s/%s%s",CVSADM, CVSPREFIX, finfo->file);
638 if (unlink_file_dir (fname) < 0)
639 if (! existence_error (errno))
640 error (1, errno, "cannot remove %s", fname);
641 rename_file (finfo->file, fname);
642 /* Copy the wrapped file to the current directory then go to work */
643 copy_file (tocvsPath, finfo->file);
646 /* Set up file labels appropriate for compatibility with the Larry Wall
647 * implementation of patch if the user didn't specify. This is irrelevant
648 * according to the POSIX.2 specification.
652 /* The user cannot set the rev2 label without first setting the rev1
655 if (!have_rev2_label)
657 if (empty_file == DIFF_REMOVED)
658 label2 = make_file_label (DEVNULL, NULL, NULL);
660 label2 = make_file_label (finfo->fullname, use_rev2,
661 vers ? vers->srcfile : NULL);
662 if (!have_rev1_label)
664 if (empty_file == DIFF_ADDED)
665 label1 = make_file_label (DEVNULL, NULL, NULL);
667 label1 = make_file_label (finfo->fullname, use_rev1,
668 vers ? vers->srcfile : NULL);
672 if (empty_file == DIFF_ADDED || empty_file == DIFF_REMOVED)
674 /* This is fullname, not file, possibly despite the POSIX.2
675 * specification, because that's the way all the Larry Wall
676 * implementations of patch (are there other implementations?) want
677 * things and the POSIX.2 spec appears to leave room for this.
680 ===================================================================\n\
682 cvs_output (finfo->fullname, 0);
683 cvs_output ("\n", 1);
685 cvs_output ("diff -N ", 0);
686 cvs_output (finfo->fullname, 0);
687 cvs_output ("\n", 1);
689 if (empty_file == DIFF_ADDED)
691 if (use_rev2 == NULL)
692 status = diff_exec (DEVNULL, finfo->file, label1, label2, opts,
698 tmp = cvs_temp_name ();
699 retcode = RCS_checkout (vers->srcfile, (char *) NULL,
700 use_rev2, (char *) NULL,
704 tmp, (RCSCHECKOUTPROC) NULL,
709 status = diff_exec (DEVNULL, tmp, label1, label2, opts, RUN_TTY);
716 tmp = cvs_temp_name ();
717 retcode = RCS_checkout (vers->srcfile, (char *) NULL,
718 use_rev1, (char *) NULL,
719 *options ? options : vers->options,
720 tmp, (RCSCHECKOUTPROC) NULL,
725 status = diff_exec (tmp, DEVNULL, label1, label2, opts, RUN_TTY);
730 status = RCS_exec_rcsdiff(vers->srcfile, opts,
731 *options ? options : vers->options,
732 use_rev1, rev1_cache, use_rev2,
738 if (label1) free (label1);
739 if (label2) free (label2);
743 case -1: /* fork failed */
744 error (1, errno, "fork failed while diffing %s",
745 vers->srcfile->path);
746 case 0: /* everything ok */
749 default: /* other error */
755 if( tocvsPath != NULL )
757 if (unlink_file_dir (finfo->file) < 0)
758 if (! existence_error (errno))
759 error (1, errno, "cannot remove %s", finfo->file);
761 rename_file (fname, finfo->file);
762 if (unlink_file (tocvsPath) < 0)
763 error (1, errno, "cannot remove %s", tocvsPath);
767 /* Call CVS_UNLINK() rather than unlink_file() below to avoid the check
772 if (CVS_UNLINK(tmp) < 0)
773 error (0, errno, "cannot remove %s", tmp);
776 if( rev1_cache != NULL )
778 if( CVS_UNLINK( rev1_cache ) < 0 )
779 error( 0, errno, "cannot remove %s", rev1_cache );
784 diff_mark_errors (err);
789 * Remember the exit status for each file.
792 diff_mark_errors (int err)
794 if (err > diff_errors)
801 * Print a warm fuzzy message when we enter a dir
803 * Don't try to diff directories that don't exist! -- DW
807 diff_dirproc (void *callerdat, const char *dir, const char *pos_repos,
808 const char *update_dir, List *entries)
810 /* XXX - check for dirs we don't want to process??? */
812 /* YES ... for instance dirs that don't exist!!! -- DW */
817 error (0, 0, "Diffing %s", update_dir);
824 * Concoct the proper exit status - done with files
828 diff_filesdoneproc (void *callerdat, int err, const char *repos,
829 const char *update_dir, List *entries)
831 return (diff_errors);
837 * Concoct the proper exit status - leaving directories
841 diff_dirleaveproc (void *callerdat, const char *dir, int err,
842 const char *update_dir, List *entries)
844 return (diff_errors);
850 * verify that a file is different
852 static enum diff_file
853 diff_file_nodiff(struct file_info *finfo, Vers_TS *vers, enum diff_file empty_file, char **rev1_cache)
857 /* Cache the content of rev1 if we have to look
864 TRACE (TRACE_FUNCTION, "diff_file_nodiff (%s, %d)",
865 finfo->fullname ? finfo->fullname : "(null)", empty_file);
867 /* free up any old use_rev* variables and reset 'em */
872 use_rev1 = use_rev2 = (char *) NULL;
874 if (diff_rev1 || diff_date1)
876 /* special handling for TAG_HEAD */
877 if (diff_rev1 && strcmp (diff_rev1, TAG_HEAD) == 0)
879 if (vers->vn_rcs != NULL && vers->srcfile != NULL)
880 use_rev1 = RCS_branch_head (vers->srcfile, vers->vn_rcs);
884 xvers = Version_TS (finfo, NULL, diff_rev1, diff_date1, 1, 0);
885 if (xvers->vn_rcs != NULL)
886 use_rev1 = xstrdup (xvers->vn_rcs);
887 freevers_ts (&xvers);
890 if (diff_rev2 || diff_date2)
892 /* special handling for TAG_HEAD */
893 if (diff_rev2 && strcmp (diff_rev2, TAG_HEAD) == 0)
895 if (vers->vn_rcs != NULL && vers->srcfile != NULL)
896 use_rev2 = RCS_branch_head (vers->srcfile, vers->vn_rcs);
900 xvers = Version_TS (finfo, NULL, diff_rev2, diff_date2, 1, 0);
901 if (xvers->vn_rcs != NULL)
902 use_rev2 = xstrdup (xvers->vn_rcs);
903 freevers_ts (&xvers);
906 if( use_rev1 == NULL || RCS_isdead( vers->srcfile, use_rev1 ) )
908 /* The first revision does not exist. If EMPTY_FILES is
909 true, treat this as an added file. Otherwise, warn
910 about the missing tag. */
911 if( use_rev2 == NULL || RCS_isdead( vers->srcfile, use_rev2 ) )
912 /* At least in the case where DIFF_REV1 and DIFF_REV2
913 * are both numeric (and non-existant (NULL), as opposed to
914 * dead?), we should be returning some kind of error (see
915 * basicb-8a0 in testsuite). The symbolic case may be more
921 if( use_rev1 != NULL )
926 "Tag %s refers to a dead (removed) revision in file `%s'.",
927 diff_rev1, finfo->fullname );
932 "Date %s refers to a dead (removed) revision in file `%s'.",
933 diff_date1, finfo->fullname );
936 "No comparison available. Pass `-N' to `%s diff'?",
940 error (0, 0, "tag %s is not in file %s", diff_rev1,
943 error (0, 0, "no revision for date %s in file %s",
944 diff_date1, finfo->fullname);
948 assert( use_rev1 != NULL );
949 if( use_rev2 == NULL || RCS_isdead( vers->srcfile, use_rev2 ) )
951 /* The second revision does not exist. If EMPTY_FILES is
952 true, treat this as a removed file. Otherwise warn
953 about the missing tag. */
956 if( use_rev2 != NULL )
961 "Tag %s refers to a dead (removed) revision in file `%s'.",
962 diff_rev2, finfo->fullname );
967 "Date %s refers to a dead (removed) revision in file `%s'.",
968 diff_date2, finfo->fullname );
971 "No comparison available. Pass `-N' to `%s diff'?",
975 error (0, 0, "tag %s is not in file %s", diff_rev2,
978 error (0, 0, "no revision for date %s in file %s",
979 diff_date2, finfo->fullname);
982 /* Now, see if we really need to do the diff. We can't assume that the
983 * files are different when the revs are.
985 assert( use_rev2 != NULL );
986 if( strcmp (use_rev1, use_rev2) == 0 )
988 /* else fall through and do the diff */
991 /* If we had a r1/d1 & r2/d2, then at this point we must have a C3P0...
992 * err... ok, then both rev1 & rev2 must have resolved to an existing,
993 * live version due to if statement we just closed.
995 assert (!(diff_rev2 || diff_date2) || (use_rev1 && use_rev2));
997 if ((diff_rev1 || diff_date1) &&
998 (use_rev1 == NULL || RCS_isdead (vers->srcfile, use_rev1)))
1000 /* The first revision does not exist, and no second revision
1004 if (empty_file == DIFF_REMOVED)
1006 if( user_file_rev && use_rev2 == NULL )
1007 use_rev2 = xstrdup( user_file_rev );
1010 if( use_rev1 != NULL )
1015 "Tag %s refers to a dead (removed) revision in file `%s'.",
1016 diff_rev1, finfo->fullname );
1021 "Date %s refers to a dead (removed) revision in file `%s'.",
1022 diff_date1, finfo->fullname );
1025 "No comparison available. Pass `-N' to `%s diff'?",
1028 else if ( diff_rev1 )
1029 error( 0, 0, "tag %s is not in file %s", diff_rev1,
1032 error( 0, 0, "no revision for date %s in file %s",
1033 diff_date1, finfo->fullname );
1037 assert( !diff_rev1 || use_rev1 );
1041 /* drop user_file_rev into first unused use_rev */
1043 use_rev1 = xstrdup (user_file_rev);
1045 use_rev2 = xstrdup (user_file_rev);
1046 /* and if not, it wasn't needed anyhow */
1047 user_file_rev = NULL;
1050 /* Now, see if we really need to do the diff. We can't assume that the
1051 * files are different when the revs are.
1053 if( use_rev1 && use_rev2)
1055 if (strcmp (use_rev1, use_rev2) == 0)
1057 /* Fall through and do the diff. */
1059 /* Don't want to do the timestamp check with both use_rev1 & use_rev2 set.
1060 * The timestamp check is just for the default case of diffing the
1061 * workspace file against its base revision.
1063 else if( use_rev1 == NULL
1064 || ( vers->vn_user != NULL
1065 && strcmp( use_rev1, vers->vn_user ) == 0 ) )
1067 if (empty_file == DIFF_DIFFERENT
1068 && vers->ts_user != NULL
1069 && strcmp (vers->ts_rcs, vers->ts_user) == 0
1070 && (!(*options) || strcmp (options, vers->options) == 0))
1074 if (use_rev1 == NULL
1075 && (vers->vn_user[0] != '0' || vers->vn_user[1] != '\0'))
1077 if (vers->vn_user[0] == '-')
1078 use_rev1 = xstrdup (vers->vn_user + 1);
1080 use_rev1 = xstrdup (vers->vn_user);
1084 /* If we already know that the file is being added or removed,
1085 then we don't want to do an actual file comparison here. */
1086 if (empty_file != DIFF_DIFFERENT)
1090 * Run a quick cmp to see if we should bother with a full diff.
1093 retcode = RCS_cmp_file( vers->srcfile, use_rev1, rev1_cache,
1094 use_rev2, *options ? options : vers->options,
1097 return retcode == 0 ? DIFF_SAME : DIFF_DIFFERENT;