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