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