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
36 static Dtype diff_dirproc PROTO ((void *callerdat, const char *dir,
37 const char *pos_repos,
38 const char *update_dir,
40 static int diff_filesdoneproc PROTO ((void *callerdat, int err,
42 const char *update_dir,
44 static int diff_dirleaveproc PROTO ((void *callerdat, const char *dir,
45 int err, const char *update_dir,
47 static enum diff_file diff_file_nodiff PROTO(( struct file_info *finfo,
51 static int diff_fileproc PROTO ((void *callerdat, struct file_info *finfo));
52 static void diff_mark_errors PROTO((int err));
55 /* Global variables. Would be cleaner if we just put this stuff in a
56 struct like log.c does. */
58 /* Command line tags, from -r option. Points into argv. */
59 static char *diff_rev1, *diff_rev2;
60 /* Command line dates, from -D option. Malloc'd. */
61 static char *diff_date1, *diff_date2;
62 static char *diff_join1, *diff_join2;
63 static char *use_rev1, *use_rev2;
64 static int have_rev1_label, have_rev2_label;
66 /* Revision of the user file, if it is unchanged from something in the
67 repository and we want to use that fact. */
68 static char *user_file_rev;
71 static char **diff_argv;
73 static size_t diff_arg_allocated;
74 static int diff_errors;
75 static int empty_files = 0;
77 static const char *const diff_usage[] =
79 "Usage: %s %s [-lR] [-k kopt] [format_options]\n",
80 " [[-r rev1 | -D date1] [-r rev2 | -D date2]] [files...] \n",
81 "\t-l\tLocal directory only, not recursive\n",
82 "\t-R\tProcess directories recursively.\n",
83 "\t-k kopt\tSpecify keyword expansion mode.\n",
84 "\t-D d1\tDiff revision for date against working file.\n",
85 "\t-D d2\tDiff rev1/date1 against date2.\n",
86 "\t-r rev1\tDiff revision for rev1 against working file.\n",
87 "\t-r rev2\tDiff rev1/date1 against rev2.\n",
88 "\nformat_options:\n",
89 " -i --ignore-case Consider upper- and lower-case to be the same.\n",
90 " -w --ignore-all-space Ignore all white space.\n",
91 " -b --ignore-space-change Ignore changes in the amount of white space.\n",
92 " -B --ignore-blank-lines Ignore changes whose lines are all blank.\n",
93 " -I RE --ignore-matching-lines=RE Ignore changes whose lines all match RE.\n",
94 " --binary Read and write data in binary mode.\n",
95 " -a --text Treat all files as text.\n\n",
96 " -c -C NUM --context[=NUM] Output NUM (default 2) lines of copied context.\n",
97 " -u -U NUM --unified[=NUM] Output NUM (default 2) lines of unified context.\n",
98 " -NUM Use NUM context lines.\n",
99 " -L LABEL --label LABEL Use LABEL instead of file name.\n",
100 " -p --show-c-function Show which C function each change is in.\n",
101 " -F RE --show-function-line=RE Show the most recent line matching RE.\n",
102 " --brief Output only whether files differ.\n",
103 " -e --ed Output an ed script.\n",
104 " -f --forward-ed Output something like an ed script in forward order.\n",
105 " -n --rcs Output an RCS format diff.\n",
106 " -y --side-by-side Output in two columns.\n",
107 " -W NUM --width=NUM Output at most NUM (default 130) characters per line.\n",
108 " --left-column Output only the left column of common lines.\n",
109 " --suppress-common-lines Do not output common lines.\n",
110 " --ifdef=NAME Output merged file to show `#ifdef NAME' diffs.\n",
111 " --GTYPE-group-format=GFMT Similar, but format GTYPE input groups with GFMT.\n",
112 " --line-format=LFMT Similar, but format all input lines with LFMT.\n",
113 " --LTYPE-line-format=LFMT Similar, but format LTYPE input lines with LFMT.\n",
114 " LTYPE is `old', `new', or `unchanged'. GTYPE is LTYPE or `changed'.\n",
115 " GFMT may contain:\n",
116 " %%< lines from FILE1\n",
117 " %%> lines from FILE2\n",
118 " %%= lines common to FILE1 and FILE2\n",
119 " %%[-][WIDTH][.[PREC]]{doxX}LETTER printf-style spec for LETTER\n",
120 " LETTERs are as follows for new group, lower case for old group:\n",
121 " F first line number\n",
122 " L last line number\n",
123 " N number of lines = L-F+1\n",
126 " LFMT may contain:\n",
127 " %%L contents of line\n",
128 " %%l contents of line, excluding any trailing newline\n",
129 " %%[-][WIDTH][.[PREC]]{doxX}n printf-style spec for input line number\n",
130 " Either GFMT or LFMT may contain:\n",
132 " %%c'C' the single character C\n",
133 " %%c'\\OOO' the character with octal code OOO\n\n",
134 " -t --expand-tabs Expand tabs to spaces in output.\n",
135 " -T --initial-tab Make tabs line up by prepending a tab.\n\n",
136 " -N --new-file Treat absent files as empty.\n",
137 " -s --report-identical-files Report when two files are the same.\n",
138 " --horizon-lines=NUM Keep NUM lines of the common prefix and suffix.\n",
139 " -d --minimal Try hard to find a smaller set of changes.\n",
140 " -H --speed-large-files Assume large files and many scattered small changes.\n",
141 "\n(Specify the --help global option for a list of other help options)\n",
145 /* I copied this array directly out of diff.c in diffutils 2.7, after
146 removing the following entries, none of which seem relevant to use
151 --unidirectional-new-file (-P)
156 --paginate (-l) (doesn't work with library callbacks)
158 I changed the options which take optional arguments (--context and
159 --unified) to return a number rather than a letter, so that the
160 optional argument could be handled more easily. I changed the
161 --brief and --ifdef options to return numbers, since -q and -D mean
162 something else to cvs diff.
164 The numbers 129- that appear in the fourth element of some entries
165 tell the big switch in `diff' how to process those options. -- Ian
167 The following options, which diff lists as "An alias, no longer
168 recommended" have been removed: --file-label --entire-new-file
171 static struct option const longopts[] =
173 {"ignore-blank-lines", 0, 0, 'B'},
174 {"context", 2, 0, 143},
175 {"ifdef", 1, 0, 131},
176 {"show-function-line", 1, 0, 'F'},
177 {"speed-large-files", 0, 0, 'H'},
178 {"ignore-matching-lines", 1, 0, 'I'},
179 {"label", 1, 0, 'L'},
180 {"new-file", 0, 0, 'N'},
181 {"initial-tab", 0, 0, 'T'},
182 {"width", 1, 0, 'W'},
184 {"ignore-space-change", 0, 0, 'b'},
185 {"minimal", 0, 0, 'd'},
187 {"forward-ed", 0, 0, 'f'},
188 {"ignore-case", 0, 0, 'i'},
190 {"show-c-function", 0, 0, 'p'},
192 /* This is a potentially very useful option, except the output is so
193 silly. It would be much better for it to look like "cvs rdiff -s"
194 which displays all the same info, minus quite a few lines of
195 extraneous garbage. */
196 {"brief", 0, 0, 145},
198 {"report-identical-files", 0, 0, 's'},
199 {"expand-tabs", 0, 0, 't'},
200 {"ignore-all-space", 0, 0, 'w'},
201 {"side-by-side", 0, 0, 'y'},
202 {"unified", 2, 0, 146},
203 {"left-column", 0, 0, 129},
204 {"suppress-common-lines", 0, 0, 130},
205 {"old-line-format", 1, 0, 132},
206 {"new-line-format", 1, 0, 133},
207 {"unchanged-line-format", 1, 0, 134},
208 {"line-format", 1, 0, 135},
209 {"old-group-format", 1, 0, 136},
210 {"new-group-format", 1, 0, 137},
211 {"unchanged-group-format", 1, 0, 138},
212 {"changed-group-format", 1, 0, 139},
213 {"horizon-lines", 1, 0, 140},
214 {"binary", 0, 0, 142},
220 /* Add one of OPT or LONGOPT, and ARGUMENT, when present, to global DIFF_ARGV.
223 * opt A character option representation.
224 * longopt A long option name.
225 * argument Optional option argument.
228 * diff_argc The number of arguments in DIFF_ARGV.
229 * diff_argv Array of argument strings.
230 * diff_arg_allocated Allocated length of DIFF_ARGV.
233 * Behavior when both OPT & LONGOPT are provided is undefined.
239 add_diff_args (char opt, const char *longopt, const char *argument)
243 /* Add opt or longopt to diff_arv. */
244 assert (opt || (longopt && *longopt));
245 assert (!(opt && (longopt && *longopt)));
249 sprintf (tmp, "-%c", opt);
253 tmp = xmalloc (3 + strlen (longopt));
254 sprintf (tmp, "--%s", longopt);
256 run_add_arg_p (&diff_argc, &diff_arg_allocated, &diff_argv, tmp);
259 /* When present, add ARGUMENT to DIFF_ARGV. */
261 run_add_arg_p (&diff_argc, &diff_arg_allocated, &diff_argv, argument);
266 /* CVS 1.9 and similar versions seemed to have pretty weird handling
267 of -y and -T. In the cases where it called rcsdiff,
268 they would have the meanings mentioned below. In the cases where it
269 called diff, they would have the meanings mentioned in "longopts".
270 Noone seems to have missed them, so I think the right thing to do is
271 just to remove the options altogether (which I have done).
273 In the case of -z and -q, "cvs diff" did not accept them even back
274 when we called rcsdiff (at least, it hasn't accepted them
277 In comparing rcsdiff to the new CVS implementation, I noticed that
278 the following rcsdiff flags are not handled by CVS diff:
280 -y: perform diff even when the requested revisions are the
283 -T: preserve modification time on the RCS file
284 -z: specify timezone for use in file labels
286 I think these are not really relevant. -y is undocumented even in
287 RCS 5.7, and seems like a minor change at best. According to RCS
288 documentation, -T only applies when a RCS file has been modified
289 because of lock changes; doesn't CVS sidestep RCS's entire lock
290 structure? -z seems to be unsupported by CVS diff, and has a
291 different meaning as a global option anyway. (Adding it could be
292 a feature, but if it is left out for now, it should not break
293 anything.) For the purposes of producing output, CVS diff appears
294 mostly to ignore -q. Maybe this should be fixed, but I think it's
295 a larger issue than the changes included here. */
310 have_rev1_label = have_rev2_label = 0;
313 * Note that we catch all the valid arguments here, so that we can
314 * intercept the -r arguments for doing revision diffs; and -l/-R for a
315 * non-recursive/recursive diff.
318 /* Clean out our global variables (multiroot can call us multiple
319 times and the server can too, if the client sends several
323 run_arg_free_p (diff_argc, diff_argv);
334 /* FIXME: This should really be allocating an argv to be passed to diff
335 * later rather than strcatting onto the opts variable. We have some
336 * handling routines that can already handle most of the argc/argv
337 * maintenance for us and currently, if anyone were to attempt to pass a
338 * quoted string in here, it would be split on spaces and tabs on its way
341 while ((c = getopt_long (argc, argv,
342 "+abcdefhilnpstuwy0123456789BHNRTC:D:F:I:L:U:W:k:r:j:",
343 longopts, &option_index)) != -1)
348 add_diff_args (0, "side-by-side", NULL);
350 case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
351 case 'h': case 'i': case 'n': case 'p': case 's': case 't':
353 case '0': case '1': case '2': case '3': case '4': case '5':
354 case '6': case '7': case '8': case '9':
355 case 'B': case 'H': case 'T':
356 add_diff_args (c, NULL, NULL);
359 if (have_rev1_label++)
360 if (have_rev2_label++)
362 error (0, 0, "extra -L arguments ignored");
366 case 'C': case 'F': case 'I': case 'U': case 'W':
367 add_diff_args (c, NULL, optarg);
369 case 129: case 130: case 131: case 132: case 133: case 134:
370 case 135: case 136: case 137: case 138: case 139: case 140:
371 case 141: case 142: case 143: case 145: case 146:
372 add_diff_args (0, longopts[option_index].name,
373 longopts[option_index].has_arg ? optarg : NULL);
384 options = RCS_check_kflag (optarg);
389 char *cpy = strdup(optarg);
391 if ((ptr = strchr(optarg, ':')) != NULL)
393 if (diff_rev2 != NULL || diff_date2 != NULL)
395 "no more than two revisions/dates can be specified");
396 if (diff_rev1 != NULL || diff_date1 != NULL) {
399 diff_date2 = ptr ? Make_Date(ptr) : NULL;
403 diff_date1 = ptr ? Make_Date(ptr) : NULL;
408 if (diff_rev2 != NULL || diff_date2 != NULL)
410 "no more than two revisions/dates can be specified");
411 if (diff_rev1 != NULL || diff_date1 != NULL)
417 if (diff_rev2 != NULL || diff_date2 != NULL)
419 "no more than two revisions/dates can be specified");
420 if (diff_rev1 != NULL || diff_date1 != NULL)
421 diff_date2 = Make_Date (optarg);
423 diff_date1 = Make_Date (optarg);
437 /* make sure options is non-null */
439 options = xstrdup ("");
441 #ifdef CLIENT_SUPPORT
442 if (current_parsed_root->isremote) {
443 /* We're the client side. Fire up the remote server. */
452 send_options (diff_argc, diff_argv);
453 if (options[0] != '\0')
456 option_with_arg ("-j", diff_join1);
458 option_with_arg ("-r", diff_rev1);
460 client_senddate (diff_date1);
463 option_with_arg ("-j", diff_join2);
465 option_with_arg ("-r", diff_rev2);
467 client_senddate (diff_date2);
470 /* Send the current files unless diffing two revs from the archive */
471 if (diff_rev2 == NULL && diff_date2 == NULL)
472 send_files (argc, argv, local, 0, 0);
474 send_files (argc, argv, local, 0, SEND_NO_CONTENTS);
476 send_file_names (argc, argv, SEND_EXPAND_WILD);
478 send_to_server ("diff\012", 0);
479 err = get_responses_and_close ();
482 { /* FreeBSD addition - warning idention not changed til matching-} */
483 if (diff_rev1 != NULL)
484 tag_check_valid (diff_rev1, argc, argv, local, 0, "");
485 if (diff_rev2 != NULL)
486 tag_check_valid (diff_rev2, argc, argv, local, 0, "");
489 if (diff_rev1 != NULL || diff_date1 != NULL)
490 which |= W_REPOS | W_ATTIC;
494 /* start the recursion processor */
495 err = start_recursion (diff_fileproc, diff_filesdoneproc, diff_dirproc,
496 diff_dirleaveproc, NULL, argc, argv, local,
497 which, 0, CVS_LOCK_READ, (char *) NULL, 1,
499 } /* FreeBSD addition */
505 if (diff_date1 != NULL)
507 if (diff_date2 != NULL)
509 if (diff_join1 != NULL)
511 if (diff_join2 != NULL)
522 diff_fileproc (callerdat, finfo)
524 struct file_info *finfo;
526 int status, err = 2; /* 2 == trouble, like rcsdiff */
528 enum diff_file empty_file = DIFF_DIFFERENT;
530 char *tocvsPath = NULL;
534 char *rev1_cache = NULL;
537 vers = Version_TS (finfo, NULL, NULL, NULL, 1, 0);
539 if (diff_rev2 != NULL || diff_date2 != NULL)
541 /* Skip all the following checks regarding the user file; we're
544 else if (vers->vn_user == NULL)
546 /* The file does not exist in the working directory. */
547 if ((diff_rev1 != NULL || diff_date1 != NULL)
548 && vers->srcfile != NULL)
550 /* The file does exist in the repository. */
552 empty_file = DIFF_REMOVED;
558 /* special handling for TAG_HEAD */
559 if (diff_rev1 && strcmp (diff_rev1, TAG_HEAD) == 0)
562 (vers->vn_rcs == NULL
564 : RCS_branch_head (vers->srcfile, vers->vn_rcs));
565 exists = head != NULL && !RCS_isdead(vers->srcfile, head);
573 xvers = Version_TS (finfo, NULL, diff_rev1, diff_date1,
575 exists = xvers->vn_rcs != NULL && !RCS_isdead(xvers->srcfile, xvers->vn_rcs);
576 freevers_ts (&xvers);
580 "%s no longer exists, no comparison available",
587 error (0, 0, "I know nothing about %s", finfo->fullname);
591 else if (vers->vn_user[0] == '0' && vers->vn_user[1] == '\0')
593 /* The file was added locally. */
596 if (vers->srcfile != NULL)
598 /* The file does exist in the repository. */
600 if ((diff_rev1 != NULL || diff_date1 != NULL))
602 /* special handling for TAG_HEAD */
603 if (diff_rev1 && strcmp (diff_rev1, TAG_HEAD) == 0)
606 (vers->vn_rcs == NULL
608 : RCS_branch_head (vers->srcfile, vers->vn_rcs));
609 exists = head != NULL && !RCS_isdead(vers->srcfile, head);
617 xvers = Version_TS (finfo, NULL, diff_rev1, diff_date1,
619 exists = xvers->vn_rcs != NULL
620 && !RCS_isdead (xvers->srcfile, xvers->vn_rcs);
621 freevers_ts (&xvers);
626 /* The file was added locally, but an RCS archive exists. Our
627 * base revision must be dead.
629 /* No need to set, exists = 0, here. That's the default. */
634 /* If we got here, then either the RCS archive does not exist or
635 * the relevant revision is dead.
638 empty_file = DIFF_ADDED;
641 error (0, 0, "%s is a new entry, no comparison available",
647 else if (vers->vn_user[0] == '-')
650 empty_file = DIFF_REMOVED;
653 error (0, 0, "%s was removed, no comparison available",
660 if (vers->vn_rcs == NULL && vers->srcfile == NULL)
662 error (0, 0, "cannot find revision control file for %s",
668 if (vers->ts_user == NULL)
670 error (0, 0, "cannot find %s", finfo->fullname);
673 else if (!strcmp (vers->ts_user, vers->ts_rcs))
675 /* The user file matches some revision in the repository
676 Diff against the repository (for remote CVS, we might not
677 have a copy of the user file around). */
678 user_file_rev = vers->vn_user;
683 empty_file = diff_file_nodiff( finfo, vers, empty_file, &rev1_cache );
684 if( empty_file == DIFF_SAME )
686 /* In the server case, would be nice to send a "Checked-in"
687 response, so that the client can rewrite its timestamp.
688 server_checked_in by itself isn't the right thing (it
689 needs a server_register), but I'm not sure what is.
690 It isn't clear to me how "cvs status" handles this (that
691 is, for a client which sends Modified not Is-modified to
692 "cvs status"), but it does. */
696 else if( empty_file == DIFF_ERROR )
699 /* Output an "Index:" line for patch to use */
700 cvs_output ("Index: ", 0);
701 cvs_output (finfo->fullname, 0);
702 cvs_output ("\n", 1);
704 tocvsPath = wrap_tocvs_process_file(finfo->file);
705 if( tocvsPath != NULL )
707 /* Backup the current version of the file to CVS/,,filename */
708 fname = xmalloc (strlen (finfo->file)
712 sprintf(fname,"%s/%s%s",CVSADM, CVSPREFIX, finfo->file);
713 if (unlink_file_dir (fname) < 0)
714 if (! existence_error (errno))
715 error (1, errno, "cannot remove %s", fname);
716 rename_file (finfo->file, fname);
717 /* Copy the wrapped file to the current directory then go to work */
718 copy_file (tocvsPath, finfo->file);
721 /* Set up file labels appropriate for compatibility with the Larry Wall
722 * implementation of patch if the user didn't specify. This is irrelevant
723 * according to the POSIX.2 specification.
727 if (!have_rev1_label)
729 if (empty_file == DIFF_ADDED)
731 make_file_label (DEVNULL, NULL, NULL);
734 make_file_label (finfo->fullname, use_rev1,
735 vers ? vers->srcfile : NULL);
738 if (!have_rev2_label)
740 if (empty_file == DIFF_REMOVED)
742 make_file_label (DEVNULL, NULL, NULL);
745 make_file_label (finfo->fullname, use_rev2,
746 vers ? vers->srcfile : NULL);
749 if (empty_file == DIFF_ADDED || empty_file == DIFF_REMOVED)
751 /* This is fullname, not file, possibly despite the POSIX.2
752 * specification, because that's the way all the Larry Wall
753 * implementations of patch (are there other implementations?) want
754 * things and the POSIX.2 spec appears to leave room for this.
757 ===================================================================\n\
759 cvs_output (finfo->fullname, 0);
760 cvs_output ("\n", 1);
762 cvs_output ("diff -N ", 0);
763 cvs_output (finfo->fullname, 0);
764 cvs_output ("\n", 1);
766 if (empty_file == DIFF_ADDED)
768 if (use_rev2 == NULL)
769 status = diff_exec (DEVNULL, finfo->file, label1, label2,
770 diff_argc, diff_argv, RUN_TTY);
775 tmp = cvs_temp_name ();
776 retcode = RCS_checkout (vers->srcfile, (char *) NULL,
777 use_rev2, (char *) NULL,
781 tmp, (RCSCHECKOUTPROC) NULL,
786 status = diff_exec (DEVNULL, tmp, label1, label2,
787 diff_argc, diff_argv, RUN_TTY);
794 tmp = cvs_temp_name ();
795 retcode = RCS_checkout (vers->srcfile, (char *) NULL,
796 use_rev1, (char *) NULL,
797 *options ? options : vers->options,
798 tmp, (RCSCHECKOUTPROC) NULL,
803 status = diff_exec (tmp, DEVNULL, label1, label2,
804 diff_argc, diff_argv, RUN_TTY);
809 status = RCS_exec_rcsdiff (vers->srcfile, diff_argc, diff_argv,
810 *options ? options : vers->options,
811 use_rev1, rev1_cache, use_rev2,
812 label1, label2, finfo->file);
816 if (label1) free (label1);
817 if (label2) free (label2);
821 case -1: /* fork failed */
822 error (1, errno, "fork failed while diffing %s",
823 vers->srcfile->path);
824 case 0: /* everything ok */
827 default: /* other error */
833 if( tocvsPath != NULL )
835 if (unlink_file_dir (finfo->file) < 0)
836 if (! existence_error (errno))
837 error (1, errno, "cannot remove %s", finfo->file);
839 rename_file (fname, finfo->file);
840 if (unlink_file (tocvsPath) < 0)
841 error (1, errno, "cannot remove %s", tocvsPath);
845 /* Call CVS_UNLINK() rather than unlink_file() below to avoid the check
850 if (CVS_UNLINK(tmp) < 0)
851 error (0, errno, "cannot remove %s", tmp);
854 if( rev1_cache != NULL )
856 if( CVS_UNLINK( rev1_cache ) < 0 )
857 error( 0, errno, "cannot remove %s", rev1_cache );
862 diff_mark_errors (err);
867 * Remember the exit status for each file.
870 diff_mark_errors (err)
873 if (err > diff_errors)
878 * Print a warm fuzzy message when we enter a dir
880 * Don't try to diff directories that don't exist! -- DW
884 diff_dirproc (callerdat, dir, pos_repos, update_dir, entries)
887 const char *pos_repos;
888 const char *update_dir;
891 /* XXX - check for dirs we don't want to process??? */
893 /* YES ... for instance dirs that don't exist!!! -- DW */
898 error (0, 0, "Diffing %s", update_dir);
903 * Concoct the proper exit status - done with files
907 diff_filesdoneproc (callerdat, err, repos, update_dir, entries)
911 const char *update_dir;
914 return (diff_errors);
918 * Concoct the proper exit status - leaving directories
922 diff_dirleaveproc (callerdat, dir, err, update_dir, entries)
926 const char *update_dir;
929 return (diff_errors);
933 * verify that a file is different
935 static enum diff_file
936 diff_file_nodiff( finfo, vers, empty_file, rev1_cache )
937 struct file_info *finfo;
939 enum diff_file empty_file;
940 char **rev1_cache; /* Cache the content of rev1 if we have to look
947 /* free up any old use_rev* variables and reset 'em */
952 use_rev1 = use_rev2 = (char *) NULL;
954 if (diff_rev1 || diff_date1)
956 /* special handling for TAG_HEAD */
957 if (diff_rev1 && strcmp (diff_rev1, TAG_HEAD) == 0)
959 if (vers->vn_rcs != NULL && vers->srcfile != NULL)
960 use_rev1 = RCS_branch_head (vers->srcfile, vers->vn_rcs);
964 xvers = Version_TS (finfo, NULL, diff_rev1, diff_date1, 1, 0);
965 if (xvers->vn_rcs != NULL)
966 use_rev1 = xstrdup (xvers->vn_rcs);
967 freevers_ts (&xvers);
970 if (diff_rev2 || diff_date2)
972 /* special handling for TAG_HEAD */
973 if (diff_rev2 && strcmp (diff_rev2, TAG_HEAD) == 0)
975 if (vers->vn_rcs != NULL && vers->srcfile != NULL)
976 use_rev2 = RCS_branch_head (vers->srcfile, vers->vn_rcs);
980 xvers = Version_TS (finfo, NULL, diff_rev2, diff_date2, 1, 0);
981 if (xvers->vn_rcs != NULL)
982 use_rev2 = xstrdup (xvers->vn_rcs);
983 freevers_ts (&xvers);
986 if( use_rev1 == NULL || RCS_isdead( vers->srcfile, use_rev1 ) )
988 /* The first revision does not exist. If EMPTY_FILES is
989 true, treat this as an added file. Otherwise, warn
990 about the missing tag. */
991 if( use_rev2 == NULL || RCS_isdead( vers->srcfile, use_rev2 ) )
992 /* At least in the case where DIFF_REV1 and DIFF_REV2
993 * are both numeric (and non-existant (NULL), as opposed to
994 * dead?), we should be returning some kind of error (see
995 * basicb-8a0 in testsuite). The symbolic case may be more
1001 if( use_rev1 != NULL )
1006 "Tag %s refers to a dead (removed) revision in file `%s'.",
1007 diff_rev1, finfo->fullname );
1012 "Date %s refers to a dead (removed) revision in file `%s'.",
1013 diff_date1, finfo->fullname );
1016 "No comparison available. Pass `-N' to `%s diff'?",
1020 error (0, 0, "tag %s is not in file %s", diff_rev1,
1023 error (0, 0, "no revision for date %s in file %s",
1024 diff_date1, finfo->fullname);
1028 assert( use_rev1 != NULL );
1029 if( use_rev2 == NULL || RCS_isdead( vers->srcfile, use_rev2 ) )
1031 /* The second revision does not exist. If EMPTY_FILES is
1032 true, treat this as a removed file. Otherwise warn
1033 about the missing tag. */
1035 return DIFF_REMOVED;
1036 if( use_rev2 != NULL )
1041 "Tag %s refers to a dead (removed) revision in file `%s'.",
1042 diff_rev2, finfo->fullname );
1047 "Date %s refers to a dead (removed) revision in file `%s'.",
1048 diff_date2, finfo->fullname );
1051 "No comparison available. Pass `-N' to `%s diff'?",
1055 error (0, 0, "tag %s is not in file %s", diff_rev2,
1058 error (0, 0, "no revision for date %s in file %s",
1059 diff_date2, finfo->fullname);
1062 /* Now, see if we really need to do the diff. We can't assume that the
1063 * files are different when the revs are.
1065 assert( use_rev2 != NULL );
1066 if( strcmp (use_rev1, use_rev2) == 0 )
1068 /* else fall through and do the diff */
1071 /* If we had a r1/d1 & r2/d2, then at this point we must have a C3P0...
1072 * err... ok, then both rev1 & rev2 must have resolved to an existing,
1073 * live version due to if statement we just closed.
1075 assert (!(diff_rev2 || diff_date2) || (use_rev1 && use_rev2));
1077 if ((diff_rev1 || diff_date1) &&
1078 (use_rev1 == NULL || RCS_isdead (vers->srcfile, use_rev1)))
1080 /* The first revision does not exist, and no second revision
1084 if (empty_file == DIFF_REMOVED)
1086 if( user_file_rev && use_rev2 == NULL )
1087 use_rev2 = xstrdup( user_file_rev );
1090 if( use_rev1 != NULL )
1095 "Tag %s refers to a dead (removed) revision in file `%s'.",
1096 diff_rev1, finfo->fullname );
1101 "Date %s refers to a dead (removed) revision in file `%s'.",
1102 diff_date1, finfo->fullname );
1105 "No comparison available. Pass `-N' to `%s diff'?",
1108 else if ( diff_rev1 )
1109 error( 0, 0, "tag %s is not in file %s", diff_rev1,
1112 error( 0, 0, "no revision for date %s in file %s",
1113 diff_date1, finfo->fullname );
1117 assert( !diff_rev1 || use_rev1 );
1121 /* drop user_file_rev into first unused use_rev */
1123 use_rev1 = xstrdup (user_file_rev);
1125 use_rev2 = xstrdup (user_file_rev);
1126 /* and if not, it wasn't needed anyhow */
1127 user_file_rev = NULL;
1130 /* Now, see if we really need to do the diff. We can't assume that the
1131 * files are different when the revs are.
1133 if( use_rev1 && use_rev2)
1135 if (strcmp (use_rev1, use_rev2) == 0)
1137 /* Fall through and do the diff. */
1139 /* Don't want to do the timestamp check with both use_rev1 & use_rev2 set.
1140 * The timestamp check is just for the default case of diffing the
1141 * workspace file against its base revision.
1143 else if( use_rev1 == NULL
1144 || ( vers->vn_user != NULL
1145 && strcmp( use_rev1, vers->vn_user ) == 0 ) )
1147 if (empty_file == DIFF_DIFFERENT
1148 && vers->ts_user != NULL
1149 && strcmp (vers->ts_rcs, vers->ts_user) == 0
1150 && (!(*options) || strcmp (options, vers->options) == 0))
1154 if (use_rev1 == NULL
1155 && (vers->vn_user[0] != '0' || vers->vn_user[1] != '\0'))
1157 if (vers->vn_user[0] == '-')
1158 use_rev1 = xstrdup (vers->vn_user + 1);
1160 use_rev1 = xstrdup (vers->vn_user);
1164 /* If we already know that the file is being added or removed,
1165 then we don't want to do an actual file comparison here. */
1166 if (empty_file != DIFF_DIFFERENT)
1170 * Run a quick cmp to see if we should bother with a full diff.
1173 retcode = RCS_cmp_file( vers->srcfile, use_rev1, rev1_cache,
1174 use_rev2, *options ? options : vers->options,
1177 return retcode == 0 ? DIFF_SAME : DIFF_DIFFERENT;