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