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