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
31 static Dtype diff_dirproc PROTO ((void *callerdat, const char *dir,
32 const char *pos_repos,
33 const char *update_dir,
35 static int diff_filesdoneproc PROTO ((void *callerdat, int err,
37 const char *update_dir,
39 static int diff_dirleaveproc PROTO ((void *callerdat, const char *dir,
40 int err, const char *update_dir,
42 static enum diff_file diff_file_nodiff PROTO(( struct file_info *finfo,
46 static int diff_fileproc PROTO ((void *callerdat, struct file_info *finfo));
47 static void diff_mark_errors PROTO((int err));
50 /* Global variables. Would be cleaner if we just put this stuff in a
51 struct like log.c does. */
53 /* Command line tags, from -r option. Points into argv. */
54 static char *diff_rev1, *diff_rev2;
55 /* Command line dates, from -D option. Malloc'd. */
56 static char *diff_date1, *diff_date2;
57 static char *diff_join1, *diff_join2;
58 static char *use_rev1, *use_rev2;
59 static int have_rev1_label, have_rev2_label;
61 /* Revision of the user file, if it is unchanged from something in the
62 repository and we want to use that fact. */
63 static char *user_file_rev;
67 static size_t opts_allocated = 1;
68 static int diff_errors;
69 static int empty_files = 0;
71 static const char *const diff_usage[] =
73 "Usage: %s %s [-lR] [-k kopt] [format_options]\n",
74 " [[-r rev1 | -D date1] [-r rev2 | -D date2]] [files...] \n",
75 "\t-l\tLocal directory only, not recursive\n",
76 "\t-R\tProcess directories recursively.\n",
77 "\t-k kopt\tSpecify keyword expansion mode.\n",
78 "\t-D d1\tDiff revision for date against working file.\n",
79 "\t-D d2\tDiff rev1/date1 against date2.\n",
80 "\t-r rev1\tDiff revision for rev1 against working file.\n",
81 "\t-r rev2\tDiff rev1/date1 against rev2.\n",
82 "\nformat_options:\n",
83 " -i --ignore-case Consider upper- and lower-case to be the same.\n",
84 " -w --ignore-all-space Ignore all white space.\n",
85 " -b --ignore-space-change Ignore changes in the amount of white space.\n",
86 " -B --ignore-blank-lines Ignore changes whose lines are all blank.\n",
87 " -I RE --ignore-matching-lines=RE Ignore changes whose lines all match RE.\n",
88 " --binary Read and write data in binary mode.\n",
89 " -a --text Treat all files as text.\n\n",
90 " -c -C NUM --context[=NUM] Output NUM (default 2) lines of copied context.\n",
91 " -u -U NUM --unified[=NUM] Output NUM (default 2) lines of unified context.\n",
92 " -NUM Use NUM context lines.\n",
93 " -L LABEL --label LABEL Use LABEL instead of file name.\n",
94 " -p --show-c-function Show which C function each change is in.\n",
95 " -F RE --show-function-line=RE Show the most recent line matching RE.\n",
96 " --brief Output only whether files differ.\n",
97 " -e --ed Output an ed script.\n",
98 " -f --forward-ed Output something like an ed script in forward order.\n",
99 " -n --rcs Output an RCS format diff.\n",
100 " -y --side-by-side Output in two columns.\n",
101 " -W NUM --width=NUM Output at most NUM (default 130) characters per line.\n",
102 " --left-column Output only the left column of common lines.\n",
103 " --suppress-common-lines Do not output common lines.\n",
104 " --ifdef=NAME Output merged file to show `#ifdef NAME' diffs.\n",
105 " --GTYPE-group-format=GFMT Similar, but format GTYPE input groups with GFMT.\n",
106 " --line-format=LFMT Similar, but format all input lines with LFMT.\n",
107 " --LTYPE-line-format=LFMT Similar, but format LTYPE input lines with LFMT.\n",
108 " LTYPE is `old', `new', or `unchanged'. GTYPE is LTYPE or `changed'.\n",
109 " GFMT may contain:\n",
110 " %%< lines from FILE1\n",
111 " %%> lines from FILE2\n",
112 " %%= lines common to FILE1 and FILE2\n",
113 " %%[-][WIDTH][.[PREC]]{doxX}LETTER printf-style spec for LETTER\n",
114 " LETTERs are as follows for new group, lower case for old group:\n",
115 " F first line number\n",
116 " L last line number\n",
117 " N number of lines = L-F+1\n",
120 " LFMT may contain:\n",
121 " %%L contents of line\n",
122 " %%l contents of line, excluding any trailing newline\n",
123 " %%[-][WIDTH][.[PREC]]{doxX}n printf-style spec for input line number\n",
124 " Either GFMT or LFMT may contain:\n",
126 " %%c'C' the single character C\n",
127 " %%c'\\OOO' the character with octal code OOO\n\n",
128 " -t --expand-tabs Expand tabs to spaces in output.\n",
129 " -T --initial-tab Make tabs line up by prepending a tab.\n\n",
130 " -N --new-file Treat absent files as empty.\n",
131 " -s --report-identical-files Report when two files are the same.\n",
132 " --horizon-lines=NUM Keep NUM lines of the common prefix and suffix.\n",
133 " -d --minimal Try hard to find a smaller set of changes.\n",
134 " -H --speed-large-files Assume large files and many scattered small changes.\n",
135 "\n(Specify the --help global option for a list of other help options)\n",
139 /* I copied this array directly out of diff.c in diffutils 2.7, after
140 removing the following entries, none of which seem relevant to use
145 --unidirectional-new-file (-P)
150 --paginate (-l) (doesn't work with library callbacks)
152 I changed the options which take optional arguments (--context and
153 --unified) to return a number rather than a letter, so that the
154 optional argument could be handled more easily. I changed the
155 --brief and --ifdef options to return numbers, since -q and -D mean
156 something else to cvs diff.
158 The numbers 129- that appear in the fourth element of some entries
159 tell the big switch in `diff' how to process those options. -- Ian
161 The following options, which diff lists as "An alias, no longer
162 recommended" have been removed: --file-label --entire-new-file
165 static struct option const longopts[] =
167 {"ignore-blank-lines", 0, 0, 'B'},
168 {"context", 2, 0, 143},
169 {"ifdef", 1, 0, 131},
170 {"show-function-line", 1, 0, 'F'},
171 {"speed-large-files", 0, 0, 'H'},
172 {"ignore-matching-lines", 1, 0, 'I'},
173 {"label", 1, 0, 'L'},
174 {"new-file", 0, 0, 'N'},
175 {"initial-tab", 0, 0, 'T'},
176 {"width", 1, 0, 'W'},
178 {"ignore-space-change", 0, 0, 'b'},
179 {"minimal", 0, 0, 'd'},
181 {"forward-ed", 0, 0, 'f'},
182 {"ignore-case", 0, 0, 'i'},
184 {"show-c-function", 0, 0, 'p'},
186 /* This is a potentially very useful option, except the output is so
187 silly. It would be much better for it to look like "cvs rdiff -s"
188 which displays all the same info, minus quite a few lines of
189 extraneous garbage. */
190 {"brief", 0, 0, 145},
192 {"report-identical-files", 0, 0, 's'},
193 {"expand-tabs", 0, 0, 't'},
194 {"ignore-all-space", 0, 0, 'w'},
195 {"side-by-side", 0, 0, 'y'},
196 {"unified", 2, 0, 146},
197 {"left-column", 0, 0, 129},
198 {"suppress-common-lines", 0, 0, 130},
199 {"old-line-format", 1, 0, 132},
200 {"new-line-format", 1, 0, 133},
201 {"unchanged-line-format", 1, 0, 134},
202 {"line-format", 1, 0, 135},
203 {"old-group-format", 1, 0, 136},
204 {"new-group-format", 1, 0, 137},
205 {"unchanged-group-format", 1, 0, 138},
206 {"changed-group-format", 1, 0, 139},
207 {"horizon-lines", 1, 0, 140},
208 {"binary", 0, 0, 142},
212 /* CVS 1.9 and similar versions seemed to have pretty weird handling
213 of -y and -T. In the cases where it called rcsdiff,
214 they would have the meanings mentioned below. In the cases where it
215 called diff, they would have the meanings mentioned in "longopts".
216 Noone seems to have missed them, so I think the right thing to do is
217 just to remove the options altogether (which I have done).
219 In the case of -z and -q, "cvs diff" did not accept them even back
220 when we called rcsdiff (at least, it hasn't accepted them
223 In comparing rcsdiff to the new CVS implementation, I noticed that
224 the following rcsdiff flags are not handled by CVS diff:
226 -y: perform diff even when the requested revisions are the
229 -T: preserve modification time on the RCS file
230 -z: specify timezone for use in file labels
232 I think these are not really relevant. -y is undocumented even in
233 RCS 5.7, and seems like a minor change at best. According to RCS
234 documentation, -T only applies when a RCS file has been modified
235 because of lock changes; doesn't CVS sidestep RCS's entire lock
236 structure? -z seems to be unsupported by CVS diff, and has a
237 different meaning as a global option anyway. (Adding it could be
238 a feature, but if it is left out for now, it should not break
239 anything.) For the purposes of producing output, CVS diff appears
240 mostly to ignore -q. Maybe this should be fixed, but I think it's
241 a larger issue than the changes included here. */
257 have_rev1_label = have_rev2_label = 0;
260 * Note that we catch all the valid arguments here, so that we can
261 * intercept the -r arguments for doing revision diffs; and -l/-R for a
262 * non-recursive/recursive diff.
265 /* Clean out our global variables (multiroot can call us multiple
266 times and the server can too, if the client sends several
271 opts = xmalloc (opts_allocated);
282 /* FIXME: This should really be allocating an argv to be passed to diff
283 * later rather than strcatting onto the opts variable. We have some
284 * handling routines that can already handle most of the argc/argv
285 * maintenance for us and currently, if anyone were to attempt to pass a
286 * quoted string in here, it would be split on spaces and tabs on its way
289 while ((c = getopt_long (argc, argv,
290 "+abcdefhilnpstuwy0123456789BHNRTC:D:F:I:L:U:W:k:r:j:",
291 longopts, &option_index)) != -1)
296 xrealloc_and_strcat (&opts, &opts_allocated, " --side-by-side");
298 case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
299 case 'h': case 'i': case 'n': case 'p': case 's': case 't':
301 case '0': case '1': case '2': case '3': case '4': case '5':
302 case '6': case '7': case '8': case '9':
303 case 'B': case 'H': case 'T':
304 (void) sprintf (tmp, " -%c", (char) c);
305 xrealloc_and_strcat (&opts, &opts_allocated, tmp);
308 if (have_rev1_label++)
309 if (have_rev2_label++)
311 error (0, 0, "extra -L arguments ignored");
315 xrealloc_and_strcat (&opts, &opts_allocated, " -L");
316 xrealloc_and_strcat (&opts, &opts_allocated, optarg);
318 case 'C': case 'F': case 'I': case 'U': case 'W':
319 (void) sprintf (tmp, " -%c", (char) c);
320 xrealloc_and_strcat (&opts, &opts_allocated, tmp);
321 xrealloc_and_strcat (&opts, &opts_allocated, optarg);
325 xrealloc_and_strcat (&opts, &opts_allocated, " --ifdef=");
326 xrealloc_and_strcat (&opts, &opts_allocated, optarg);
328 case 129: case 130: case 132: case 133: case 134:
329 case 135: case 136: case 137: case 138: case 139: case 140:
330 case 141: case 142: case 143: case 145: case 146:
331 xrealloc_and_strcat (&opts, &opts_allocated, " --");
332 xrealloc_and_strcat (&opts, &opts_allocated,
333 longopts[option_index].name);
334 if (longopts[option_index].has_arg == 1
335 || (longopts[option_index].has_arg == 2
338 xrealloc_and_strcat (&opts, &opts_allocated, "=");
339 xrealloc_and_strcat (&opts, &opts_allocated, optarg);
351 options = RCS_check_kflag (optarg);
356 char *cpy = strdup(optarg);
358 if ((ptr = strchr(optarg, ':')) != NULL)
360 if (diff_rev2 != NULL || diff_date2 != NULL)
362 "no more than two revisions/dates can be specified");
363 if (diff_rev1 != NULL || diff_date1 != NULL) {
366 diff_date2 = ptr ? Make_Date(ptr) : NULL;
370 diff_date1 = ptr ? Make_Date(ptr) : NULL;
375 if (diff_rev2 != NULL || diff_date2 != NULL)
377 "no more than two revisions/dates can be specified");
378 if (diff_rev1 != NULL || diff_date1 != NULL)
384 if (diff_rev2 != NULL || diff_date2 != NULL)
386 "no more than two revisions/dates can be specified");
387 if (diff_rev1 != NULL || diff_date1 != NULL)
388 diff_date2 = Make_Date (optarg);
390 diff_date1 = Make_Date (optarg);
404 /* make sure options is non-null */
406 options = xstrdup ("");
408 #ifdef CLIENT_SUPPORT
409 if (current_parsed_root->isremote) {
410 /* We're the client side. Fire up the remote server. */
419 send_option_string (opts);
420 if (options[0] != '\0')
423 option_with_arg ("-j", diff_join1);
425 option_with_arg ("-r", diff_rev1);
427 client_senddate (diff_date1);
430 option_with_arg ("-j", diff_join2);
432 option_with_arg ("-r", diff_rev2);
434 client_senddate (diff_date2);
437 /* Send the current files unless diffing two revs from the archive */
438 if (diff_rev2 == NULL && diff_date2 == NULL)
439 send_files (argc, argv, local, 0, 0);
441 send_files (argc, argv, local, 0, SEND_NO_CONTENTS);
443 send_file_names (argc, argv, SEND_EXPAND_WILD);
445 send_to_server ("diff\012", 0);
446 err = get_responses_and_close ();
450 if (diff_rev1 != NULL)
451 tag_check_valid (diff_rev1, argc, argv, local, 0, "");
452 if (diff_rev2 != NULL)
453 tag_check_valid (diff_rev2, argc, argv, local, 0, "");
456 if (diff_rev1 != NULL || diff_date1 != NULL)
457 which |= W_REPOS | W_ATTIC;
461 /* start the recursion processor */
462 err = start_recursion (diff_fileproc, diff_filesdoneproc, diff_dirproc,
463 diff_dirleaveproc, NULL, argc, argv, local,
464 which, 0, CVS_LOCK_READ, (char *) NULL, 1,
472 if (diff_date1 != NULL)
474 if (diff_date2 != NULL)
476 if (diff_join1 != NULL)
478 if (diff_join2 != NULL)
489 diff_fileproc (callerdat, finfo)
491 struct file_info *finfo;
493 int status, err = 2; /* 2 == trouble, like rcsdiff */
495 enum diff_file empty_file = DIFF_DIFFERENT;
497 char *tocvsPath = NULL;
501 char *rev1_cache = NULL;
504 vers = Version_TS (finfo, NULL, NULL, NULL, 1, 0);
506 if (diff_rev2 != NULL || diff_date2 != NULL)
508 /* Skip all the following checks regarding the user file; we're
511 else if (vers->vn_user == NULL)
513 /* The file does not exist in the working directory. */
514 if ((diff_rev1 != NULL || diff_date1 != NULL)
515 && vers->srcfile != NULL)
517 /* The file does exist in the repository. */
519 empty_file = DIFF_REMOVED;
525 /* special handling for TAG_HEAD XXX */
526 if (diff_rev1 && strcmp (diff_rev1, TAG_HEAD) == 0)
529 (vers->vn_rcs == NULL
531 : RCS_branch_head (vers->srcfile, vers->vn_rcs));
532 exists = head != NULL && !RCS_isdead(vers->srcfile, head);
540 xvers = Version_TS (finfo, NULL, diff_rev1, diff_date1,
542 exists = xvers->vn_rcs != NULL && !RCS_isdead(xvers->srcfile, xvers->vn_rcs);
543 freevers_ts (&xvers);
547 "%s no longer exists, no comparison available",
554 error (0, 0, "I know nothing about %s", finfo->fullname);
558 else if (vers->vn_user[0] == '0' && vers->vn_user[1] == '\0')
560 /* The file was added locally. */
563 if (vers->srcfile != NULL)
565 /* The file does exist in the repository. */
567 if ((diff_rev1 != NULL || diff_date1 != NULL))
569 /* special handling for TAG_HEAD */
570 if (diff_rev1 && strcmp (diff_rev1, TAG_HEAD) == 0)
573 (vers->vn_rcs == NULL
575 : RCS_branch_head (vers->srcfile, vers->vn_rcs));
576 exists = head != NULL && !RCS_isdead(vers->srcfile, head);
584 xvers = Version_TS (finfo, NULL, diff_rev1, diff_date1,
586 exists = xvers->vn_rcs != NULL
587 && !RCS_isdead (xvers->srcfile, xvers->vn_rcs);
588 freevers_ts (&xvers);
593 /* The file was added locally, but an RCS archive exists. Our
594 * base revision must be dead.
596 /* No need to set, exists = 0, here. That's the default. */
601 /* If we got here, then either the RCS archive does not exist or
602 * the relevant revision is dead.
605 empty_file = DIFF_ADDED;
608 error (0, 0, "%s is a new entry, no comparison available",
614 else if (vers->vn_user[0] == '-')
617 empty_file = DIFF_REMOVED;
620 error (0, 0, "%s was removed, no comparison available",
627 if (vers->vn_rcs == NULL && vers->srcfile == NULL)
629 error (0, 0, "cannot find revision control file for %s",
635 if (vers->ts_user == NULL)
637 error (0, 0, "cannot find %s", finfo->fullname);
640 else if (!strcmp (vers->ts_user, vers->ts_rcs))
642 /* The user file matches some revision in the repository
643 Diff against the repository (for remote CVS, we might not
644 have a copy of the user file around). */
645 user_file_rev = vers->vn_user;
650 empty_file = diff_file_nodiff( finfo, vers, empty_file, &rev1_cache );
651 if( empty_file == DIFF_SAME )
653 /* In the server case, would be nice to send a "Checked-in"
654 response, so that the client can rewrite its timestamp.
655 server_checked_in by itself isn't the right thing (it
656 needs a server_register), but I'm not sure what is.
657 It isn't clear to me how "cvs status" handles this (that
658 is, for a client which sends Modified not Is-modified to
659 "cvs status"), but it does. */
663 else if( empty_file == DIFF_ERROR )
666 /* Output an "Index:" line for patch to use */
667 cvs_output ("Index: ", 0);
668 cvs_output (finfo->fullname, 0);
669 cvs_output ("\n", 1);
671 tocvsPath = wrap_tocvs_process_file(finfo->file);
672 if( tocvsPath != NULL )
674 /* Backup the current version of the file to CVS/,,filename */
675 fname = xmalloc (strlen (finfo->file)
679 sprintf(fname,"%s/%s%s",CVSADM, CVSPREFIX, finfo->file);
680 if (unlink_file_dir (fname) < 0)
681 if (! existence_error (errno))
682 error (1, errno, "cannot remove %s", fname);
683 rename_file (finfo->file, fname);
684 /* Copy the wrapped file to the current directory then go to work */
685 copy_file (tocvsPath, finfo->file);
688 /* Set up file labels appropriate for compatibility with the Larry Wall
689 * implementation of patch if the user didn't specify. This is irrelevant
690 * according to the POSIX.2 specification.
694 if (!have_rev1_label)
696 if (empty_file == DIFF_ADDED)
698 make_file_label (DEVNULL, NULL, NULL);
701 make_file_label (finfo->fullname, use_rev1,
702 vers ? vers->srcfile : NULL);
705 if (!have_rev2_label)
707 if (empty_file == DIFF_REMOVED)
709 make_file_label (DEVNULL, NULL, NULL);
712 make_file_label (finfo->fullname, use_rev2,
713 vers ? vers->srcfile : NULL);
716 if (empty_file == DIFF_ADDED || empty_file == DIFF_REMOVED)
718 /* This is fullname, not file, possibly despite the POSIX.2
719 * specification, because that's the way all the Larry Wall
720 * implementations of patch (are there other implementations?) want
721 * things and the POSIX.2 spec appears to leave room for this.
724 ===================================================================\n\
726 cvs_output (finfo->fullname, 0);
727 cvs_output ("\n", 1);
729 cvs_output ("diff -N ", 0);
730 cvs_output (finfo->fullname, 0);
731 cvs_output ("\n", 1);
733 if (empty_file == DIFF_ADDED)
735 if (use_rev2 == NULL)
736 status = diff_exec (DEVNULL, finfo->file, label1, label2, opts,
742 tmp = cvs_temp_name ();
743 retcode = RCS_checkout (vers->srcfile, (char *) NULL,
744 use_rev2, (char *) NULL,
748 tmp, (RCSCHECKOUTPROC) NULL,
753 status = diff_exec (DEVNULL, tmp, label1, label2, opts, RUN_TTY);
760 tmp = cvs_temp_name ();
761 retcode = RCS_checkout (vers->srcfile, (char *) NULL,
762 use_rev1, (char *) NULL,
763 *options ? options : vers->options,
764 tmp, (RCSCHECKOUTPROC) NULL,
769 status = diff_exec (tmp, DEVNULL, label1, label2, opts, RUN_TTY);
774 status = RCS_exec_rcsdiff(vers->srcfile, opts,
775 *options ? options : vers->options,
776 use_rev1, rev1_cache, use_rev2,
782 if (label1) free (label1);
783 if (label2) free (label2);
787 case -1: /* fork failed */
788 error (1, errno, "fork failed while diffing %s",
789 vers->srcfile->path);
790 case 0: /* everything ok */
793 default: /* other error */
799 if( tocvsPath != NULL )
801 if (unlink_file_dir (finfo->file) < 0)
802 if (! existence_error (errno))
803 error (1, errno, "cannot remove %s", finfo->file);
805 rename_file (fname, finfo->file);
806 if (unlink_file (tocvsPath) < 0)
807 error (1, errno, "cannot remove %s", tocvsPath);
811 /* Call CVS_UNLINK() rather than unlink_file() below to avoid the check
816 if (CVS_UNLINK(tmp) < 0)
817 error (0, errno, "cannot remove %s", tmp);
820 if( rev1_cache != NULL )
822 if( CVS_UNLINK( rev1_cache ) < 0 )
823 error( 0, errno, "cannot remove %s", rev1_cache );
828 diff_mark_errors (err);
833 * Remember the exit status for each file.
836 diff_mark_errors (err)
839 if (err > diff_errors)
844 * Print a warm fuzzy message when we enter a dir
846 * Don't try to diff directories that don't exist! -- DW
850 diff_dirproc (callerdat, dir, pos_repos, update_dir, entries)
853 const char *pos_repos;
854 const char *update_dir;
857 /* XXX - check for dirs we don't want to process??? */
859 /* YES ... for instance dirs that don't exist!!! -- DW */
864 error (0, 0, "Diffing %s", update_dir);
869 * Concoct the proper exit status - done with files
873 diff_filesdoneproc (callerdat, err, repos, update_dir, entries)
877 const char *update_dir;
880 return (diff_errors);
884 * Concoct the proper exit status - leaving directories
888 diff_dirleaveproc (callerdat, dir, err, update_dir, entries)
892 const char *update_dir;
895 return (diff_errors);
899 * verify that a file is different
901 static enum diff_file
902 diff_file_nodiff( finfo, vers, empty_file, rev1_cache )
903 struct file_info *finfo;
905 enum diff_file empty_file;
906 char **rev1_cache; /* Cache the content of rev1 if we have to look
913 /* free up any old use_rev* variables and reset 'em */
918 use_rev1 = use_rev2 = (char *) NULL;
920 if (diff_rev1 || diff_date1)
922 /* special handling for TAG_HEAD XXX */
923 if (diff_rev1 && strcmp (diff_rev1, TAG_HEAD) == 0)
925 if (vers->vn_rcs != NULL && vers->srcfile != NULL)
926 use_rev1 = RCS_branch_head (vers->srcfile, vers->vn_rcs);
930 xvers = Version_TS (finfo, NULL, diff_rev1, diff_date1, 1, 0);
931 if (xvers->vn_rcs != NULL)
932 use_rev1 = xstrdup (xvers->vn_rcs);
933 freevers_ts (&xvers);
936 if (diff_rev2 || diff_date2)
938 /* special handling for TAG_HEAD XXX */
939 if (diff_rev2 && strcmp (diff_rev2, TAG_HEAD) == 0)
941 if (vers->vn_rcs != NULL && vers->srcfile != NULL)
942 use_rev2 = RCS_branch_head (vers->srcfile, vers->vn_rcs);
946 xvers = Version_TS (finfo, NULL, diff_rev2, diff_date2, 1, 0);
947 if (xvers->vn_rcs != NULL)
948 use_rev2 = xstrdup (xvers->vn_rcs);
949 freevers_ts (&xvers);
952 if( use_rev1 == NULL || RCS_isdead( vers->srcfile, use_rev1 ) )
954 /* The first revision does not exist. If EMPTY_FILES is
955 true, treat this as an added file. Otherwise, warn
956 about the missing tag. */
957 if( use_rev2 == NULL || RCS_isdead( vers->srcfile, use_rev2 ) )
958 /* At least in the case where DIFF_REV1 and DIFF_REV2
959 * are both numeric (and non-existant (NULL), as opposed to
960 * dead?), we should be returning some kind of error (see
961 * basicb-8a0 in testsuite). The symbolic case may be more
967 if( use_rev1 != NULL )
972 "Tag %s refers to a dead (removed) revision in file `%s'.",
973 diff_rev1, finfo->fullname );
978 "Date %s refers to a dead (removed) revision in file `%s'.",
979 diff_date1, finfo->fullname );
982 "No comparison available. Pass `-N' to `%s diff'?",
986 error (0, 0, "tag %s is not in file %s", diff_rev1,
989 error (0, 0, "no revision for date %s in file %s",
990 diff_date1, finfo->fullname);
994 assert( use_rev1 != NULL );
995 if( use_rev2 == NULL || RCS_isdead( vers->srcfile, use_rev2 ) )
997 /* The second revision does not exist. If EMPTY_FILES is
998 true, treat this as a removed file. Otherwise warn
999 about the missing tag. */
1001 return DIFF_REMOVED;
1002 if( use_rev2 != NULL )
1007 "Tag %s refers to a dead (removed) revision in file `%s'.",
1008 diff_rev2, finfo->fullname );
1013 "Date %s refers to a dead (removed) revision in file `%s'.",
1014 diff_date2, finfo->fullname );
1017 "No comparison available. Pass `-N' to `%s diff'?",
1021 error (0, 0, "tag %s is not in file %s", diff_rev2,
1024 error (0, 0, "no revision for date %s in file %s",
1025 diff_date2, finfo->fullname);
1028 /* Now, see if we really need to do the diff. We can't assume that the
1029 * files are different when the revs are.
1031 assert( use_rev2 != NULL );
1032 if( strcmp (use_rev1, use_rev2) == 0 )
1034 /* else fall through and do the diff */
1037 /* If we had a r1/d1 & r2/d2, then at this point we must have a C3P0...
1038 * err... ok, then both rev1 & rev2 must have resolved to an existing,
1039 * live version due to if statement we just closed.
1041 assert (!(diff_rev2 || diff_date2) || (use_rev1 && use_rev2));
1043 if ((diff_rev1 || diff_date1) &&
1044 (use_rev1 == NULL || RCS_isdead (vers->srcfile, use_rev1)))
1046 /* The first revision does not exist, and no second revision
1050 if (empty_file == DIFF_REMOVED)
1052 if( user_file_rev && use_rev2 == NULL )
1053 use_rev2 = xstrdup( user_file_rev );
1056 if( use_rev1 != NULL )
1061 "Tag %s refers to a dead (removed) revision in file `%s'.",
1062 diff_rev1, finfo->fullname );
1067 "Date %s refers to a dead (removed) revision in file `%s'.",
1068 diff_date1, finfo->fullname );
1071 "No comparison available. Pass `-N' to `%s diff'?",
1074 else if ( diff_rev1 )
1075 error( 0, 0, "tag %s is not in file %s", diff_rev1,
1078 error( 0, 0, "no revision for date %s in file %s",
1079 diff_date1, finfo->fullname );
1083 assert( !diff_rev1 || use_rev1 );
1087 /* drop user_file_rev into first unused use_rev */
1089 use_rev1 = xstrdup (user_file_rev);
1091 use_rev2 = xstrdup (user_file_rev);
1092 /* and if not, it wasn't needed anyhow */
1093 user_file_rev = NULL;
1096 /* Now, see if we really need to do the diff. We can't assume that the
1097 * files are different when the revs are.
1099 if( use_rev1 && use_rev2)
1101 if (strcmp (use_rev1, use_rev2) == 0)
1103 /* Fall through and do the diff. */
1105 /* Don't want to do the timestamp check with both use_rev1 & use_rev2 set.
1106 * The timestamp check is just for the default case of diffing the
1107 * workspace file against its base revision.
1109 else if( use_rev1 == NULL
1110 || ( vers->vn_user != NULL
1111 && strcmp( use_rev1, vers->vn_user ) == 0 ) )
1113 if (empty_file == DIFF_DIFFERENT
1114 && vers->ts_user != NULL
1115 && strcmp (vers->ts_rcs, vers->ts_user) == 0
1116 && (!(*options) || strcmp (options, vers->options) == 0))
1120 if (use_rev1 == NULL
1121 && (vers->vn_user[0] != '0' || vers->vn_user[1] != '\0'))
1123 if (vers->vn_user[0] == '-')
1124 use_rev1 = xstrdup (vers->vn_user + 1);
1126 use_rev1 = xstrdup (vers->vn_user);
1130 /* If we already know that the file is being added or removed,
1131 then we don't want to do an actual file comparison here. */
1132 if (empty_file != DIFF_DIFFERENT)
1136 * Run a quick cmp to see if we should bother with a full diff.
1139 retcode = RCS_cmp_file( vers->srcfile, use_rev1, rev1_cache,
1140 use_rev2, *options ? options : vers->options,
1143 return retcode == 0 ? DIFF_SAME : DIFF_DIFFERENT;