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