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