2 * Copyright (C) 1986-2005 The Free Software Foundation, Inc.
4 * Portions Copyright (C) 1998-2005 Derek Price, Ximbiot <http://ximbiot.com>,
7 * Portions Copyright (C) 1992, Brian Berliner and Jeff Polk
8 * Portions Copyright (C) 1989-1992, Brian Berliner
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.
15 * Run diff against versions in the repository. Options that are specified are
16 * passed on directly to "rcsdiff".
18 * Without any file arguments, runs diff against all the currently modified
33 static Dtype diff_dirproc (void *callerdat, const char *dir,
34 const char *pos_repos, const char *update_dir,
36 static int diff_filesdoneproc (void *callerdat, int err,
37 const char *repos, const char *update_dir,
39 static int diff_dirleaveproc (void *callerdat, const char *dir,
40 int err, const char *update_dir,
42 static enum diff_file diff_file_nodiff (struct file_info *finfo, Vers_TS *vers,
43 enum diff_file, char **rev1_cache );
44 static int diff_fileproc (void *callerdat, struct file_info *finfo);
45 static void diff_mark_errors (int err);
48 /* Global variables. Would be cleaner if we just put this stuff in a
49 struct like log.c does. */
51 /* Command line tags, from -r option. Points into argv. */
52 static char *diff_rev1, *diff_rev2;
53 /* Command line dates, from -D option. Malloc'd. */
54 static char *diff_date1, *diff_date2;
55 static char *use_rev1, *use_rev2;
56 static int have_rev1_label, have_rev2_label;
58 /* Revision of the user file, if it is unchanged from something in the
59 repository and we want to use that fact. */
60 static char *user_file_rev;
64 static size_t opts_allocated = 1;
65 static int diff_errors;
66 static int empty_files;
68 static const char *const diff_usage[] =
70 "Usage: %s %s [-lR] [-k kopt] [format_options]\n",
71 " [[-r rev1 | -D date1] [-r rev2 | -D date2]] [files...] \n",
72 "\t-l\tLocal directory only, not recursive\n",
73 "\t-R\tProcess directories recursively.\n",
74 "\t-k kopt\tSpecify keyword expansion mode.\n",
75 "\t-D d1\tDiff revision for date against working file.\n",
76 "\t-D d2\tDiff rev1/date1 against date2.\n",
77 "\t-r rev1\tDiff revision for rev1 against working file.\n",
78 "\t-r rev2\tDiff rev1/date1 against rev2.\n",
79 "\nformat_options:\n",
80 " -i --ignore-case Consider upper- and lower-case to be the same.\n",
81 " -w --ignore-all-space Ignore all white space.\n",
82 " -b --ignore-space-change Ignore changes in the amount of white space.\n",
83 " -B --ignore-blank-lines Ignore changes whose lines are all blank.\n",
84 " -I RE --ignore-matching-lines=RE Ignore changes whose lines all match RE.\n",
85 " --binary Read and write data in binary mode.\n",
86 " -a --text Treat all files as text.\n\n",
87 " -c -C NUM --context[=NUM] Output NUM (default 2) lines of copied context.\n",
88 " -u -U NUM --unified[=NUM] Output NUM (default 2) lines of unified context.\n",
89 " -NUM Use NUM context lines.\n",
90 " -L LABEL --label LABEL Use LABEL instead of file name.\n",
91 " -p --show-c-function Show which C function each change is in.\n",
92 " -F RE --show-function-line=RE Show the most recent line matching RE.\n",
93 " --brief Output only whether files differ.\n",
94 " -e --ed Output an ed script.\n",
95 " -f --forward-ed Output something like an ed script in forward order.\n",
96 " -n --rcs Output an RCS format diff.\n",
97 " -y --side-by-side Output in two columns.\n",
98 " -W NUM --width=NUM Output at most NUM (default 130) characters per line.\n",
99 " --left-column Output only the left column of common lines.\n",
100 " --suppress-common-lines Do not output common lines.\n",
101 " --ifdef=NAME Output merged file to show `#ifdef NAME' diffs.\n",
102 " --GTYPE-group-format=GFMT Similar, but format GTYPE input groups with GFMT.\n",
103 " --line-format=LFMT Similar, but format all input lines with LFMT.\n",
104 " --LTYPE-line-format=LFMT Similar, but format LTYPE input lines with LFMT.\n",
105 " LTYPE is `old', `new', or `unchanged'. GTYPE is LTYPE or `changed'.\n",
106 " GFMT may contain:\n",
107 " %%< lines from FILE1\n",
108 " %%> lines from FILE2\n",
109 " %%= lines common to FILE1 and FILE2\n",
110 " %%[-][WIDTH][.[PREC]]{doxX}LETTER printf-style spec for LETTER\n",
111 " LETTERs are as follows for new group, lower case for old group:\n",
112 " F first line number\n",
113 " L last line number\n",
114 " N number of lines = L-F+1\n",
117 " LFMT may contain:\n",
118 " %%L contents of line\n",
119 " %%l contents of line, excluding any trailing newline\n",
120 " %%[-][WIDTH][.[PREC]]{doxX}n printf-style spec for input line number\n",
121 " Either GFMT or LFMT may contain:\n",
123 " %%c'C' the single character C\n",
124 " %%c'\\OOO' the character with octal code OOO\n\n",
125 " -t --expand-tabs Expand tabs to spaces in output.\n",
126 " -T --initial-tab Make tabs line up by prepending a tab.\n\n",
127 " -N --new-file Treat absent files as empty.\n",
128 " -s --report-identical-files Report when two files are the same.\n",
129 " --horizon-lines=NUM Keep NUM lines of the common prefix and suffix.\n",
130 " -d --minimal Try hard to find a smaller set of changes.\n",
131 " -H --speed-large-files Assume large files and many scattered small changes.\n",
132 "\n(Specify the --help global option for a list of other help options)\n",
136 /* I copied this array directly out of diff.c in diffutils 2.7, after
137 removing the following entries, none of which seem relevant to use
142 --unidirectional-new-file (-P)
147 --paginate (-l) (doesn't work with library callbacks)
149 I changed the options which take optional arguments (--context and
150 --unified) to return a number rather than a letter, so that the
151 optional argument could be handled more easily. I changed the
152 --brief and --ifdef options to return numbers, since -q and -D mean
153 something else to cvs diff.
155 The numbers 129- that appear in the fourth element of some entries
156 tell the big switch in `diff' how to process those options. -- Ian
158 The following options, which diff lists as "An alias, no longer
159 recommended" have been removed: --file-label --entire-new-file
162 static struct option const longopts[] =
164 {"ignore-blank-lines", 0, 0, 'B'},
165 {"context", 2, 0, 143},
166 {"ifdef", 1, 0, 131},
167 {"show-function-line", 1, 0, 'F'},
168 {"speed-large-files", 0, 0, 'H'},
169 {"ignore-matching-lines", 1, 0, 'I'},
170 {"label", 1, 0, 'L'},
171 {"new-file", 0, 0, 'N'},
172 {"initial-tab", 0, 0, 'T'},
173 {"width", 1, 0, 'W'},
175 {"ignore-space-change", 0, 0, 'b'},
176 {"minimal", 0, 0, 'd'},
178 {"forward-ed", 0, 0, 'f'},
179 {"ignore-case", 0, 0, 'i'},
181 {"show-c-function", 0, 0, 'p'},
183 /* This is a potentially very useful option, except the output is so
184 silly. It would be much better for it to look like "cvs rdiff -s"
185 which displays all the same info, minus quite a few lines of
186 extraneous garbage. */
187 {"brief", 0, 0, 145},
189 {"report-identical-files", 0, 0, 's'},
190 {"expand-tabs", 0, 0, 't'},
191 {"ignore-all-space", 0, 0, 'w'},
192 {"side-by-side", 0, 0, 'y'},
193 {"unified", 2, 0, 146},
194 {"left-column", 0, 0, 129},
195 {"suppress-common-lines", 0, 0, 130},
196 {"old-line-format", 1, 0, 132},
197 {"new-line-format", 1, 0, 133},
198 {"unchanged-line-format", 1, 0, 134},
199 {"line-format", 1, 0, 135},
200 {"old-group-format", 1, 0, 136},
201 {"new-group-format", 1, 0, 137},
202 {"unchanged-group-format", 1, 0, 138},
203 {"changed-group-format", 1, 0, 139},
204 {"horizon-lines", 1, 0, 140},
205 {"binary", 0, 0, 142},
209 /* CVS 1.9 and similar versions seemed to have pretty weird handling
210 of -y and -T. In the cases where it called rcsdiff,
211 they would have the meanings mentioned below. In the cases where it
212 called diff, they would have the meanings mentioned in "longopts".
213 Noone seems to have missed them, so I think the right thing to do is
214 just to remove the options altogether (which I have done).
216 In the case of -z and -q, "cvs diff" did not accept them even back
217 when we called rcsdiff (at least, it hasn't accepted them
220 In comparing rcsdiff to the new CVS implementation, I noticed that
221 the following rcsdiff flags are not handled by CVS diff:
223 -y: perform diff even when the requested revisions are the
226 -T: preserve modification time on the RCS file
227 -z: specify timezone for use in file labels
229 I think these are not really relevant. -y is undocumented even in
230 RCS 5.7, and seems like a minor change at best. According to RCS
231 documentation, -T only applies when a RCS file has been modified
232 because of lock changes; doesn't CVS sidestep RCS's entire lock
233 structure? -z seems to be unsupported by CVS diff, and has a
234 different meaning as a global option anyway. (Adding it could be
235 a feature, but if it is left out for now, it should not break
236 anything.) For the purposes of producing output, CVS diff appears
237 mostly to ignore -q. Maybe this should be fixed, but I think it's
238 a larger issue than the changes included here. */
241 diff (int argc, char **argv)
248 char *diff_orig1, *diff_orig2;
253 have_rev1_label = have_rev2_label = 0;
256 * Note that we catch all the valid arguments here, so that we can
257 * intercept the -r arguments for doing revision diffs; and -l/-R for a
258 * non-recursive/recursive diff.
261 /* Clean out our global variables (multiroot can call us multiple
262 times and the server can too, if the client sends several
267 opts = xmalloc (opts_allocated);
278 /* FIXME: This should really be allocating an argv to be passed to diff
279 * later rather than strcatting onto the opts variable. We have some
280 * handling routines that can already handle most of the argc/argv
281 * maintenance for us and currently, if anyone were to attempt to pass a
282 * quoted string in here, it would be split on spaces and tabs on its way
285 while ((c = getopt_long (argc, argv,
286 "+abcdefhilnpstuwy0123456789BHNRTC:D:F:I:L:U:W:k:r:",
287 longopts, &option_index)) != -1)
292 xrealloc_and_strcat (&opts, &opts_allocated, " --side-by-side");
294 case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
295 case 'h': case 'i': case 'n': case 'p': case 's': case 't':
297 case '0': case '1': case '2': case '3': case '4': case '5':
298 case '6': case '7': case '8': case '9':
299 case 'B': case 'H': case 'T':
300 (void) sprintf (tmp, " -%c", (char) c);
301 xrealloc_and_strcat (&opts, &opts_allocated, tmp);
304 if (have_rev1_label++)
305 if (have_rev2_label++)
307 error (0, 0, "extra -L arguments ignored");
311 xrealloc_and_strcat (&opts, &opts_allocated, " -L");
312 xrealloc_and_strcat (&opts, &opts_allocated, optarg);
314 case 'C': case 'F': case 'I': case 'U': case 'W':
315 (void) sprintf (tmp, " -%c", (char) c);
316 xrealloc_and_strcat (&opts, &opts_allocated, tmp);
317 xrealloc_and_strcat (&opts, &opts_allocated, optarg);
321 xrealloc_and_strcat (&opts, &opts_allocated, " --ifdef=");
322 xrealloc_and_strcat (&opts, &opts_allocated, optarg);
324 case 129: case 130: case 132: case 133: case 134:
325 case 135: case 136: case 137: case 138: case 139: case 140:
326 case 141: case 142: case 143: case 145: case 146:
327 xrealloc_and_strcat (&opts, &opts_allocated, " --");
328 xrealloc_and_strcat (&opts, &opts_allocated,
329 longopts[option_index].name);
330 if (longopts[option_index].has_arg == 1
331 || (longopts[option_index].has_arg == 2
334 xrealloc_and_strcat (&opts, &opts_allocated, "=");
335 xrealloc_and_strcat (&opts, &opts_allocated, optarg);
347 options = RCS_check_kflag (optarg);
350 if (diff_rev2 || diff_date2)
352 "no more than two revisions/dates can be specified");
353 if (diff_rev1 || diff_date1)
355 diff_orig2 = xstrdup (optarg);
356 parse_tagdate (&diff_rev2, &diff_date2, optarg);
360 diff_orig1 = xstrdup (optarg);
361 parse_tagdate (&diff_rev1, &diff_date1, optarg);
365 if (diff_rev2 || diff_date2)
367 "no more than two revisions/dates can be specified");
368 if (diff_rev1 || diff_date1)
369 diff_date2 = Make_Date (optarg);
371 diff_date1 = Make_Date (optarg);
385 /* make sure options is non-null */
387 options = xstrdup ("");
389 #ifdef CLIENT_SUPPORT
390 if (current_parsed_root->isremote) {
391 /* We're the client side. Fire up the remote server. */
400 send_option_string (opts);
401 if (options[0] != '\0')
404 option_with_arg ("-r", diff_orig1);
406 client_senddate (diff_date1);
408 option_with_arg ("-r", diff_orig2);
410 client_senddate (diff_date2);
413 /* Send the current files unless diffing two revs from the archive */
414 if (!diff_rev2 && !diff_date2)
415 send_files (argc, argv, local, 0, 0);
417 send_files (argc, argv, local, 0, SEND_NO_CONTENTS);
419 send_file_names (argc, argv, SEND_EXPAND_WILD);
421 send_to_server ("diff\012", 0);
422 err = get_responses_and_close ();
429 if (diff_rev1 != NULL)
430 tag_check_valid (diff_rev1, argc, argv, local, 0, "", false);
431 if (diff_rev2 != NULL)
432 tag_check_valid (diff_rev2, argc, argv, local, 0, "", false);
435 if (diff_rev1 || diff_date1)
436 which |= W_REPOS | W_ATTIC;
440 /* start the recursion processor */
441 err = start_recursion (diff_fileproc, diff_filesdoneproc, diff_dirproc,
442 diff_dirleaveproc, NULL, argc, argv, local,
443 which, 0, CVS_LOCK_READ, NULL, 1, NULL);
449 if (diff_date1 != NULL)
451 if (diff_date2 != NULL)
464 diff_fileproc (void *callerdat, struct file_info *finfo)
466 int status, err = 2; /* 2 == trouble, like rcsdiff */
468 enum diff_file empty_file = DIFF_DIFFERENT;
470 char *tocvsPath = NULL;
474 char *rev1_cache = NULL;
477 vers = Version_TS (finfo, NULL, NULL, NULL, 1, 0);
479 if (diff_rev2 || diff_date2)
481 /* Skip all the following checks regarding the user file; we're
484 else if (vers->vn_user == NULL)
486 /* The file does not exist in the working directory. */
487 if ((diff_rev1 || diff_date1)
488 && vers->srcfile != NULL)
490 /* The file does exist in the repository. */
492 empty_file = DIFF_REMOVED;
498 /* special handling for TAG_HEAD */
499 if (diff_rev1 && strcmp (diff_rev1, TAG_HEAD) == 0)
502 (vers->vn_rcs == NULL
504 : RCS_branch_head (vers->srcfile, vers->vn_rcs));
505 exists = head != NULL && !RCS_isdead (vers->srcfile, head);
513 xvers = Version_TS (finfo, NULL, diff_rev1, diff_date1,
515 exists = xvers->vn_rcs && !RCS_isdead (xvers->srcfile,
517 freevers_ts (&xvers);
521 "%s no longer exists, no comparison available",
528 error (0, 0, "I know nothing about %s", finfo->fullname);
532 else if (vers->vn_user[0] == '0' && vers->vn_user[1] == '\0')
534 /* The file was added locally. */
537 if (vers->srcfile != NULL)
539 /* The file does exist in the repository. */
541 if (diff_rev1 || diff_date1)
543 /* special handling for TAG_HEAD */
544 if (diff_rev1 && strcmp (diff_rev1, TAG_HEAD) == 0)
547 (vers->vn_rcs == NULL
549 : RCS_branch_head (vers->srcfile, vers->vn_rcs));
550 exists = head && !RCS_isdead (vers->srcfile, head);
558 xvers = Version_TS (finfo, NULL, diff_rev1, diff_date1,
560 exists = xvers->vn_rcs
561 && !RCS_isdead (xvers->srcfile, xvers->vn_rcs);
562 freevers_ts (&xvers);
567 /* The file was added locally, but an RCS archive exists. Our
568 * base revision must be dead.
570 /* No need to set, exists = 0, here. That's the default. */
575 /* If we got here, then either the RCS archive does not exist or
576 * the relevant revision is dead.
579 empty_file = DIFF_ADDED;
582 error (0, 0, "%s is a new entry, no comparison available",
588 else if (vers->vn_user[0] == '-')
591 empty_file = DIFF_REMOVED;
594 error (0, 0, "%s was removed, no comparison available",
601 if (!vers->vn_rcs && !vers->srcfile)
603 error (0, 0, "cannot find revision control file for %s",
609 if (vers->ts_user == NULL)
611 error (0, 0, "cannot find %s", finfo->fullname);
614 else if (!strcmp (vers->ts_user, vers->ts_rcs))
616 /* The user file matches some revision in the repository
617 Diff against the repository (for remote CVS, we might not
618 have a copy of the user file around). */
619 user_file_rev = vers->vn_user;
624 empty_file = diff_file_nodiff (finfo, vers, empty_file, &rev1_cache);
625 if (empty_file == DIFF_SAME)
627 /* In the server case, would be nice to send a "Checked-in"
628 response, so that the client can rewrite its timestamp.
629 server_checked_in by itself isn't the right thing (it
630 needs a server_register), but I'm not sure what is.
631 It isn't clear to me how "cvs status" handles this (that
632 is, for a client which sends Modified not Is-modified to
633 "cvs status"), but it does. */
637 else if (empty_file == DIFF_ERROR)
640 /* Output an "Index:" line for patch to use */
641 cvs_output ("Index: ", 0);
642 cvs_output (finfo->fullname, 0);
643 cvs_output ("\n", 1);
645 tocvsPath = wrap_tocvs_process_file (finfo->file);
648 /* Backup the current version of the file to CVS/,,filename */
649 fname = Xasprintf (fname, "%s/%s%s", CVSADM, CVSPREFIX, finfo->file);
650 if (unlink_file_dir (fname) < 0)
651 if (!existence_error (errno))
652 error (1, errno, "cannot remove %s", fname);
653 rename_file (finfo->file, fname);
654 /* Copy the wrapped file to the current directory then go to work */
655 copy_file (tocvsPath, finfo->file);
658 /* Set up file labels appropriate for compatibility with the Larry Wall
659 * implementation of patch if the user didn't specify. This is irrelevant
660 * according to the POSIX.2 specification.
664 /* The user cannot set the rev2 label without first setting the rev1
667 if (!have_rev2_label)
669 if (empty_file == DIFF_REMOVED)
670 label2 = make_file_label (DEVNULL, NULL, NULL);
672 label2 = make_file_label (finfo->fullname, use_rev2,
674 if (!have_rev1_label)
676 if (empty_file == DIFF_ADDED)
677 label1 = make_file_label (DEVNULL, NULL, NULL);
679 label1 = make_file_label (finfo->fullname, use_rev1,
684 if (empty_file == DIFF_ADDED || empty_file == DIFF_REMOVED)
686 /* This is fullname, not file, possibly despite the POSIX.2
687 * specification, because that's the way all the Larry Wall
688 * implementations of patch (are there other implementations?) want
689 * things and the POSIX.2 spec appears to leave room for this.
692 ===================================================================\n\
694 cvs_output (finfo->fullname, 0);
695 cvs_output ("\n", 1);
697 cvs_output ("diff -N ", 0);
698 cvs_output (finfo->fullname, 0);
699 cvs_output ("\n", 1);
701 if (empty_file == DIFF_ADDED)
703 if (use_rev2 == NULL)
704 status = diff_exec (DEVNULL, finfo->file, label1, label2, opts,
710 tmp = cvs_temp_name ();
711 retcode = RCS_checkout (vers->srcfile, NULL, use_rev2, NULL,
712 *options ? options : vers->options,
717 status = diff_exec (DEVNULL, tmp, label1, label2, opts,
725 tmp = cvs_temp_name ();
726 retcode = RCS_checkout (vers->srcfile, NULL, use_rev1, NULL,
727 *options ? options : vers->options,
732 status = diff_exec (tmp, DEVNULL, label1, label2, opts, RUN_TTY);
737 status = RCS_exec_rcsdiff (vers->srcfile, opts,
738 *options ? options : vers->options,
739 use_rev1, rev1_cache, use_rev2,
740 label1, label2, finfo->file);
744 if (label1) free (label1);
745 if (label2) free (label2);
749 case -1: /* fork failed */
750 error (1, errno, "fork failed while diffing %s",
751 vers->srcfile->path);
752 case 0: /* everything ok */
755 default: /* other error */
761 if( tocvsPath != NULL )
763 if (unlink_file_dir (finfo->file) < 0)
764 if (! existence_error (errno))
765 error (1, errno, "cannot remove %s", finfo->file);
767 rename_file (fname, finfo->file);
768 if (unlink_file (tocvsPath) < 0)
769 error (1, errno, "cannot remove %s", tocvsPath);
773 /* Call CVS_UNLINK() rather than unlink_file() below to avoid the check
778 if (CVS_UNLINK (tmp) < 0)
779 error (0, errno, "cannot remove %s", tmp);
782 if (rev1_cache != NULL)
784 if (CVS_UNLINK (rev1_cache) < 0)
785 error (0, errno, "cannot remove %s", rev1_cache);
790 diff_mark_errors (err);
797 * Remember the exit status for each file.
800 diff_mark_errors (int err)
802 if (err > diff_errors)
809 * Print a warm fuzzy message when we enter a dir
811 * Don't try to diff directories that don't exist! -- DW
815 diff_dirproc (void *callerdat, const char *dir, const char *pos_repos,
816 const char *update_dir, List *entries)
818 /* XXX - check for dirs we don't want to process??? */
820 /* YES ... for instance dirs that don't exist!!! -- DW */
825 error (0, 0, "Diffing %s", update_dir);
832 * Concoct the proper exit status - done with files
836 diff_filesdoneproc (void *callerdat, int err, const char *repos,
837 const char *update_dir, List *entries)
845 * Concoct the proper exit status - leaving directories
849 diff_dirleaveproc (void *callerdat, const char *dir, int err,
850 const char *update_dir, List *entries)
858 * verify that a file is different
866 * rev1_cache Cache the contents of rev1 if we look it up.
868 static enum diff_file
869 diff_file_nodiff (struct file_info *finfo, Vers_TS *vers,
870 enum diff_file empty_file, char **rev1_cache)
875 TRACE (TRACE_FUNCTION, "diff_file_nodiff (%s, %d)",
876 finfo->fullname ? finfo->fullname : "(null)", empty_file);
878 /* free up any old use_rev* variables and reset 'em */
883 use_rev1 = use_rev2 = NULL;
885 if (diff_rev1 || diff_date1)
887 /* special handling for TAG_HEAD */
888 if (diff_rev1 && strcmp (diff_rev1, TAG_HEAD) == 0)
890 if (vers->vn_rcs != NULL && vers->srcfile != NULL)
891 use_rev1 = RCS_branch_head (vers->srcfile, vers->vn_rcs);
895 xvers = Version_TS (finfo, NULL, diff_rev1, diff_date1, 1, 0);
896 if (xvers->vn_rcs != NULL)
897 use_rev1 = xstrdup (xvers->vn_rcs);
898 freevers_ts (&xvers);
901 if (diff_rev2 || diff_date2)
903 /* special handling for TAG_HEAD */
904 if (diff_rev2 && strcmp (diff_rev2, TAG_HEAD) == 0)
906 if (vers->vn_rcs && vers->srcfile)
907 use_rev2 = RCS_branch_head (vers->srcfile, vers->vn_rcs);
911 xvers = Version_TS (finfo, NULL, diff_rev2, diff_date2, 1, 0);
912 if (xvers->vn_rcs != NULL)
913 use_rev2 = xstrdup (xvers->vn_rcs);
914 freevers_ts (&xvers);
917 if (use_rev1 == NULL || RCS_isdead (vers->srcfile, use_rev1))
919 /* The first revision does not exist. If EMPTY_FILES is
920 true, treat this as an added file. Otherwise, warn
921 about the missing tag. */
922 if (use_rev2 == NULL || RCS_isdead (vers->srcfile, use_rev2))
923 /* At least in the case where DIFF_REV1 and DIFF_REV2
924 * are both numeric (and non-existant (NULL), as opposed to
925 * dead?), we should be returning some kind of error (see
926 * basicb-8a0 in testsuite). The symbolic case may be more
932 if (use_rev1 != NULL)
937 "Tag %s refers to a dead (removed) revision in file `%s'.",
938 diff_rev1, finfo->fullname);
943 "Date %s refers to a dead (removed) revision in file `%s'.",
944 diff_date1, finfo->fullname);
947 "No comparison available. Pass `-N' to `%s diff'?",
951 error (0, 0, "tag %s is not in file %s", diff_rev1,
954 error (0, 0, "no revision for date %s in file %s",
955 diff_date1, finfo->fullname);
959 assert( use_rev1 != NULL );
960 if( use_rev2 == NULL || RCS_isdead( vers->srcfile, use_rev2 ) )
962 /* The second revision does not exist. If EMPTY_FILES is
963 true, treat this as a removed file. Otherwise warn
964 about the missing tag. */
967 if( use_rev2 != NULL )
972 "Tag %s refers to a dead (removed) revision in file `%s'.",
973 diff_rev2, finfo->fullname );
978 "Date %s refers to a dead (removed) revision in file `%s'.",
979 diff_date2, finfo->fullname );
982 "No comparison available. Pass `-N' to `%s diff'?",
986 error (0, 0, "tag %s is not in file %s", diff_rev2,
989 error (0, 0, "no revision for date %s in file %s",
990 diff_date2, finfo->fullname);
993 /* Now, see if we really need to do the diff. We can't assume that the
994 * files are different when the revs are.
996 assert( use_rev2 != NULL );
997 if( strcmp (use_rev1, use_rev2) == 0 )
999 /* else fall through and do the diff */
1002 /* If we had a r1/d1 & r2/d2, then at this point we must have a C3P0...
1003 * err... ok, then both rev1 & rev2 must have resolved to an existing,
1004 * live version due to if statement we just closed.
1006 assert (!(diff_rev2 || diff_date2) || (use_rev1 && use_rev2));
1008 if ((diff_rev1 || diff_date1) &&
1009 (use_rev1 == NULL || RCS_isdead (vers->srcfile, use_rev1)))
1011 /* The first revision does not exist, and no second revision
1015 if (empty_file == DIFF_REMOVED)
1017 if( user_file_rev && use_rev2 == NULL )
1018 use_rev2 = xstrdup( user_file_rev );
1021 if( use_rev1 != NULL )
1026 "Tag %s refers to a dead (removed) revision in file `%s'.",
1027 diff_rev1, finfo->fullname );
1032 "Date %s refers to a dead (removed) revision in file `%s'.",
1033 diff_date1, finfo->fullname );
1036 "No comparison available. Pass `-N' to `%s diff'?",
1039 else if ( diff_rev1 )
1040 error( 0, 0, "tag %s is not in file %s", diff_rev1,
1043 error( 0, 0, "no revision for date %s in file %s",
1044 diff_date1, finfo->fullname );
1048 assert( !diff_rev1 || use_rev1 );
1052 /* drop user_file_rev into first unused use_rev */
1054 use_rev1 = xstrdup (user_file_rev);
1056 use_rev2 = xstrdup (user_file_rev);
1057 /* and if not, it wasn't needed anyhow */
1058 user_file_rev = NULL;
1061 /* Now, see if we really need to do the diff. We can't assume that the
1062 * files are different when the revs are.
1064 if( use_rev1 && use_rev2)
1066 if (strcmp (use_rev1, use_rev2) == 0)
1068 /* Fall through and do the diff. */
1070 /* Don't want to do the timestamp check with both use_rev1 & use_rev2 set.
1071 * The timestamp check is just for the default case of diffing the
1072 * workspace file against its base revision.
1074 else if( use_rev1 == NULL
1075 || ( vers->vn_user != NULL
1076 && strcmp( use_rev1, vers->vn_user ) == 0 ) )
1078 if (empty_file == DIFF_DIFFERENT
1079 && vers->ts_user != NULL
1080 && strcmp (vers->ts_rcs, vers->ts_user) == 0
1081 && (!(*options) || strcmp (options, vers->options) == 0))
1085 if (use_rev1 == NULL
1086 && (vers->vn_user[0] != '0' || vers->vn_user[1] != '\0'))
1088 if (vers->vn_user[0] == '-')
1089 use_rev1 = xstrdup (vers->vn_user + 1);
1091 use_rev1 = xstrdup (vers->vn_user);
1095 /* If we already know that the file is being added or removed,
1096 then we don't want to do an actual file comparison here. */
1097 if (empty_file != DIFF_DIFFERENT)
1101 * Run a quick cmp to see if we should bother with a full diff.
1104 retcode = RCS_cmp_file( vers->srcfile, use_rev1, rev1_cache,
1105 use_rev2, *options ? options : vers->options,
1108 return retcode == 0 ? DIFF_SAME : DIFF_DIFFERENT;