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