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