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