xref: /netbsd-src/external/gpl2/xcvs/dist/src/diff.c (revision 5a6c14c844c4c665da5632061aebde7bb2cb5766)
1a7c91847Schristos /*
2a7c91847Schristos  * Copyright (C) 1986-2005 The Free Software Foundation, Inc.
3a7c91847Schristos  *
4a7c91847Schristos  * Portions Copyright (C) 1998-2005 Derek Price, Ximbiot <http://ximbiot.com>,
5a7c91847Schristos  *                                  and others.
6a7c91847Schristos  *
7a7c91847Schristos  * Portions Copyright (C) 1992, Brian Berliner and Jeff Polk
8a7c91847Schristos  * Portions Copyright (C) 1989-1992, Brian Berliner
9a7c91847Schristos  *
10a7c91847Schristos  * You may distribute under the terms of the GNU General Public License as
11a7c91847Schristos  * specified in the README file that comes with the CVS source distribution.
12a7c91847Schristos  *
13a7c91847Schristos  * Difference
14a7c91847Schristos  *
15a7c91847Schristos  * Run diff against versions in the repository.  Options that are specified are
16a7c91847Schristos  * passed on directly to "rcsdiff".
17a7c91847Schristos  *
18a7c91847Schristos  * Without any file arguments, runs diff against all the currently modified
19a7c91847Schristos  * files.
20a7c91847Schristos  */
21*5a6c14c8Schristos #include <sys/cdefs.h>
22*5a6c14c8Schristos __RCSID("$NetBSD: diff.c,v 1.4 2016/05/17 14:00:09 christos Exp $");
23a7c91847Schristos 
24a7c91847Schristos #include "cvs.h"
25a7c91847Schristos 
26a7c91847Schristos enum diff_file
27a7c91847Schristos {
28a7c91847Schristos     DIFF_ERROR,
29a7c91847Schristos     DIFF_ADDED,
30a7c91847Schristos     DIFF_REMOVED,
31a7c91847Schristos     DIFF_DIFFERENT,
32a7c91847Schristos     DIFF_SAME
33a7c91847Schristos };
34a7c91847Schristos 
35a7c91847Schristos static Dtype diff_dirproc (void *callerdat, const char *dir,
36a7c91847Schristos                            const char *pos_repos, const char *update_dir,
37a7c91847Schristos                            List *entries);
38a7c91847Schristos static int diff_filesdoneproc (void *callerdat, int err,
39a7c91847Schristos                                const char *repos, const char *update_dir,
40a7c91847Schristos                                List *entries);
41a7c91847Schristos static int diff_dirleaveproc (void *callerdat, const char *dir,
42a7c91847Schristos                               int err, const char *update_dir,
43a7c91847Schristos                               List *entries);
44a7c91847Schristos static enum diff_file diff_file_nodiff (struct file_info *finfo, Vers_TS *vers,
45a7c91847Schristos                                         enum diff_file, char **rev1_cache );
46a7c91847Schristos static int diff_fileproc (void *callerdat, struct file_info *finfo);
47a7c91847Schristos static void diff_mark_errors (int err);
48a7c91847Schristos 
49a7c91847Schristos 
50a7c91847Schristos /* Global variables.  Would be cleaner if we just put this stuff in a
51a7c91847Schristos    struct like log.c does.  */
52a7c91847Schristos 
53a7c91847Schristos /* Command line tags, from -r option.  Points into argv.  */
54a7c91847Schristos static char *diff_rev1, *diff_rev2;
55a7c91847Schristos /* Command line dates, from -D option.  Malloc'd.  */
56a7c91847Schristos static char *diff_date1, *diff_date2;
57a7c91847Schristos static char *use_rev1, *use_rev2;
58a7c91847Schristos static int have_rev1_label, have_rev2_label;
59a7c91847Schristos 
60a7c91847Schristos /* Revision of the user file, if it is unchanged from something in the
61a7c91847Schristos    repository and we want to use that fact.  */
62a7c91847Schristos static char *user_file_rev;
63a7c91847Schristos 
64a7c91847Schristos static char *options;
65a7c91847Schristos static char **diff_argv;
66a7c91847Schristos static int diff_argc;
67a7c91847Schristos static size_t diff_arg_allocated;
68a7c91847Schristos static int diff_errors;
69a7c91847Schristos static int empty_files;
70a7c91847Schristos 
71a7c91847Schristos static const char *const diff_usage[] =
72a7c91847Schristos {
73a7c91847Schristos     "Usage: %s %s [-lR] [-k kopt] [format_options]\n",
74a7c91847Schristos     "    [[-r rev1 | -D date1] [-r rev2 | -D date2]] [files...] \n",
75a7c91847Schristos     "\t-l\tLocal directory only, not recursive\n",
76a7c91847Schristos     "\t-R\tProcess directories recursively.\n",
77a7c91847Schristos     "\t-k kopt\tSpecify keyword expansion mode.\n",
78a7c91847Schristos     "\t-D d1\tDiff revision for date against working file.\n",
79a7c91847Schristos     "\t-D d2\tDiff rev1/date1 against date2.\n",
80a7c91847Schristos     "\t-r rev1\tDiff revision for rev1 against working file.\n",
81a7c91847Schristos     "\t-r rev2\tDiff rev1/date1 against rev2.\n",
82a7c91847Schristos     "\nformat_options:\n",
83a7c91847Schristos     "  -i  --ignore-case  Consider upper- and lower-case to be the same.\n",
84a7c91847Schristos     "  -w  --ignore-all-space  Ignore all white space.\n",
85a7c91847Schristos     "  -b  --ignore-space-change  Ignore changes in the amount of white space.\n",
86a7c91847Schristos     "  -B  --ignore-blank-lines  Ignore changes whose lines are all blank.\n",
87a7c91847Schristos     "  -I RE  --ignore-matching-lines=RE  Ignore changes whose lines all match RE.\n",
88a7c91847Schristos     "  --binary  Read and write data in binary mode.\n",
89a7c91847Schristos     "  -a  --text  Treat all files as text.\n\n",
90a7c91847Schristos     "  -c  -C NUM  --context[=NUM]  Output NUM (default 2) lines of copied context.\n",
91a7c91847Schristos     "  -u  -U NUM  --unified[=NUM]  Output NUM (default 2) lines of unified context.\n",
92a7c91847Schristos     "    -NUM  Use NUM context lines.\n",
93a7c91847Schristos     "    -L LABEL  --label LABEL  Use LABEL instead of file name.\n",
94a7c91847Schristos     "    -p  --show-c-function  Show which C function each change is in.\n",
95a7c91847Schristos     "    -F RE  --show-function-line=RE  Show the most recent line matching RE.\n",
96a7c91847Schristos     "  --brief  Output only whether files differ.\n",
97a7c91847Schristos     "  -e  --ed  Output an ed script.\n",
98a7c91847Schristos     "  -f  --forward-ed  Output something like an ed script in forward order.\n",
99a7c91847Schristos     "  -n  --rcs  Output an RCS format diff.\n",
100a7c91847Schristos     "  -y  --side-by-side  Output in two columns.\n",
101a7c91847Schristos     "    -W NUM  --width=NUM  Output at most NUM (default 130) characters per line.\n",
102a7c91847Schristos     "    --left-column  Output only the left column of common lines.\n",
103a7c91847Schristos     "    --suppress-common-lines  Do not output common lines.\n",
104a7c91847Schristos     "  --ifdef=NAME  Output merged file to show `#ifdef NAME' diffs.\n",
105a7c91847Schristos     "  --GTYPE-group-format=GFMT  Similar, but format GTYPE input groups with GFMT.\n",
106a7c91847Schristos     "  --line-format=LFMT  Similar, but format all input lines with LFMT.\n",
107a7c91847Schristos     "  --LTYPE-line-format=LFMT  Similar, but format LTYPE input lines with LFMT.\n",
108a7c91847Schristos     "    LTYPE is `old', `new', or `unchanged'.  GTYPE is LTYPE or `changed'.\n",
109a7c91847Schristos     "    GFMT may contain:\n",
110a7c91847Schristos     "      %%<  lines from FILE1\n",
111a7c91847Schristos     "      %%>  lines from FILE2\n",
112a7c91847Schristos     "      %%=  lines common to FILE1 and FILE2\n",
113a7c91847Schristos     "      %%[-][WIDTH][.[PREC]]{doxX}LETTER  printf-style spec for LETTER\n",
114a7c91847Schristos     "        LETTERs are as follows for new group, lower case for old group:\n",
115a7c91847Schristos     "          F  first line number\n",
116a7c91847Schristos     "          L  last line number\n",
117a7c91847Schristos     "          N  number of lines = L-F+1\n",
118a7c91847Schristos     "          E  F-1\n",
119a7c91847Schristos     "          M  L+1\n",
120a7c91847Schristos     "    LFMT may contain:\n",
121a7c91847Schristos     "      %%L  contents of line\n",
122a7c91847Schristos     "      %%l  contents of line, excluding any trailing newline\n",
123a7c91847Schristos     "      %%[-][WIDTH][.[PREC]]{doxX}n  printf-style spec for input line number\n",
124a7c91847Schristos     "    Either GFMT or LFMT may contain:\n",
125a7c91847Schristos     "      %%%%  %%\n",
126a7c91847Schristos     "      %%c'C'  the single character C\n",
127a7c91847Schristos     "      %%c'\\OOO'  the character with octal code OOO\n\n",
128a7c91847Schristos     "  -t  --expand-tabs  Expand tabs to spaces in output.\n",
129a7c91847Schristos     "  -T  --initial-tab  Make tabs line up by prepending a tab.\n\n",
130a7c91847Schristos     "  -N  --new-file  Treat absent files as empty.\n",
131a7c91847Schristos     "  -s  --report-identical-files  Report when two files are the same.\n",
132a7c91847Schristos     "  --horizon-lines=NUM  Keep NUM lines of the common prefix and suffix.\n",
133a7c91847Schristos     "  -d  --minimal  Try hard to find a smaller set of changes.\n",
134a7c91847Schristos     "  -H  --speed-large-files  Assume large files and many scattered small changes.\n",
135a7c91847Schristos     "\n(Specify the --help global option for a list of other help options)\n",
136a7c91847Schristos     NULL
137a7c91847Schristos };
138a7c91847Schristos 
139a7c91847Schristos /* I copied this array directly out of diff.c in diffutils 2.7, after
140a7c91847Schristos    removing the following entries, none of which seem relevant to use
141a7c91847Schristos    with CVS:
142a7c91847Schristos      --help
143a7c91847Schristos      --version (-v)
144a7c91847Schristos      --recursive (-r)
145a7c91847Schristos      --unidirectional-new-file (-P)
146a7c91847Schristos      --starting-file (-S)
147a7c91847Schristos      --exclude (-x)
148a7c91847Schristos      --exclude-from (-X)
149a7c91847Schristos      --sdiff-merge-assist
150a7c91847Schristos      --paginate (-l)  (doesn't work with library callbacks)
151a7c91847Schristos 
152a7c91847Schristos    I changed the options which take optional arguments (--context and
153a7c91847Schristos    --unified) to return a number rather than a letter, so that the
154a7c91847Schristos    optional argument could be handled more easily.  I changed the
155a7c91847Schristos    --brief and --ifdef options to return numbers, since -q  and -D mean
156a7c91847Schristos    something else to cvs diff.
157a7c91847Schristos 
158a7c91847Schristos    The numbers 129- that appear in the fourth element of some entries
159a7c91847Schristos    tell the big switch in `diff' how to process those options. -- Ian
160a7c91847Schristos 
161a7c91847Schristos    The following options, which diff lists as "An alias, no longer
162a7c91847Schristos    recommended" have been removed: --file-label --entire-new-file
163a7c91847Schristos    --ascii --print.  */
164a7c91847Schristos 
165a7c91847Schristos static struct option const longopts[] =
166a7c91847Schristos {
167a7c91847Schristos     {"ignore-blank-lines", 0, 0, 'B'},
168a7c91847Schristos     {"context", 2, 0, 143},
169a7c91847Schristos     {"ifdef", 1, 0, 131},
170a7c91847Schristos     {"show-function-line", 1, 0, 'F'},
171a7c91847Schristos     {"speed-large-files", 0, 0, 'H'},
172a7c91847Schristos     {"ignore-matching-lines", 1, 0, 'I'},
173a7c91847Schristos     {"label", 1, 0, 'L'},
174a7c91847Schristos     {"new-file", 0, 0, 'N'},
175a7c91847Schristos     {"initial-tab", 0, 0, 'T'},
176a7c91847Schristos     {"width", 1, 0, 'W'},
177a7c91847Schristos     {"text", 0, 0, 'a'},
178a7c91847Schristos     {"ignore-space-change", 0, 0, 'b'},
179a7c91847Schristos     {"minimal", 0, 0, 'd'},
180a7c91847Schristos     {"ed", 0, 0, 'e'},
181a7c91847Schristos     {"forward-ed", 0, 0, 'f'},
182a7c91847Schristos     {"ignore-case", 0, 0, 'i'},
183a7c91847Schristos     {"rcs", 0, 0, 'n'},
184a7c91847Schristos     {"show-c-function", 0, 0, 'p'},
185a7c91847Schristos 
186a7c91847Schristos     /* This is a potentially very useful option, except the output is so
187a7c91847Schristos        silly.  It would be much better for it to look like "cvs rdiff -s"
188a7c91847Schristos        which displays all the same info, minus quite a few lines of
189a7c91847Schristos        extraneous garbage.  */
190a7c91847Schristos     {"brief", 0, 0, 145},
191a7c91847Schristos 
192a7c91847Schristos     {"report-identical-files", 0, 0, 's'},
193a7c91847Schristos     {"expand-tabs", 0, 0, 't'},
194a7c91847Schristos     {"ignore-all-space", 0, 0, 'w'},
195a7c91847Schristos     {"side-by-side", 0, 0, 'y'},
196a7c91847Schristos     {"unified", 2, 0, 146},
197a7c91847Schristos     {"left-column", 0, 0, 129},
198a7c91847Schristos     {"suppress-common-lines", 0, 0, 130},
199a7c91847Schristos     {"old-line-format", 1, 0, 132},
200a7c91847Schristos     {"new-line-format", 1, 0, 133},
201a7c91847Schristos     {"unchanged-line-format", 1, 0, 134},
202a7c91847Schristos     {"line-format", 1, 0, 135},
203a7c91847Schristos     {"old-group-format", 1, 0, 136},
204a7c91847Schristos     {"new-group-format", 1, 0, 137},
205a7c91847Schristos     {"unchanged-group-format", 1, 0, 138},
206a7c91847Schristos     {"changed-group-format", 1, 0, 139},
207a7c91847Schristos     {"horizon-lines", 1, 0, 140},
208a7c91847Schristos     {"binary", 0, 0, 142},
209a7c91847Schristos     {0, 0, 0, 0}
210a7c91847Schristos };
211a7c91847Schristos 
212a7c91847Schristos 
213a7c91847Schristos 
214a7c91847Schristos /* Add one of OPT or LONGOPT, and ARGUMENT, when present, to global DIFF_ARGV.
215a7c91847Schristos  *
216a7c91847Schristos  * INPUTS
217a7c91847Schristos  *   opt		A character option representation.
218a7c91847Schristos  *   longopt		A long option name.
219a7c91847Schristos  *   argument		Optional option argument.
220a7c91847Schristos  *
221a7c91847Schristos  * GLOBALS
222a7c91847Schristos  *   diff_argc		The number of arguments in DIFF_ARGV.
223a7c91847Schristos  *   diff_argv		Array of argument strings.
224a7c91847Schristos  *   diff_arg_allocated	Allocated length of DIFF_ARGV.
225a7c91847Schristos  *
226a7c91847Schristos  * NOTES
227a7c91847Schristos  *   Behavior when both OPT & LONGOPT are provided is undefined.
228a7c91847Schristos  *
229a7c91847Schristos  * RETURNS
230a7c91847Schristos  *   Nothing.
231a7c91847Schristos  */
232a7c91847Schristos static void
add_diff_args(char opt,const char * longopt,const char * argument)233a7c91847Schristos add_diff_args (char opt, const char *longopt, const char *argument)
234a7c91847Schristos {
235a7c91847Schristos     char *tmp;
236a7c91847Schristos 
237a7c91847Schristos     /* Add opt or longopt to diff_arv.  */
238a7c91847Schristos     assert (opt || (longopt && *longopt));
239a7c91847Schristos     assert (!(opt && (longopt && *longopt)));
240a7c91847Schristos     if (opt) tmp = Xasprintf ("-%c", opt);
241a7c91847Schristos     else tmp = Xasprintf ("--%s", longopt);
242a7c91847Schristos     run_add_arg_p (&diff_argc, &diff_arg_allocated, &diff_argv, tmp);
243a7c91847Schristos     free (tmp);
244a7c91847Schristos 
245a7c91847Schristos     /* When present, add ARGUMENT to DIFF_ARGV.  */
246a7c91847Schristos     if (argument)
247a7c91847Schristos 	run_add_arg_p (&diff_argc, &diff_arg_allocated, &diff_argv, argument);
248a7c91847Schristos }
249a7c91847Schristos 
250a7c91847Schristos 
251a7c91847Schristos 
252a7c91847Schristos /* CVS 1.9 and similar versions seemed to have pretty weird handling
253a7c91847Schristos    of -y and -T.  In the cases where it called rcsdiff,
254a7c91847Schristos    they would have the meanings mentioned below.  In the cases where it
255a7c91847Schristos    called diff, they would have the meanings mentioned in "longopts".
256a7c91847Schristos    Noone seems to have missed them, so I think the right thing to do is
257a7c91847Schristos    just to remove the options altogether (which I have done).
258a7c91847Schristos 
259a7c91847Schristos    In the case of -z and -q, "cvs diff" did not accept them even back
260a7c91847Schristos    when we called rcsdiff (at least, it hasn't accepted them
261a7c91847Schristos    recently).
262a7c91847Schristos 
263a7c91847Schristos    In comparing rcsdiff to the new CVS implementation, I noticed that
264a7c91847Schristos    the following rcsdiff flags are not handled by CVS diff:
265a7c91847Schristos 
266a7c91847Schristos 	   -y: perform diff even when the requested revisions are the
267a7c91847Schristos 		   same revision number
268a7c91847Schristos 	   -q: run quietly
269a7c91847Schristos 	   -T: preserve modification time on the RCS file
270a7c91847Schristos 	   -z: specify timezone for use in file labels
271a7c91847Schristos 
272a7c91847Schristos    I think these are not really relevant.  -y is undocumented even in
273a7c91847Schristos    RCS 5.7, and seems like a minor change at best.  According to RCS
274a7c91847Schristos    documentation, -T only applies when a RCS file has been modified
275a7c91847Schristos    because of lock changes; doesn't CVS sidestep RCS's entire lock
276a7c91847Schristos    structure?  -z seems to be unsupported by CVS diff, and has a
277a7c91847Schristos    different meaning as a global option anyway.  (Adding it could be
278a7c91847Schristos    a feature, but if it is left out for now, it should not break
279a7c91847Schristos    anything.)  For the purposes of producing output, CVS diff appears
280a7c91847Schristos    mostly to ignore -q.  Maybe this should be fixed, but I think it's
281a7c91847Schristos    a larger issue than the changes included here.  */
282a7c91847Schristos 
283a7c91847Schristos int
diff(int argc,char ** argv)284a7c91847Schristos diff (int argc, char **argv)
285a7c91847Schristos {
286a7c91847Schristos     int c, err = 0;
287a7c91847Schristos     int local = 0;
288a7c91847Schristos     int which;
289a7c91847Schristos     int option_index;
290a7c91847Schristos     char *diff_orig1, *diff_orig2;
291a7c91847Schristos 
292a7c91847Schristos     if (argc == -1)
293a7c91847Schristos 	usage (diff_usage);
294a7c91847Schristos 
295a7c91847Schristos     have_rev1_label = have_rev2_label = 0;
296a7c91847Schristos 
297a7c91847Schristos     /*
298a7c91847Schristos      * Note that we catch all the valid arguments here, so that we can
299a7c91847Schristos      * intercept the -r arguments for doing revision diffs; and -l/-R for a
300a7c91847Schristos      * non-recursive/recursive diff.
301a7c91847Schristos      */
302a7c91847Schristos 
303a7c91847Schristos     /* Clean out our global variables (multiroot can call us multiple
304a7c91847Schristos        times and the server can too, if the client sends several
305a7c91847Schristos        diff commands).  */
306a7c91847Schristos     run_arg_free_p (diff_argc, diff_argv);
307a7c91847Schristos     diff_argc = 0;
308a7c91847Schristos 
309a7c91847Schristos     diff_orig1 = NULL;
310a7c91847Schristos     diff_orig2 = NULL;
311a7c91847Schristos     diff_rev1 = NULL;
312a7c91847Schristos     diff_rev2 = NULL;
313a7c91847Schristos     diff_date1 = NULL;
314a7c91847Schristos     diff_date2 = NULL;
315a7c91847Schristos 
316889c434eSchristos     getoptreset ();
317a7c91847Schristos     /* FIXME: This should really be allocating an argv to be passed to diff
318a7c91847Schristos      * later rather than strcatting onto the opts variable.  We have some
319a7c91847Schristos      * handling routines that can already handle most of the argc/argv
320a7c91847Schristos      * maintenance for us and currently, if anyone were to attempt to pass a
321a7c91847Schristos      * quoted string in here, it would be split on spaces and tabs on its way
322a7c91847Schristos      * to diff.
323a7c91847Schristos      */
324a7c91847Schristos     while ((c = getopt_long (argc, argv,
325a7c91847Schristos 	       "+abcdefhilnpstuwy0123456789BHNRTC:D:F:I:L:U:W:k:r:",
326a7c91847Schristos 			     longopts, &option_index)) != -1)
327a7c91847Schristos     {
328a7c91847Schristos 	switch (c)
329a7c91847Schristos 	{
330a7c91847Schristos 	    case 'y':
331a7c91847Schristos 		add_diff_args (0, "side-by-side", NULL);
332a7c91847Schristos 		break;
333a7c91847Schristos 	    case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
334a7c91847Schristos 	    case 'h': case 'i': case 'n': case 'p': case 's': case 't':
335a7c91847Schristos 	    case 'u': case 'w':
336a7c91847Schristos             case '0': case '1': case '2': case '3': case '4': case '5':
337a7c91847Schristos             case '6': case '7': case '8': case '9':
338a7c91847Schristos 	    case 'B': case 'H': case 'T':
339a7c91847Schristos 		add_diff_args (c, NULL, NULL);
340a7c91847Schristos 		break;
341a7c91847Schristos 	    case 'L':
342a7c91847Schristos 		if (have_rev1_label++)
343a7c91847Schristos 		    if (have_rev2_label++)
344a7c91847Schristos 		    {
345a7c91847Schristos 			error (0, 0, "extra -L arguments ignored");
346a7c91847Schristos 			break;
347a7c91847Schristos 		    }
348a7c91847Schristos 		/* Fall through.  */
349a7c91847Schristos 	    case 'C': case 'F': case 'I': case 'U': case 'W':
350a7c91847Schristos 		add_diff_args (c, NULL, optarg);
351a7c91847Schristos 		break;
352a7c91847Schristos 	    case 129: case 130: case 131: case 132: case 133: case 134:
353a7c91847Schristos 	    case 135: case 136: case 137: case 138: case 139: case 140:
354a7c91847Schristos 	    case 141: case 142: case 143: case 145: case 146:
355a7c91847Schristos 		add_diff_args (0, longopts[option_index].name,
356a7c91847Schristos 			      longopts[option_index].has_arg ? optarg : NULL);
357a7c91847Schristos 		break;
358a7c91847Schristos 	    case 'R':
359a7c91847Schristos 		local = 0;
360a7c91847Schristos 		break;
361a7c91847Schristos 	    case 'l':
362a7c91847Schristos 		local = 1;
363a7c91847Schristos 		break;
364a7c91847Schristos 	    case 'k':
365a7c91847Schristos 		if (options)
366a7c91847Schristos 		    free (options);
367a7c91847Schristos 		options = RCS_check_kflag (optarg);
368a7c91847Schristos 		break;
369a7c91847Schristos 	    case 'r':
370a7c91847Schristos 		if (diff_rev2 || diff_date2)
371a7c91847Schristos 		    error (1, 0,
372a7c91847Schristos 		       "no more than two revisions/dates can be specified");
373a7c91847Schristos 		if (diff_rev1 || diff_date1)
374a7c91847Schristos 		{
375a7c91847Schristos 		    diff_orig2 = xstrdup (optarg);
376a7c91847Schristos 		    parse_tagdate (&diff_rev2, &diff_date2, optarg);
377a7c91847Schristos 		}
378a7c91847Schristos 		else
379a7c91847Schristos 		{
380a7c91847Schristos 		    diff_orig1 = xstrdup (optarg);
381a7c91847Schristos 		    parse_tagdate (&diff_rev1, &diff_date1, optarg);
382a7c91847Schristos 		}
383a7c91847Schristos 		break;
384a7c91847Schristos 	    case 'D':
385a7c91847Schristos 		if (diff_rev2 || diff_date2)
386a7c91847Schristos 		    error (1, 0,
387a7c91847Schristos 		       "no more than two revisions/dates can be specified");
388a7c91847Schristos 		if (diff_rev1 || diff_date1)
389a7c91847Schristos 		    diff_date2 = Make_Date (optarg);
390a7c91847Schristos 		else
391a7c91847Schristos 		    diff_date1 = Make_Date (optarg);
392a7c91847Schristos 		break;
393a7c91847Schristos 	    case 'N':
394a7c91847Schristos 		empty_files = 1;
395a7c91847Schristos 		break;
396a7c91847Schristos 	    case '?':
397a7c91847Schristos 	    default:
398a7c91847Schristos 		usage (diff_usage);
399a7c91847Schristos 		break;
400a7c91847Schristos 	}
401a7c91847Schristos     }
402a7c91847Schristos     argc -= optind;
403a7c91847Schristos     argv += optind;
404a7c91847Schristos 
405a7c91847Schristos     /* make sure options is non-null */
406a7c91847Schristos     if (!options)
407a7c91847Schristos 	options = xstrdup ("");
408a7c91847Schristos 
409a7c91847Schristos #ifdef CLIENT_SUPPORT
410a7c91847Schristos     if (current_parsed_root->isremote) {
411a7c91847Schristos 	/* We're the client side.  Fire up the remote server.  */
412a7c91847Schristos 	start_server ();
413a7c91847Schristos 
414a7c91847Schristos 	ign_setup ();
415a7c91847Schristos 
416a7c91847Schristos 	if (local)
417a7c91847Schristos 	    send_arg("-l");
418a7c91847Schristos 	if (empty_files)
419a7c91847Schristos 	    send_arg("-N");
420a7c91847Schristos 	send_options (diff_argc, diff_argv);
421a7c91847Schristos 	if (options[0] != '\0')
422a7c91847Schristos 	    send_arg (options);
423a7c91847Schristos 	if (diff_orig1)
424a7c91847Schristos 	    option_with_arg ("-r", diff_orig1);
425a7c91847Schristos 	else if (diff_date1)
426a7c91847Schristos 	    client_senddate (diff_date1);
427a7c91847Schristos 	if (diff_orig2)
428a7c91847Schristos 	    option_with_arg ("-r", diff_orig2);
429a7c91847Schristos 	else if (diff_date2)
430a7c91847Schristos 	    client_senddate (diff_date2);
431a7c91847Schristos 	send_arg ("--");
432a7c91847Schristos 
433a7c91847Schristos 	/* Send the current files unless diffing two revs from the archive */
434a7c91847Schristos 	if (!diff_rev2 && !diff_date2)
435a7c91847Schristos 	    send_files (argc, argv, local, 0, 0);
436a7c91847Schristos 	else
437a7c91847Schristos 	    send_files (argc, argv, local, 0, SEND_NO_CONTENTS);
438a7c91847Schristos 
439a7c91847Schristos 	send_file_names (argc, argv, SEND_EXPAND_WILD);
440a7c91847Schristos 
441a7c91847Schristos 	send_to_server ("diff\012", 0);
442a7c91847Schristos         err = get_responses_and_close ();
443a7c91847Schristos 	free (options);
444a7c91847Schristos 	options = NULL;
445a7c91847Schristos 	return err;
446a7c91847Schristos     }
447a7c91847Schristos #endif
448a7c91847Schristos 
449a7c91847Schristos     if (diff_rev1 != NULL)
450a7c91847Schristos 	tag_check_valid (diff_rev1, argc, argv, local, 0, "", false);
451a7c91847Schristos     if (diff_rev2 != NULL)
452a7c91847Schristos 	tag_check_valid (diff_rev2, argc, argv, local, 0, "", false);
453a7c91847Schristos 
454a7c91847Schristos     which = W_LOCAL;
455a7c91847Schristos     if (diff_rev1 || diff_date1)
456a7c91847Schristos 	which |= W_REPOS | W_ATTIC;
457a7c91847Schristos 
458a7c91847Schristos     wrap_setup ();
459a7c91847Schristos 
460a7c91847Schristos     /* start the recursion processor */
461a7c91847Schristos     err = start_recursion (diff_fileproc, diff_filesdoneproc, diff_dirproc,
462a7c91847Schristos                            diff_dirleaveproc, NULL, argc, argv, local,
463a7c91847Schristos                            which, 0, CVS_LOCK_READ, NULL, 1, NULL);
464a7c91847Schristos 
465a7c91847Schristos     /* clean up */
466a7c91847Schristos     free (options);
467a7c91847Schristos     options = NULL;
468a7c91847Schristos 
469a7c91847Schristos     if (diff_date1 != NULL)
470a7c91847Schristos 	free (diff_date1);
471a7c91847Schristos     if (diff_date2 != NULL)
472a7c91847Schristos 	free (diff_date2);
473a7c91847Schristos 
474a7c91847Schristos     return err;
475a7c91847Schristos }
476a7c91847Schristos 
477a7c91847Schristos 
478a7c91847Schristos 
479a7c91847Schristos /*
480a7c91847Schristos  * Do a file diff
481a7c91847Schristos  */
482a7c91847Schristos /* ARGSUSED */
483a7c91847Schristos static int
diff_fileproc(void * callerdat,struct file_info * finfo)484a7c91847Schristos diff_fileproc (void *callerdat, struct file_info *finfo)
485a7c91847Schristos {
486a7c91847Schristos     int status, err = 2;		/* 2 == trouble, like rcsdiff */
487a7c91847Schristos     Vers_TS *vers;
488a7c91847Schristos     enum diff_file empty_file = DIFF_DIFFERENT;
489a7c91847Schristos     char *tmp = NULL;
490a7c91847Schristos     char *tocvsPath = NULL;
491a7c91847Schristos     char *fname = NULL;
492a7c91847Schristos     char *label1;
493a7c91847Schristos     char *label2;
494a7c91847Schristos     char *rev1_cache = NULL;
495a7c91847Schristos 
496a7c91847Schristos     user_file_rev = 0;
497a7c91847Schristos     vers = Version_TS (finfo, NULL, NULL, NULL, 1, 0);
498a7c91847Schristos 
499a7c91847Schristos     if (diff_rev2 || diff_date2)
500a7c91847Schristos     {
501a7c91847Schristos 	/* Skip all the following checks regarding the user file; we're
502a7c91847Schristos 	   not using it.  */
5036c8d7be6Schristos 
5046c8d7be6Schristos /* cvsacl patch */
5056c8d7be6Schristos #ifdef SERVER_SUPPORT
5066c8d7be6Schristos 	if (use_cvs_acl /* && server_active */)
5076c8d7be6Schristos 	{
5086c8d7be6Schristos 	    if (diff_rev1)
5096c8d7be6Schristos 	    {
5106c8d7be6Schristos 		if (!access_allowed (NULL, finfo->repository, diff_rev1, 5,
5116c8d7be6Schristos 				     NULL, NULL, 1))
5126c8d7be6Schristos 		{
5136c8d7be6Schristos 		    if (stop_at_first_permission_denied)
5146c8d7be6Schristos 			error (1, 0, "permission denied for %s",
5156c8d7be6Schristos 			       Short_Repository (finfo->repository));
5166c8d7be6Schristos 		    else
5176c8d7be6Schristos 			error (0, 0, "permission denied for %s/%s",
5186c8d7be6Schristos 			       Short_Repository (finfo->repository),
5196c8d7be6Schristos 			       finfo->file);
5206c8d7be6Schristos 
5216c8d7be6Schristos 		    return (0);
5226c8d7be6Schristos 		}
5236c8d7be6Schristos 	    }
5246c8d7be6Schristos 	    if (diff_rev2)
5256c8d7be6Schristos 	    {
5266c8d7be6Schristos 		if (!access_allowed (NULL, finfo->repository, diff_rev2, 5,
5276c8d7be6Schristos 				     NULL, NULL, 1))
5286c8d7be6Schristos 		{
5296c8d7be6Schristos 		    if (stop_at_first_permission_denied)
5306c8d7be6Schristos 			error (1, 0, "permission denied for %s",
5316c8d7be6Schristos 			       Short_Repository (finfo->repository));
5326c8d7be6Schristos 		    else
5336c8d7be6Schristos 			error (0, 0, "permission denied for %s/%s",
5346c8d7be6Schristos 			       Short_Repository (finfo->repository),
5356c8d7be6Schristos 			       finfo->file);
5366c8d7be6Schristos 
5376c8d7be6Schristos 		    return (0);
5386c8d7be6Schristos 		}
5396c8d7be6Schristos 	    }
5406c8d7be6Schristos 	}
5416c8d7be6Schristos #endif
5426c8d7be6Schristos 
543a7c91847Schristos     }
544a7c91847Schristos     else if (vers->vn_user == NULL)
545a7c91847Schristos     {
546a7c91847Schristos 	/* The file does not exist in the working directory.  */
547a7c91847Schristos 	if ((diff_rev1 || diff_date1)
548a7c91847Schristos 	    && vers->srcfile != NULL)
549a7c91847Schristos 	{
550a7c91847Schristos 	    /* The file does exist in the repository.  */
551a7c91847Schristos 	    if (empty_files)
552a7c91847Schristos 		empty_file = DIFF_REMOVED;
553a7c91847Schristos 	    else
554a7c91847Schristos 	    {
555a7c91847Schristos 		int exists;
556a7c91847Schristos 
557a7c91847Schristos 		exists = 0;
558a7c91847Schristos 		/* special handling for TAG_HEAD */
559a7c91847Schristos 		if (diff_rev1 && strcmp (diff_rev1, TAG_HEAD) == 0)
560a7c91847Schristos 		{
561a7c91847Schristos 		    char *head =
562a7c91847Schristos 			(vers->vn_rcs == NULL
563a7c91847Schristos 			 ? NULL
564a7c91847Schristos 			 : RCS_branch_head (vers->srcfile, vers->vn_rcs));
565a7c91847Schristos 		    exists = head != NULL && !RCS_isdead (vers->srcfile, head);
566a7c91847Schristos 		    if (head != NULL)
567a7c91847Schristos 			free (head);
568a7c91847Schristos 		}
569a7c91847Schristos 		else
570a7c91847Schristos 		{
571a7c91847Schristos 		    Vers_TS *xvers;
572a7c91847Schristos 
573a7c91847Schristos 		    xvers = Version_TS (finfo, NULL, diff_rev1, diff_date1,
574a7c91847Schristos 					1, 0);
575a7c91847Schristos 		    exists = xvers->vn_rcs && !RCS_isdead (xvers->srcfile,
576a7c91847Schristos 		                                           xvers->vn_rcs);
577a7c91847Schristos 		    freevers_ts (&xvers);
578a7c91847Schristos 		}
579a7c91847Schristos 		if (exists)
580a7c91847Schristos 		    error (0, 0,
581a7c91847Schristos 			   "%s no longer exists, no comparison available",
582a7c91847Schristos 			   finfo->fullname);
583a7c91847Schristos 		goto out;
584a7c91847Schristos 	    }
585a7c91847Schristos 	}
586a7c91847Schristos 	else
587a7c91847Schristos 	{
588a7c91847Schristos 	    error (0, 0, "I know nothing about %s", finfo->fullname);
589a7c91847Schristos 	    goto out;
590a7c91847Schristos 	}
591a7c91847Schristos     }
592a7c91847Schristos     else if (vers->vn_user[0] == '0' && vers->vn_user[1] == '\0')
593a7c91847Schristos     {
594a7c91847Schristos 	/* The file was added locally.  */
595a7c91847Schristos 	int exists = 0;
596a7c91847Schristos 
597a7c91847Schristos 	if (vers->srcfile != NULL)
598a7c91847Schristos 	{
599a7c91847Schristos 	    /* The file does exist in the repository.  */
600a7c91847Schristos 
601a7c91847Schristos 	    if (diff_rev1 || diff_date1)
602a7c91847Schristos 	    {
603a7c91847Schristos 		/* special handling for TAG_HEAD */
604a7c91847Schristos 		if (diff_rev1 && strcmp (diff_rev1, TAG_HEAD) == 0)
605a7c91847Schristos 		{
606a7c91847Schristos 		    char *head =
607a7c91847Schristos 			(vers->vn_rcs == NULL
608a7c91847Schristos 			 ? NULL
609a7c91847Schristos 			 : RCS_branch_head (vers->srcfile, vers->vn_rcs));
610a7c91847Schristos 		    exists = head && !RCS_isdead (vers->srcfile, head);
611a7c91847Schristos 		    if (head != NULL)
612a7c91847Schristos 			free (head);
613a7c91847Schristos 		}
614a7c91847Schristos 		else
615a7c91847Schristos 		{
616a7c91847Schristos 		    Vers_TS *xvers;
617a7c91847Schristos 
618a7c91847Schristos 		    xvers = Version_TS (finfo, NULL, diff_rev1, diff_date1,
619a7c91847Schristos 					1, 0);
620a7c91847Schristos 		    exists = xvers->vn_rcs
621a7c91847Schristos 		             && !RCS_isdead (xvers->srcfile, xvers->vn_rcs);
622a7c91847Schristos 		    freevers_ts (&xvers);
623a7c91847Schristos 		}
624a7c91847Schristos 	    }
625a7c91847Schristos 	    else
626a7c91847Schristos 	    {
627a7c91847Schristos 		/* The file was added locally, but an RCS archive exists.  Our
628a7c91847Schristos 		 * base revision must be dead.
629a7c91847Schristos 		 */
630a7c91847Schristos 		/* No need to set, exists = 0, here.  That's the default.  */
631a7c91847Schristos 	    }
632a7c91847Schristos 	}
633a7c91847Schristos 	if (!exists)
634a7c91847Schristos 	{
635a7c91847Schristos 	    /* If we got here, then either the RCS archive does not exist or
636a7c91847Schristos 	     * the relevant revision is dead.
637a7c91847Schristos 	     */
638a7c91847Schristos 	    if (empty_files)
639a7c91847Schristos 		empty_file = DIFF_ADDED;
640a7c91847Schristos 	    else
641a7c91847Schristos 	    {
642a7c91847Schristos 		error (0, 0, "%s is a new entry, no comparison available",
643a7c91847Schristos 		       finfo->fullname);
644a7c91847Schristos 		goto out;
645a7c91847Schristos 	    }
646a7c91847Schristos 	}
647a7c91847Schristos     }
648a7c91847Schristos     else if (vers->vn_user[0] == '-')
649a7c91847Schristos     {
650a7c91847Schristos 	if (empty_files)
651a7c91847Schristos 	    empty_file = DIFF_REMOVED;
652a7c91847Schristos 	else
653a7c91847Schristos 	{
654a7c91847Schristos 	    error (0, 0, "%s was removed, no comparison available",
655a7c91847Schristos 		   finfo->fullname);
656a7c91847Schristos 	    goto out;
657a7c91847Schristos 	}
658a7c91847Schristos     }
659a7c91847Schristos     else
660a7c91847Schristos     {
661a7c91847Schristos 	if (!vers->vn_rcs && !vers->srcfile)
662a7c91847Schristos 	{
663a7c91847Schristos 	    error (0, 0, "cannot find revision control file for %s",
664a7c91847Schristos 		   finfo->fullname);
665a7c91847Schristos 	    goto out;
666a7c91847Schristos 	}
667a7c91847Schristos 	else
668a7c91847Schristos 	{
669a7c91847Schristos 	    if (vers->ts_user == NULL)
670a7c91847Schristos 	    {
671a7c91847Schristos 		error (0, 0, "cannot find %s", finfo->fullname);
672a7c91847Schristos 		goto out;
673a7c91847Schristos 	    }
674a7c91847Schristos 	    else if (!strcmp (vers->ts_user, vers->ts_rcs))
675a7c91847Schristos 	    {
676a7c91847Schristos 		/* The user file matches some revision in the repository
677a7c91847Schristos 		   Diff against the repository (for remote CVS, we might not
678a7c91847Schristos 		   have a copy of the user file around).  */
679a7c91847Schristos 		user_file_rev = vers->vn_user;
680a7c91847Schristos 	    }
681a7c91847Schristos 	}
682a7c91847Schristos     }
683a7c91847Schristos 
684a7c91847Schristos     empty_file = diff_file_nodiff (finfo, vers, empty_file, &rev1_cache);
685a7c91847Schristos     if (empty_file == DIFF_SAME)
686a7c91847Schristos     {
687a7c91847Schristos 	/* In the server case, would be nice to send a "Checked-in"
688a7c91847Schristos 	   response, so that the client can rewrite its timestamp.
689a7c91847Schristos 	   server_checked_in by itself isn't the right thing (it
690a7c91847Schristos 	   needs a server_register), but I'm not sure what is.
691a7c91847Schristos 	   It isn't clear to me how "cvs status" handles this (that
692a7c91847Schristos 	   is, for a client which sends Modified not Is-modified to
693a7c91847Schristos 	   "cvs status"), but it does.  */
694a7c91847Schristos 	err = 0;
695a7c91847Schristos 	goto out;
696a7c91847Schristos     }
697a7c91847Schristos     else if (empty_file == DIFF_ERROR)
698a7c91847Schristos 	goto out;
699a7c91847Schristos 
700a7c91847Schristos     /* Output an "Index:" line for patch to use */
701a7c91847Schristos     cvs_output ("Index: ", 0);
702a7c91847Schristos     cvs_output (finfo->fullname, 0);
703a7c91847Schristos     cvs_output ("\n", 1);
704a7c91847Schristos 
705a7c91847Schristos     tocvsPath = wrap_tocvs_process_file (finfo->file);
706a7c91847Schristos     if (tocvsPath)
707a7c91847Schristos     {
708a7c91847Schristos 	/* Backup the current version of the file to CVS/,,filename */
709a7c91847Schristos 	fname = Xasprintf (fname, "%s/%s%s", CVSADM, CVSPREFIX, finfo->file);
710a7c91847Schristos 	if (unlink_file_dir (fname) < 0)
711a7c91847Schristos 	    if (!existence_error (errno))
712a7c91847Schristos 		error (1, errno, "cannot remove %s", fname);
713a7c91847Schristos 	rename_file (finfo->file, fname);
714a7c91847Schristos 	/* Copy the wrapped file to the current directory then go to work */
715a7c91847Schristos 	copy_file (tocvsPath, finfo->file);
716a7c91847Schristos     }
717a7c91847Schristos 
718a7c91847Schristos     /* Set up file labels appropriate for compatibility with the Larry Wall
719a7c91847Schristos      * implementation of patch if the user didn't specify.  This is irrelevant
720a7c91847Schristos      * according to the POSIX.2 specification.
721a7c91847Schristos      */
722a7c91847Schristos     label1 = NULL;
723a7c91847Schristos     label2 = NULL;
724a7c91847Schristos     /* The user cannot set the rev2 label without first setting the rev1
725a7c91847Schristos      * label.
726a7c91847Schristos      */
727a7c91847Schristos     if (!have_rev2_label)
728a7c91847Schristos     {
729a7c91847Schristos 	if (empty_file == DIFF_REMOVED)
730a7c91847Schristos 	    label2 = make_file_label (DEVNULL, NULL, NULL);
731a7c91847Schristos 	else
732a7c91847Schristos 	    label2 = make_file_label (finfo->fullname, use_rev2,
733a7c91847Schristos 	                              vers->srcfile);
734a7c91847Schristos 	if (!have_rev1_label)
735a7c91847Schristos 	{
736a7c91847Schristos 	    if (empty_file == DIFF_ADDED)
737a7c91847Schristos 		label1 = make_file_label (DEVNULL, NULL, NULL);
738a7c91847Schristos 	    else
739a7c91847Schristos 		label1 = make_file_label (finfo->fullname, use_rev1,
740a7c91847Schristos 		                          vers->srcfile);
741a7c91847Schristos 	}
742a7c91847Schristos     }
743a7c91847Schristos 
744a7c91847Schristos     if (empty_file == DIFF_ADDED || empty_file == DIFF_REMOVED)
745a7c91847Schristos     {
746a7c91847Schristos 	/* This is fullname, not file, possibly despite the POSIX.2
747a7c91847Schristos 	 * specification, because that's the way all the Larry Wall
748a7c91847Schristos 	 * implementations of patch (are there other implementations?) want
749a7c91847Schristos 	 * things and the POSIX.2 spec appears to leave room for this.
750a7c91847Schristos 	 */
751a7c91847Schristos 	cvs_output ("\
752a7c91847Schristos ===================================================================\n\
753a7c91847Schristos RCS file: ", 0);
754a7c91847Schristos 	cvs_output (finfo->fullname, 0);
755a7c91847Schristos 	cvs_output ("\n", 1);
756a7c91847Schristos 
757a7c91847Schristos 	cvs_output ("diff -N ", 0);
758a7c91847Schristos 	cvs_output (finfo->fullname, 0);
759a7c91847Schristos 	cvs_output ("\n", 1);
760a7c91847Schristos 
761a7c91847Schristos 	if (empty_file == DIFF_ADDED)
762a7c91847Schristos 	{
763a7c91847Schristos 	    if (use_rev2 == NULL)
764a7c91847Schristos                 status = diff_exec (DEVNULL, finfo->file, label1, label2,
765a7c91847Schristos 				    diff_argc, diff_argv, RUN_TTY);
766a7c91847Schristos 	    else
767a7c91847Schristos 	    {
768a7c91847Schristos 		int retcode;
769a7c91847Schristos 
770a7c91847Schristos 		tmp = cvs_temp_name ();
771a7c91847Schristos 		retcode = RCS_checkout (vers->srcfile, NULL, use_rev2, NULL,
772a7c91847Schristos 					*options ? options : vers->options,
773a7c91847Schristos 					tmp, NULL, NULL);
774a7c91847Schristos 		if (retcode != 0)
775a7c91847Schristos 		    goto out;
776a7c91847Schristos 
777a7c91847Schristos 		status = diff_exec (DEVNULL, tmp, label1, label2,
778a7c91847Schristos 				    diff_argc, diff_argv, RUN_TTY);
779a7c91847Schristos 	    }
780a7c91847Schristos 	}
781a7c91847Schristos 	else
782a7c91847Schristos 	{
783a7c91847Schristos 	    int retcode;
784a7c91847Schristos 
785a7c91847Schristos 	    tmp = cvs_temp_name ();
786a7c91847Schristos 	    retcode = RCS_checkout (vers->srcfile, NULL, use_rev1, NULL,
787a7c91847Schristos 				    *options ? options : vers->options,
788a7c91847Schristos 				    tmp, NULL, NULL);
789a7c91847Schristos 	    if (retcode != 0)
790a7c91847Schristos 		goto out;
791a7c91847Schristos 
792a7c91847Schristos 	    status = diff_exec (tmp, DEVNULL, label1, label2,
793a7c91847Schristos 				diff_argc, diff_argv, RUN_TTY);
794a7c91847Schristos 	}
795a7c91847Schristos     }
796a7c91847Schristos     else
797a7c91847Schristos     {
798a7c91847Schristos 	status = RCS_exec_rcsdiff (vers->srcfile, diff_argc, diff_argv,
799a7c91847Schristos                                    *options ? options : vers->options,
800a7c91847Schristos                                    use_rev1, rev1_cache, use_rev2,
801a7c91847Schristos                                    label1, label2, finfo->file);
802a7c91847Schristos 
803a7c91847Schristos     }
804a7c91847Schristos 
805a7c91847Schristos     if (label1) free (label1);
806a7c91847Schristos     if (label2) free (label2);
807a7c91847Schristos 
808a7c91847Schristos     switch (status)
809a7c91847Schristos     {
810a7c91847Schristos 	case -1:			/* fork failed */
811a7c91847Schristos 	    error (1, errno, "fork failed while diffing %s",
812a7c91847Schristos 		   vers->srcfile->path);
813a7c91847Schristos 	case 0:				/* everything ok */
814a7c91847Schristos 	    err = 0;
815a7c91847Schristos 	    break;
816a7c91847Schristos 	default:			/* other error */
817a7c91847Schristos 	    err = status;
818a7c91847Schristos 	    break;
819a7c91847Schristos     }
820a7c91847Schristos 
821a7c91847Schristos out:
822a7c91847Schristos     if( tocvsPath != NULL )
823a7c91847Schristos     {
824a7c91847Schristos 	if (unlink_file_dir (finfo->file) < 0)
825a7c91847Schristos 	    if (! existence_error (errno))
826a7c91847Schristos 		error (1, errno, "cannot remove %s", finfo->file);
827a7c91847Schristos 
828a7c91847Schristos 	rename_file (fname, finfo->file);
829a7c91847Schristos 	if (unlink_file (tocvsPath) < 0)
830a7c91847Schristos 	    error (1, errno, "cannot remove %s", tocvsPath);
831a7c91847Schristos 	free (fname);
832a7c91847Schristos     }
833a7c91847Schristos 
834a7c91847Schristos     /* Call CVS_UNLINK() rather than unlink_file() below to avoid the check
835a7c91847Schristos      * for noexec.
836a7c91847Schristos      */
837a7c91847Schristos     if (tmp != NULL)
838a7c91847Schristos     {
839a7c91847Schristos 	if (CVS_UNLINK (tmp) < 0)
840a7c91847Schristos 	    error (0, errno, "cannot remove %s", tmp);
841a7c91847Schristos 	free (tmp);
842a7c91847Schristos     }
843a7c91847Schristos     if (rev1_cache != NULL)
844a7c91847Schristos     {
845a7c91847Schristos 	if (CVS_UNLINK (rev1_cache) < 0)
846a7c91847Schristos 	    error (0, errno, "cannot remove %s", rev1_cache);
847a7c91847Schristos 	free (rev1_cache);
848a7c91847Schristos     }
849a7c91847Schristos 
850a7c91847Schristos     freevers_ts (&vers);
851a7c91847Schristos     diff_mark_errors (err);
852a7c91847Schristos     return err;
853a7c91847Schristos }
854a7c91847Schristos 
855a7c91847Schristos 
856a7c91847Schristos 
857a7c91847Schristos /*
858a7c91847Schristos  * Remember the exit status for each file.
859a7c91847Schristos  */
860a7c91847Schristos static void
diff_mark_errors(int err)861a7c91847Schristos diff_mark_errors (int err)
862a7c91847Schristos {
863a7c91847Schristos     if (err > diff_errors)
864a7c91847Schristos 	diff_errors = err;
865a7c91847Schristos }
866a7c91847Schristos 
867a7c91847Schristos 
868a7c91847Schristos 
869a7c91847Schristos /*
870a7c91847Schristos  * Print a warm fuzzy message when we enter a dir
871a7c91847Schristos  *
872a7c91847Schristos  * Don't try to diff directories that don't exist! -- DW
873a7c91847Schristos  */
874a7c91847Schristos /* ARGSUSED */
875a7c91847Schristos static Dtype
diff_dirproc(void * callerdat,const char * dir,const char * pos_repos,const char * update_dir,List * entries)876a7c91847Schristos diff_dirproc (void *callerdat, const char *dir, const char *pos_repos,
877a7c91847Schristos               const char *update_dir, List *entries)
878a7c91847Schristos {
879a7c91847Schristos     /* XXX - check for dirs we don't want to process??? */
880a7c91847Schristos 
881a7c91847Schristos     /* YES ... for instance dirs that don't exist!!! -- DW */
882a7c91847Schristos     if (!isdir (dir))
883a7c91847Schristos 	return R_SKIP_ALL;
884a7c91847Schristos 
8856c8d7be6Schristos /* cvsacl patch */
8866c8d7be6Schristos #ifdef SERVER_SUPPORT
8876c8d7be6Schristos     if (use_cvs_acl /* && server_active */)
8886c8d7be6Schristos     {
8896c8d7be6Schristos 	if (diff_rev1)
8906c8d7be6Schristos 	{
8916c8d7be6Schristos 	    if (!access_allowed (NULL, update_dir, diff_rev1, 5, NULL, NULL, 1))
8926c8d7be6Schristos 	    {
8936c8d7be6Schristos 		if (stop_at_first_permission_denied)
8946c8d7be6Schristos 		    error (1, 0, "permission denied for %s",
8956c8d7be6Schristos 			   Short_Repository (update_dir));
8966c8d7be6Schristos 		else
8976c8d7be6Schristos 		    error (0, 0, "permission denied for %s/%s",
8986c8d7be6Schristos 			   Short_Repository (update_dir), update_dir);
8996c8d7be6Schristos 
9006c8d7be6Schristos 		return (0);
9016c8d7be6Schristos 	    }
9026c8d7be6Schristos 	}
9036c8d7be6Schristos 	if (diff_rev2)
9046c8d7be6Schristos 	{
9056c8d7be6Schristos 	    if (!access_allowed (NULL, update_dir, diff_rev2, 5, NULL, NULL, 1))
9066c8d7be6Schristos 	    {
9076c8d7be6Schristos 		if (stop_at_first_permission_denied)
9086c8d7be6Schristos 		    error (1, 0, "permission denied for %s",
9096c8d7be6Schristos 			   Short_Repository (update_dir));
9106c8d7be6Schristos 		else
9116c8d7be6Schristos 		    error (0, 0, "permission denied for %s/%s",
9126c8d7be6Schristos 			   Short_Repository (update_dir), update_dir);
9136c8d7be6Schristos 
9146c8d7be6Schristos 		return (0);
9156c8d7be6Schristos 	    }
9166c8d7be6Schristos 	}
9176c8d7be6Schristos     }
9186c8d7be6Schristos #endif
919a7c91847Schristos     if (!quiet)
920a7c91847Schristos 	error (0, 0, "Diffing %s", update_dir);
921a7c91847Schristos     return R_PROCESS;
922a7c91847Schristos }
923a7c91847Schristos 
924a7c91847Schristos 
925a7c91847Schristos 
926a7c91847Schristos /*
927a7c91847Schristos  * Concoct the proper exit status - done with files
928a7c91847Schristos  */
929a7c91847Schristos /* ARGSUSED */
930a7c91847Schristos static int
diff_filesdoneproc(void * callerdat,int err,const char * repos,const char * update_dir,List * entries)931a7c91847Schristos diff_filesdoneproc (void *callerdat, int err, const char *repos,
932a7c91847Schristos                     const char *update_dir, List *entries)
933a7c91847Schristos {
934a7c91847Schristos     return diff_errors;
935a7c91847Schristos }
936a7c91847Schristos 
937a7c91847Schristos 
938a7c91847Schristos 
939a7c91847Schristos /*
940a7c91847Schristos  * Concoct the proper exit status - leaving directories
941a7c91847Schristos  */
942a7c91847Schristos /* ARGSUSED */
943a7c91847Schristos static int
diff_dirleaveproc(void * callerdat,const char * dir,int err,const char * update_dir,List * entries)944a7c91847Schristos diff_dirleaveproc (void *callerdat, const char *dir, int err,
945a7c91847Schristos                    const char *update_dir, List *entries)
946a7c91847Schristos {
947a7c91847Schristos     return diff_errors;
948a7c91847Schristos }
949a7c91847Schristos 
950a7c91847Schristos 
951a7c91847Schristos 
952a7c91847Schristos /*
953a7c91847Schristos  * verify that a file is different
954a7c91847Schristos  *
955a7c91847Schristos  * INPUTS
956a7c91847Schristos  *   finfo
957a7c91847Schristos  *   vers
958a7c91847Schristos  *   empty_file
959a7c91847Schristos  *
960a7c91847Schristos  * OUTPUTS
961a7c91847Schristos  *   rev1_cache		Cache the contents of rev1 if we look it up.
962a7c91847Schristos  */
963a7c91847Schristos static enum diff_file
diff_file_nodiff(struct file_info * finfo,Vers_TS * vers,enum diff_file empty_file,char ** rev1_cache)964a7c91847Schristos diff_file_nodiff (struct file_info *finfo, Vers_TS *vers,
965a7c91847Schristos                   enum diff_file empty_file, char **rev1_cache)
966a7c91847Schristos {
967a7c91847Schristos     Vers_TS *xvers;
968a7c91847Schristos     int retcode;
969a7c91847Schristos 
970a7c91847Schristos     TRACE (TRACE_FUNCTION, "diff_file_nodiff (%s, %d)",
971a7c91847Schristos            finfo->fullname ? finfo->fullname : "(null)", empty_file);
972a7c91847Schristos 
973a7c91847Schristos     /* free up any old use_rev* variables and reset 'em */
974a7c91847Schristos     if (use_rev1)
975a7c91847Schristos 	free (use_rev1);
976a7c91847Schristos     if (use_rev2)
977a7c91847Schristos 	free (use_rev2);
978a7c91847Schristos     use_rev1 = use_rev2 = NULL;
979a7c91847Schristos 
980a7c91847Schristos     if (diff_rev1 || diff_date1)
981a7c91847Schristos     {
982a7c91847Schristos 	/* special handling for TAG_HEAD */
983a7c91847Schristos 	if (diff_rev1 && strcmp (diff_rev1, TAG_HEAD) == 0)
984a7c91847Schristos 	{
985a7c91847Schristos 	    if (vers->vn_rcs != NULL && vers->srcfile != NULL)
986a7c91847Schristos 		use_rev1 = RCS_branch_head (vers->srcfile, vers->vn_rcs);
987a7c91847Schristos 	}
988a7c91847Schristos 	else
989a7c91847Schristos 	{
990a7c91847Schristos 	    xvers = Version_TS (finfo, NULL, diff_rev1, diff_date1, 1, 0);
991a7c91847Schristos 	    if (xvers->vn_rcs != NULL)
992a7c91847Schristos 		use_rev1 = xstrdup (xvers->vn_rcs);
993a7c91847Schristos 	    freevers_ts (&xvers);
994a7c91847Schristos 	}
995a7c91847Schristos     }
996a7c91847Schristos     if (diff_rev2 || diff_date2)
997a7c91847Schristos     {
998a7c91847Schristos 	/* special handling for TAG_HEAD */
999a7c91847Schristos 	if (diff_rev2 && strcmp (diff_rev2, TAG_HEAD) == 0)
1000a7c91847Schristos 	{
1001a7c91847Schristos 	    if (vers->vn_rcs && vers->srcfile)
1002a7c91847Schristos 		use_rev2 = RCS_branch_head (vers->srcfile, vers->vn_rcs);
1003a7c91847Schristos 	}
1004a7c91847Schristos 	else
1005a7c91847Schristos 	{
1006a7c91847Schristos 	    xvers = Version_TS (finfo, NULL, diff_rev2, diff_date2, 1, 0);
1007a7c91847Schristos 	    if (xvers->vn_rcs != NULL)
1008a7c91847Schristos 		use_rev2 = xstrdup (xvers->vn_rcs);
1009a7c91847Schristos 	    freevers_ts (&xvers);
1010a7c91847Schristos 	}
1011a7c91847Schristos 
1012a7c91847Schristos 	if (use_rev1 == NULL || RCS_isdead (vers->srcfile, use_rev1))
1013a7c91847Schristos 	{
1014a7c91847Schristos 	    /* The first revision does not exist.  If EMPTY_FILES is
1015a7c91847Schristos                true, treat this as an added file.  Otherwise, warn
1016a7c91847Schristos                about the missing tag.  */
1017a7c91847Schristos 	    if (use_rev2 == NULL || RCS_isdead (vers->srcfile, use_rev2))
1018a7c91847Schristos 		/* At least in the case where DIFF_REV1 and DIFF_REV2
1019a7c91847Schristos 		 * are both numeric (and non-existant (NULL), as opposed to
1020a7c91847Schristos 		 * dead?), we should be returning some kind of error (see
1021a7c91847Schristos 		 * basicb-8a0 in testsuite).  The symbolic case may be more
1022a7c91847Schristos 		 * complicated.
1023a7c91847Schristos 		 */
1024a7c91847Schristos 		return DIFF_SAME;
1025a7c91847Schristos 	    if (empty_files)
1026a7c91847Schristos 		return DIFF_ADDED;
1027a7c91847Schristos 	    if (use_rev1 != NULL)
1028a7c91847Schristos 	    {
1029a7c91847Schristos 		if (diff_rev1)
1030a7c91847Schristos 		{
1031a7c91847Schristos 		    error (0, 0,
1032a7c91847Schristos 		       "Tag %s refers to a dead (removed) revision in file `%s'.",
1033a7c91847Schristos 		       diff_rev1, finfo->fullname);
1034a7c91847Schristos 		}
1035a7c91847Schristos 		else
1036a7c91847Schristos 		{
1037a7c91847Schristos 		    error (0, 0,
1038a7c91847Schristos 		       "Date %s refers to a dead (removed) revision in file `%s'.",
1039a7c91847Schristos 		       diff_date1, finfo->fullname);
1040a7c91847Schristos 		}
1041a7c91847Schristos 		error (0, 0,
1042a7c91847Schristos 		       "No comparison available.  Pass `-N' to `%s diff'?",
1043a7c91847Schristos 		       program_name);
1044a7c91847Schristos 	    }
1045a7c91847Schristos 	    else if (diff_rev1)
1046a7c91847Schristos 		error (0, 0, "tag %s is not in file %s", diff_rev1,
1047a7c91847Schristos 		       finfo->fullname);
1048a7c91847Schristos 	    else
1049a7c91847Schristos 		error (0, 0, "no revision for date %s in file %s",
1050a7c91847Schristos 		       diff_date1, finfo->fullname);
1051a7c91847Schristos 	    return DIFF_ERROR;
1052a7c91847Schristos 	}
1053a7c91847Schristos 
1054a7c91847Schristos 	assert( use_rev1 != NULL );
1055a7c91847Schristos 	if( use_rev2 == NULL || RCS_isdead( vers->srcfile, use_rev2 ) )
1056a7c91847Schristos 	{
1057a7c91847Schristos 	    /* The second revision does not exist.  If EMPTY_FILES is
1058a7c91847Schristos                true, treat this as a removed file.  Otherwise warn
1059a7c91847Schristos                about the missing tag.  */
1060a7c91847Schristos 	    if (empty_files)
1061a7c91847Schristos 		return DIFF_REMOVED;
1062a7c91847Schristos 	    if( use_rev2 != NULL )
1063a7c91847Schristos 	    {
1064a7c91847Schristos 		if (diff_rev2)
1065a7c91847Schristos 		{
1066a7c91847Schristos 		    error( 0, 0,
1067a7c91847Schristos 		       "Tag %s refers to a dead (removed) revision in file `%s'.",
1068a7c91847Schristos 		       diff_rev2, finfo->fullname );
1069a7c91847Schristos 		}
1070a7c91847Schristos 		else
1071a7c91847Schristos 		{
1072a7c91847Schristos 		    error( 0, 0,
1073a7c91847Schristos 		       "Date %s refers to a dead (removed) revision in file `%s'.",
1074a7c91847Schristos 		       diff_date2, finfo->fullname );
1075a7c91847Schristos 		}
1076a7c91847Schristos 		error( 0, 0,
1077a7c91847Schristos 		       "No comparison available.  Pass `-N' to `%s diff'?",
1078a7c91847Schristos 		       program_name );
1079a7c91847Schristos 	    }
1080a7c91847Schristos 	    else if (diff_rev2)
1081a7c91847Schristos 		error (0, 0, "tag %s is not in file %s", diff_rev2,
1082a7c91847Schristos 		       finfo->fullname);
1083a7c91847Schristos 	    else
1084a7c91847Schristos 		error (0, 0, "no revision for date %s in file %s",
1085a7c91847Schristos 		       diff_date2, finfo->fullname);
1086a7c91847Schristos 	    return DIFF_ERROR;
1087a7c91847Schristos 	}
1088a7c91847Schristos 	/* Now, see if we really need to do the diff.  We can't assume that the
1089a7c91847Schristos 	 * files are different when the revs are.
1090a7c91847Schristos 	 */
1091a7c91847Schristos 	assert( use_rev2 != NULL );
1092a7c91847Schristos 	if( strcmp (use_rev1, use_rev2) == 0 )
1093a7c91847Schristos 	    return DIFF_SAME;
1094a7c91847Schristos 	/* else fall through and do the diff */
1095a7c91847Schristos     }
1096a7c91847Schristos 
1097a7c91847Schristos     /* If we had a r1/d1 & r2/d2, then at this point we must have a C3P0...
1098a7c91847Schristos      * err...  ok, then both rev1 & rev2 must have resolved to an existing,
1099a7c91847Schristos      * live version due to if statement we just closed.
1100a7c91847Schristos      */
1101a7c91847Schristos     assert (!(diff_rev2 || diff_date2) || (use_rev1 && use_rev2));
1102a7c91847Schristos 
1103a7c91847Schristos     if ((diff_rev1 || diff_date1) &&
1104a7c91847Schristos 	(use_rev1 == NULL || RCS_isdead (vers->srcfile, use_rev1)))
1105a7c91847Schristos     {
1106a7c91847Schristos 	/* The first revision does not exist, and no second revision
1107a7c91847Schristos            was given.  */
1108a7c91847Schristos 	if (empty_files)
1109a7c91847Schristos 	{
1110a7c91847Schristos 	    if (empty_file == DIFF_REMOVED)
1111a7c91847Schristos 		return DIFF_SAME;
1112a7c91847Schristos 	    if( user_file_rev && use_rev2 == NULL )
1113a7c91847Schristos 		use_rev2 = xstrdup( user_file_rev );
1114a7c91847Schristos 	    return DIFF_ADDED;
1115a7c91847Schristos 	}
1116a7c91847Schristos 	if( use_rev1 != NULL )
1117a7c91847Schristos 	{
1118a7c91847Schristos 	    if (diff_rev1)
1119a7c91847Schristos 	    {
1120a7c91847Schristos 		error( 0, 0,
1121a7c91847Schristos 		   "Tag %s refers to a dead (removed) revision in file `%s'.",
1122a7c91847Schristos 		   diff_rev1, finfo->fullname );
1123a7c91847Schristos 	    }
1124a7c91847Schristos 	    else
1125a7c91847Schristos 	    {
1126a7c91847Schristos 		error( 0, 0,
1127a7c91847Schristos 		   "Date %s refers to a dead (removed) revision in file `%s'.",
1128a7c91847Schristos 		   diff_date1, finfo->fullname );
1129a7c91847Schristos 	    }
1130a7c91847Schristos 	    error( 0, 0,
1131a7c91847Schristos 		   "No comparison available.  Pass `-N' to `%s diff'?",
1132a7c91847Schristos 		   program_name );
1133a7c91847Schristos 	}
1134a7c91847Schristos 	else if ( diff_rev1 )
1135a7c91847Schristos 	    error( 0, 0, "tag %s is not in file %s", diff_rev1,
1136a7c91847Schristos 		   finfo->fullname );
1137a7c91847Schristos 	else
1138a7c91847Schristos 	    error( 0, 0, "no revision for date %s in file %s",
1139a7c91847Schristos 		   diff_date1, finfo->fullname );
1140a7c91847Schristos 	return DIFF_ERROR;
1141a7c91847Schristos     }
1142a7c91847Schristos 
1143a7c91847Schristos     assert( !diff_rev1 || use_rev1 );
1144a7c91847Schristos 
1145a7c91847Schristos     if (user_file_rev)
1146a7c91847Schristos     {
1147a7c91847Schristos         /* drop user_file_rev into first unused use_rev */
1148a7c91847Schristos         if (!use_rev1)
1149a7c91847Schristos 	    use_rev1 = xstrdup (user_file_rev);
1150a7c91847Schristos 	else if (!use_rev2)
1151a7c91847Schristos 	    use_rev2 = xstrdup (user_file_rev);
1152a7c91847Schristos 	/* and if not, it wasn't needed anyhow */
1153a7c91847Schristos 	user_file_rev = NULL;
1154a7c91847Schristos     }
1155a7c91847Schristos 
1156a7c91847Schristos     /* Now, see if we really need to do the diff.  We can't assume that the
1157a7c91847Schristos      * files are different when the revs are.
1158a7c91847Schristos      */
1159a7c91847Schristos     if( use_rev1 && use_rev2)
1160a7c91847Schristos     {
1161a7c91847Schristos 	if (strcmp (use_rev1, use_rev2) == 0)
1162a7c91847Schristos 	    return DIFF_SAME;
1163a7c91847Schristos 	/* Fall through and do the diff. */
1164a7c91847Schristos     }
1165a7c91847Schristos     /* Don't want to do the timestamp check with both use_rev1 & use_rev2 set.
1166a7c91847Schristos      * The timestamp check is just for the default case of diffing the
1167a7c91847Schristos      * workspace file against its base revision.
1168a7c91847Schristos      */
1169a7c91847Schristos     else if( use_rev1 == NULL
1170a7c91847Schristos              || ( vers->vn_user != NULL
1171a7c91847Schristos                   && strcmp( use_rev1, vers->vn_user ) == 0 ) )
1172a7c91847Schristos     {
1173a7c91847Schristos 	if (empty_file == DIFF_DIFFERENT
1174a7c91847Schristos 	    && vers->ts_user != NULL
1175a7c91847Schristos 	    && strcmp (vers->ts_rcs, vers->ts_user) == 0
1176a7c91847Schristos 	    && (!(*options) || strcmp (options, vers->options) == 0))
1177a7c91847Schristos 	{
1178a7c91847Schristos 	    return DIFF_SAME;
1179a7c91847Schristos 	}
1180a7c91847Schristos 	if (use_rev1 == NULL
1181a7c91847Schristos 	    && (vers->vn_user[0] != '0' || vers->vn_user[1] != '\0'))
1182a7c91847Schristos 	{
1183a7c91847Schristos 	    if (vers->vn_user[0] == '-')
1184a7c91847Schristos 		use_rev1 = xstrdup (vers->vn_user + 1);
1185a7c91847Schristos 	    else
1186a7c91847Schristos 		use_rev1 = xstrdup (vers->vn_user);
1187a7c91847Schristos 	}
1188a7c91847Schristos     }
1189a7c91847Schristos 
1190a7c91847Schristos     /* If we already know that the file is being added or removed,
1191a7c91847Schristos        then we don't want to do an actual file comparison here.  */
1192a7c91847Schristos     if (empty_file != DIFF_DIFFERENT)
1193a7c91847Schristos 	return empty_file;
1194a7c91847Schristos 
1195a7c91847Schristos     /*
1196a7c91847Schristos      * Run a quick cmp to see if we should bother with a full diff.
1197a7c91847Schristos      */
1198a7c91847Schristos 
1199a7c91847Schristos     retcode = RCS_cmp_file( vers->srcfile, use_rev1, rev1_cache,
1200a7c91847Schristos                             use_rev2, *options ? options : vers->options,
1201a7c91847Schristos 			    finfo->file );
1202a7c91847Schristos 
1203a7c91847Schristos     return retcode == 0 ? DIFF_SAME : DIFF_DIFFERENT;
1204a7c91847Schristos }
1205