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