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