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