]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/cvs/src/diff.c
merge fix for boot-time hang on centos' xen
[FreeBSD/FreeBSD.git] / contrib / cvs / src / diff.c
1 /*
2  * Copyright (c) 1992, Brian Berliner and Jeff Polk
3  * Copyright (c) 1989-1992, Brian Berliner
4  * 
5  * You may distribute under the terms of the GNU General Public License as
6  * specified in the README file that comes with the CVS source distribution.
7  * 
8  * Difference
9  * 
10  * Run diff against versions in the repository.  Options that are specified are
11  * passed on directly to "rcsdiff".
12  * 
13  * Without any file arguments, runs diff against all the currently modified
14  * files.
15  *
16  * $FreeBSD$
17  */
18
19 #include <assert.h>
20 #include "cvs.h"
21
22 enum diff_file
23 {
24     DIFF_ERROR,
25     DIFF_ADDED,
26     DIFF_REMOVED,
27     DIFF_DIFFERENT,
28     DIFF_SAME
29 };
30
31 static Dtype diff_dirproc PROTO ((void *callerdat, const char *dir,
32                                   const char *pos_repos,
33                                   const char *update_dir,
34                                   List *entries));
35 static int diff_filesdoneproc PROTO ((void *callerdat, int err,
36                                       const char *repos,
37                                       const char *update_dir,
38                                       List *entries));
39 static int diff_dirleaveproc PROTO ((void *callerdat, const char *dir,
40                                      int err, const char *update_dir,
41                                      List *entries));
42 static enum diff_file diff_file_nodiff PROTO(( struct file_info *finfo,
43                                                Vers_TS *vers,
44                                                enum diff_file, 
45                                                char **rev1_cache ));
46 static int diff_fileproc PROTO ((void *callerdat, struct file_info *finfo));
47 static void diff_mark_errors PROTO((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 *diff_join1, *diff_join2;
58 static char *use_rev1, *use_rev2;
59 static int have_rev1_label, have_rev2_label;
60
61 /* Revision of the user file, if it is unchanged from something in the
62    repository and we want to use that fact.  */
63 static char *user_file_rev;
64
65 static char *options;
66 static char *opts;
67 static size_t opts_allocated = 1;
68 static int diff_errors;
69 static int empty_files = 0;
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 /* CVS 1.9 and similar versions seemed to have pretty weird handling
213    of -y and -T.  In the cases where it called rcsdiff,
214    they would have the meanings mentioned below.  In the cases where it
215    called diff, they would have the meanings mentioned in "longopts".
216    Noone seems to have missed them, so I think the right thing to do is
217    just to remove the options altogether (which I have done).
218
219    In the case of -z and -q, "cvs diff" did not accept them even back
220    when we called rcsdiff (at least, it hasn't accepted them
221    recently).
222
223    In comparing rcsdiff to the new CVS implementation, I noticed that
224    the following rcsdiff flags are not handled by CVS diff:
225
226            -y: perform diff even when the requested revisions are the
227                    same revision number
228            -q: run quietly
229            -T: preserve modification time on the RCS file
230            -z: specify timezone for use in file labels
231
232    I think these are not really relevant.  -y is undocumented even in
233    RCS 5.7, and seems like a minor change at best.  According to RCS
234    documentation, -T only applies when a RCS file has been modified
235    because of lock changes; doesn't CVS sidestep RCS's entire lock
236    structure?  -z seems to be unsupported by CVS diff, and has a
237    different meaning as a global option anyway.  (Adding it could be
238    a feature, but if it is left out for now, it should not break
239    anything.)  For the purposes of producing output, CVS diff appears
240    mostly to ignore -q.  Maybe this should be fixed, but I think it's
241    a larger issue than the changes included here.  */
242
243 int
244 diff (argc, argv)
245     int argc;
246     char **argv;
247 {
248     char tmp[50];
249     int c, err = 0;
250     int local = 0;
251     int which;
252     int option_index;
253
254     if (argc == -1)
255         usage (diff_usage);
256
257     have_rev1_label = have_rev2_label = 0;
258
259     /*
260      * Note that we catch all the valid arguments here, so that we can
261      * intercept the -r arguments for doing revision diffs; and -l/-R for a
262      * non-recursive/recursive diff.
263      */
264
265     /* Clean out our global variables (multiroot can call us multiple
266        times and the server can too, if the client sends several
267        diff commands).  */
268     if (opts == NULL)
269     {
270         opts_allocated = 1;
271         opts = xmalloc (opts_allocated);
272     }
273     opts[0] = '\0';
274     diff_rev1 = NULL;
275     diff_rev2 = NULL;
276     diff_date1 = NULL;
277     diff_date2 = NULL;
278     diff_join1 = NULL;
279     diff_join2 = NULL;
280
281     optind = 0;
282     /* FIXME: This should really be allocating an argv to be passed to diff
283      * later rather than strcatting onto the opts variable.  We have some
284      * handling routines that can already handle most of the argc/argv
285      * maintenance for us and currently, if anyone were to attempt to pass a
286      * quoted string in here, it would be split on spaces and tabs on its way
287      * to diff.
288      */
289     while ((c = getopt_long (argc, argv,
290                "+abcdefhilnpstuwy0123456789BHNRTC:D:F:I:L:U:W:k:r:j:",
291                              longopts, &option_index)) != -1)
292     {
293         switch (c)
294         {
295             case 'y':
296                 xrealloc_and_strcat (&opts, &opts_allocated, " --side-by-side");
297                 break;
298             case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
299             case 'h': case 'i': case 'n': case 'p': case 's': case 't':
300             case 'u': case 'w':
301             case '0': case '1': case '2': case '3': case '4': case '5':
302             case '6': case '7': case '8': case '9':
303             case 'B': case 'H': case 'T':
304                 (void) sprintf (tmp, " -%c", (char) c);
305                 xrealloc_and_strcat (&opts, &opts_allocated, tmp);
306                 break;
307             case 'L':
308                 if (have_rev1_label++)
309                     if (have_rev2_label++)
310                     {
311                         error (0, 0, "extra -L arguments ignored");
312                         break;
313                     }
314
315                 xrealloc_and_strcat (&opts, &opts_allocated, " -L");
316                 xrealloc_and_strcat (&opts, &opts_allocated, optarg);
317                 break;
318             case 'C': case 'F': case 'I': case 'U': case 'W':
319                 (void) sprintf (tmp, " -%c", (char) c);
320                 xrealloc_and_strcat (&opts, &opts_allocated, tmp);
321                 xrealloc_and_strcat (&opts, &opts_allocated, optarg);
322                 break;
323             case 131:
324                 /* --ifdef.  */
325                 xrealloc_and_strcat (&opts, &opts_allocated, " --ifdef=");
326                 xrealloc_and_strcat (&opts, &opts_allocated, optarg);
327                 break;
328             case 129: case 130:           case 132: case 133: case 134:
329             case 135: case 136: case 137: case 138: case 139: case 140:
330             case 141: case 142: case 143: case 145: case 146:
331                 xrealloc_and_strcat (&opts, &opts_allocated, " --");
332                 xrealloc_and_strcat (&opts, &opts_allocated,
333                                      longopts[option_index].name);
334                 if (longopts[option_index].has_arg == 1
335                     || (longopts[option_index].has_arg == 2
336                         && optarg != NULL))
337                 {
338                     xrealloc_and_strcat (&opts, &opts_allocated, "=");
339                     xrealloc_and_strcat (&opts, &opts_allocated, optarg);
340                 }
341                 break;
342             case 'R':
343                 local = 0;
344                 break;
345             case 'l':
346                 local = 1;
347                 break;
348             case 'k':
349                 if (options)
350                     free (options);
351                 options = RCS_check_kflag (optarg);
352                 break;
353             case 'j':
354                 {
355                     char *ptr;
356                     char *cpy = strdup(optarg);
357
358                     if ((ptr = strchr(optarg, ':')) != NULL)
359                         *ptr++ = 0;
360                     if (diff_rev2 != NULL || diff_date2 != NULL)
361                         error (1, 0,
362                            "no more than two revisions/dates can be specified");
363                     if (diff_rev1 != NULL || diff_date1 != NULL) {
364                         diff_join2 = cpy;
365                         diff_rev2 = optarg;
366                         diff_date2 = ptr ? Make_Date(ptr) : NULL;
367                     } else {
368                         diff_join1 = cpy;
369                         diff_rev1 = optarg;
370                         diff_date1 = ptr ? Make_Date(ptr) : NULL;
371                     }
372                 }
373                 break;
374             case 'r':
375                 if (diff_rev2 != NULL || diff_date2 != NULL)
376                     error (1, 0,
377                        "no more than two revisions/dates can be specified");
378                 if (diff_rev1 != NULL || diff_date1 != NULL)
379                     diff_rev2 = optarg;
380                 else
381                     diff_rev1 = optarg;
382                 break;
383             case 'D':
384                 if (diff_rev2 != NULL || diff_date2 != NULL)
385                     error (1, 0,
386                        "no more than two revisions/dates can be specified");
387                 if (diff_rev1 != NULL || diff_date1 != NULL)
388                     diff_date2 = Make_Date (optarg);
389                 else
390                     diff_date1 = Make_Date (optarg);
391                 break;
392             case 'N':
393                 empty_files = 1;
394                 break;
395             case '?':
396             default:
397                 usage (diff_usage);
398                 break;
399         }
400     }
401     argc -= optind;
402     argv += optind;
403
404     /* make sure options is non-null */
405     if (!options)
406         options = xstrdup ("");
407
408 #ifdef CLIENT_SUPPORT
409     if (current_parsed_root->isremote) {
410         /* We're the client side.  Fire up the remote server.  */
411         start_server ();
412         
413         ign_setup ();
414
415         if (local)
416             send_arg("-l");
417         if (empty_files)
418             send_arg("-N");
419         send_option_string (opts);
420         if (options[0] != '\0')
421             send_arg (options);
422         if (diff_join1)
423             option_with_arg ("-j", diff_join1);
424         else if (diff_rev1)
425             option_with_arg ("-r", diff_rev1);
426         else if (diff_date1)
427             client_senddate (diff_date1);
428
429         if (diff_join2)
430             option_with_arg ("-j", diff_join2);
431         else if (diff_rev2)
432             option_with_arg ("-r", diff_rev2);
433         else if (diff_date2)
434             client_senddate (diff_date2);
435         send_arg ("--");
436
437         /* Send the current files unless diffing two revs from the archive */
438         if (diff_rev2 == NULL && diff_date2 == NULL)
439             send_files (argc, argv, local, 0, 0);
440         else
441             send_files (argc, argv, local, 0, SEND_NO_CONTENTS);
442
443         send_file_names (argc, argv, SEND_EXPAND_WILD);
444
445         send_to_server ("diff\012", 0);
446         err = get_responses_and_close ();
447     } else
448 #endif
449     {
450         if (diff_rev1 != NULL)
451             tag_check_valid (diff_rev1, argc, argv, local, 0, "");
452         if (diff_rev2 != NULL)
453             tag_check_valid (diff_rev2, argc, argv, local, 0, "");
454
455         which = W_LOCAL;
456         if (diff_rev1 != NULL || diff_date1 != NULL)
457             which |= W_REPOS | W_ATTIC;
458
459         wrap_setup ();
460
461         /* start the recursion processor */
462         err = start_recursion (diff_fileproc, diff_filesdoneproc, diff_dirproc,
463                                diff_dirleaveproc, NULL, argc, argv, local,
464                                which, 0, CVS_LOCK_READ, (char *) NULL, 1,
465                                (char *) NULL);
466     }
467
468     /* clean up */
469     free (options);
470     options = NULL;
471
472     if (diff_date1 != NULL)
473         free (diff_date1);
474     if (diff_date2 != NULL)
475         free (diff_date2);
476     if (diff_join1 != NULL)
477         free (diff_join1);
478     if (diff_join2 != NULL)
479         free (diff_join2);
480
481     return (err);
482 }
483
484 /*
485  * Do a file diff
486  */
487 /* ARGSUSED */
488 static int
489 diff_fileproc (callerdat, finfo)
490     void *callerdat;
491     struct file_info *finfo;
492 {
493     int status, err = 2;                /* 2 == trouble, like rcsdiff */
494     Vers_TS *vers;
495     enum diff_file empty_file = DIFF_DIFFERENT;
496     char *tmp = NULL;
497     char *tocvsPath = NULL;
498     char *fname = NULL;
499     char *label1;
500     char *label2;
501     char *rev1_cache = NULL;
502
503     user_file_rev = 0;
504     vers = Version_TS (finfo, NULL, NULL, NULL, 1, 0);
505
506     if (diff_rev2 != NULL || diff_date2 != NULL)
507     {
508         /* Skip all the following checks regarding the user file; we're
509            not using it.  */
510     }
511     else if (vers->vn_user == NULL)
512     {
513         /* The file does not exist in the working directory.  */
514         if ((diff_rev1 != NULL || diff_date1 != NULL)
515             && vers->srcfile != NULL)
516         {
517             /* The file does exist in the repository.  */
518             if (empty_files)
519                 empty_file = DIFF_REMOVED;
520             else
521             {
522                 int exists;
523
524                 exists = 0;
525                 /* special handling for TAG_HEAD XXX */
526                 if (diff_rev1 && strcmp (diff_rev1, TAG_HEAD) == 0)
527                 {
528                     char *head =
529                         (vers->vn_rcs == NULL
530                          ? NULL
531                          : RCS_branch_head (vers->srcfile, vers->vn_rcs));
532                     exists = head != NULL && !RCS_isdead(vers->srcfile, head);
533                     if (head != NULL)
534                         free (head);
535                 }
536                 else
537                 {
538                     Vers_TS *xvers;
539
540                     xvers = Version_TS (finfo, NULL, diff_rev1, diff_date1,
541                                         1, 0);
542                     exists = xvers->vn_rcs != NULL && !RCS_isdead(xvers->srcfile, xvers->vn_rcs);
543                     freevers_ts (&xvers);
544                 }
545                 if (exists)
546                     error (0, 0,
547                            "%s no longer exists, no comparison available",
548                            finfo->fullname);
549                 goto out;
550             }
551         }
552         else
553         {
554             error (0, 0, "I know nothing about %s", finfo->fullname);
555             goto out;
556         }
557     }
558     else if (vers->vn_user[0] == '0' && vers->vn_user[1] == '\0')
559     {
560         /* The file was added locally.  */
561         int exists = 0;
562
563         if (vers->srcfile != NULL)
564         {
565             /* The file does exist in the repository.  */
566
567             if ((diff_rev1 != NULL || diff_date1 != NULL))
568             {
569                 /* special handling for TAG_HEAD */
570                 if (diff_rev1 && strcmp (diff_rev1, TAG_HEAD) == 0)
571                 {
572                     char *head =
573                         (vers->vn_rcs == NULL
574                          ? NULL
575                          : RCS_branch_head (vers->srcfile, vers->vn_rcs));
576                     exists = head != NULL && !RCS_isdead(vers->srcfile, head);
577                     if (head != NULL)
578                         free (head);
579                 }
580                 else
581                 {
582                     Vers_TS *xvers;
583
584                     xvers = Version_TS (finfo, NULL, diff_rev1, diff_date1,
585                                         1, 0);
586                     exists = xvers->vn_rcs != NULL
587                              && !RCS_isdead (xvers->srcfile, xvers->vn_rcs);
588                     freevers_ts (&xvers);
589                 }
590             }
591             else
592             {
593                 /* The file was added locally, but an RCS archive exists.  Our
594                  * base revision must be dead.
595                  */
596                 /* No need to set, exists = 0, here.  That's the default.  */
597             }
598         }
599         if (!exists)
600         {
601             /* If we got here, then either the RCS archive does not exist or
602              * the relevant revision is dead.
603              */
604             if (empty_files)
605                 empty_file = DIFF_ADDED;
606             else
607             {
608                 error (0, 0, "%s is a new entry, no comparison available",
609                        finfo->fullname);
610                 goto out;
611             }
612         }
613     }
614     else if (vers->vn_user[0] == '-')
615     {
616         if (empty_files)
617             empty_file = DIFF_REMOVED;
618         else
619         {
620             error (0, 0, "%s was removed, no comparison available",
621                    finfo->fullname);
622             goto out;
623         }
624     }
625     else
626     {
627         if (vers->vn_rcs == NULL && vers->srcfile == NULL)
628         {
629             error (0, 0, "cannot find revision control file for %s",
630                    finfo->fullname);
631             goto out;
632         }
633         else
634         {
635             if (vers->ts_user == NULL)
636             {
637                 error (0, 0, "cannot find %s", finfo->fullname);
638                 goto out;
639             }
640             else if (!strcmp (vers->ts_user, vers->ts_rcs)) 
641             {
642                 /* The user file matches some revision in the repository
643                    Diff against the repository (for remote CVS, we might not
644                    have a copy of the user file around).  */
645                 user_file_rev = vers->vn_user;
646             }
647         }
648     }
649
650     empty_file = diff_file_nodiff( finfo, vers, empty_file, &rev1_cache );
651     if( empty_file == DIFF_SAME )
652     {
653         /* In the server case, would be nice to send a "Checked-in"
654            response, so that the client can rewrite its timestamp.
655            server_checked_in by itself isn't the right thing (it
656            needs a server_register), but I'm not sure what is.
657            It isn't clear to me how "cvs status" handles this (that
658            is, for a client which sends Modified not Is-modified to
659            "cvs status"), but it does.  */
660         err = 0;
661         goto out;
662     }
663     else if( empty_file == DIFF_ERROR )
664         goto out;
665
666     /* Output an "Index:" line for patch to use */
667     cvs_output ("Index: ", 0);
668     cvs_output (finfo->fullname, 0);
669     cvs_output ("\n", 1);
670
671     tocvsPath = wrap_tocvs_process_file(finfo->file);
672     if( tocvsPath != NULL )
673     {
674         /* Backup the current version of the file to CVS/,,filename */
675         fname = xmalloc (strlen (finfo->file)
676                          + sizeof CVSADM
677                          + sizeof CVSPREFIX
678                          + 10);
679         sprintf(fname,"%s/%s%s",CVSADM, CVSPREFIX, finfo->file);
680         if (unlink_file_dir (fname) < 0)
681             if (! existence_error (errno))
682                 error (1, errno, "cannot remove %s", fname);
683         rename_file (finfo->file, fname);
684         /* Copy the wrapped file to the current directory then go to work */
685         copy_file (tocvsPath, finfo->file);
686     }
687
688     /* Set up file labels appropriate for compatibility with the Larry Wall
689      * implementation of patch if the user didn't specify.  This is irrelevant
690      * according to the POSIX.2 specification.
691      */
692     label1 = NULL;
693     label2 = NULL;
694     if (!have_rev1_label)
695     {
696         if (empty_file == DIFF_ADDED)
697             label1 =
698                 make_file_label (DEVNULL, NULL, NULL);
699         else
700             label1 =
701                 make_file_label (finfo->fullname, use_rev1,
702                                  vers ? vers->srcfile : NULL);
703     }
704
705     if (!have_rev2_label)
706     {
707         if (empty_file == DIFF_REMOVED)
708             label2 =
709                 make_file_label (DEVNULL, NULL, NULL);
710         else
711             label2 =
712                 make_file_label (finfo->fullname, use_rev2,
713                                  vers ? vers->srcfile : NULL);
714     }
715
716     if (empty_file == DIFF_ADDED || empty_file == DIFF_REMOVED)
717     {
718         /* This is fullname, not file, possibly despite the POSIX.2
719          * specification, because that's the way all the Larry Wall
720          * implementations of patch (are there other implementations?) want
721          * things and the POSIX.2 spec appears to leave room for this.
722          */
723         cvs_output ("\
724 ===================================================================\n\
725 RCS file: ", 0);
726         cvs_output (finfo->fullname, 0);
727         cvs_output ("\n", 1);
728
729         cvs_output ("diff -N ", 0);
730         cvs_output (finfo->fullname, 0);
731         cvs_output ("\n", 1);
732
733         if (empty_file == DIFF_ADDED)
734         {
735             if (use_rev2 == NULL)
736                 status = diff_exec (DEVNULL, finfo->file, label1, label2, opts,
737                                     RUN_TTY);
738             else
739             {
740                 int retcode;
741
742                 tmp = cvs_temp_name ();
743                 retcode = RCS_checkout (vers->srcfile, (char *) NULL,
744                                         use_rev2, (char *) NULL,
745                                         (*options
746                                          ? options
747                                          : vers->options),
748                                         tmp, (RCSCHECKOUTPROC) NULL,
749                                         (void *) NULL);
750                 if( retcode != 0 )
751                     goto out;
752
753                 status = diff_exec (DEVNULL, tmp, label1, label2, opts, RUN_TTY);
754             }
755         }
756         else
757         {
758             int retcode;
759
760             tmp = cvs_temp_name ();
761             retcode = RCS_checkout (vers->srcfile, (char *) NULL,
762                                     use_rev1, (char *) NULL,
763                                     *options ? options : vers->options,
764                                     tmp, (RCSCHECKOUTPROC) NULL,
765                                     (void *) NULL);
766             if (retcode != 0)
767                 goto out;
768
769             status = diff_exec (tmp, DEVNULL, label1, label2, opts, RUN_TTY);
770         }
771     }
772     else
773     {
774         status = RCS_exec_rcsdiff(vers->srcfile, opts,
775                                   *options ? options : vers->options,
776                                   use_rev1, rev1_cache, use_rev2,
777                                   label1, label2,
778                                   finfo->file);
779
780     }
781
782     if (label1) free (label1);
783     if (label2) free (label2);
784
785     switch (status)
786     {
787         case -1:                        /* fork failed */
788             error (1, errno, "fork failed while diffing %s",
789                    vers->srcfile->path);
790         case 0:                         /* everything ok */
791             err = 0;
792             break;
793         default:                        /* other error */
794             err = status;
795             break;
796     }
797
798 out:
799     if( tocvsPath != NULL )
800     {
801         if (unlink_file_dir (finfo->file) < 0)
802             if (! existence_error (errno))
803                 error (1, errno, "cannot remove %s", finfo->file);
804
805         rename_file (fname, finfo->file);
806         if (unlink_file (tocvsPath) < 0)
807             error (1, errno, "cannot remove %s", tocvsPath);
808         free (fname);
809     }
810
811     /* Call CVS_UNLINK() rather than unlink_file() below to avoid the check
812      * for noexec.
813      */
814     if( tmp != NULL )
815     {
816         if (CVS_UNLINK(tmp) < 0)
817             error (0, errno, "cannot remove %s", tmp);
818         free (tmp);
819     }
820     if( rev1_cache != NULL )
821     {
822         if( CVS_UNLINK( rev1_cache ) < 0 )
823             error( 0, errno, "cannot remove %s", rev1_cache );
824         free( rev1_cache );
825     }
826
827     freevers_ts (&vers);
828     diff_mark_errors (err);
829     return err;
830 }
831
832 /*
833  * Remember the exit status for each file.
834  */
835 static void
836 diff_mark_errors (err)
837     int err;
838 {
839     if (err > diff_errors)
840         diff_errors = err;
841 }
842
843 /*
844  * Print a warm fuzzy message when we enter a dir
845  *
846  * Don't try to diff directories that don't exist! -- DW
847  */
848 /* ARGSUSED */
849 static Dtype
850 diff_dirproc (callerdat, dir, pos_repos, update_dir, entries)
851     void *callerdat;
852     const char *dir;
853     const char *pos_repos;
854     const char *update_dir;
855     List *entries;
856 {
857     /* XXX - check for dirs we don't want to process??? */
858
859     /* YES ... for instance dirs that don't exist!!! -- DW */
860     if (!isdir (dir))
861         return (R_SKIP_ALL);
862
863     if (!quiet)
864         error (0, 0, "Diffing %s", update_dir);
865     return (R_PROCESS);
866 }
867
868 /*
869  * Concoct the proper exit status - done with files
870  */
871 /* ARGSUSED */
872 static int
873 diff_filesdoneproc (callerdat, err, repos, update_dir, entries)
874     void *callerdat;
875     int err;
876     const char *repos;
877     const char *update_dir;
878     List *entries;
879 {
880     return (diff_errors);
881 }
882
883 /*
884  * Concoct the proper exit status - leaving directories
885  */
886 /* ARGSUSED */
887 static int
888 diff_dirleaveproc (callerdat, dir, err, update_dir, entries)
889     void *callerdat;
890     const char *dir;
891     int err;
892     const char *update_dir;
893     List *entries;
894 {
895     return (diff_errors);
896 }
897
898 /*
899  * verify that a file is different
900  */
901 static enum diff_file
902 diff_file_nodiff( finfo, vers, empty_file, rev1_cache )
903     struct file_info *finfo;
904     Vers_TS *vers;
905     enum diff_file empty_file;
906     char **rev1_cache;          /* Cache the content of rev1 if we have to look
907                                  * it up.
908                                  */
909 {
910     Vers_TS *xvers;
911     int retcode;
912
913     /* free up any old use_rev* variables and reset 'em */
914     if (use_rev1)
915         free (use_rev1);
916     if (use_rev2)
917         free (use_rev2);
918     use_rev1 = use_rev2 = (char *) NULL;
919
920     if (diff_rev1 || diff_date1)
921     {
922         /* special handling for TAG_HEAD XXX */
923         if (diff_rev1 && strcmp (diff_rev1, TAG_HEAD) == 0)
924         {
925             if (vers->vn_rcs != NULL && vers->srcfile != NULL)
926                 use_rev1 = RCS_branch_head (vers->srcfile, vers->vn_rcs);
927         }
928         else
929         {
930             xvers = Version_TS (finfo, NULL, diff_rev1, diff_date1, 1, 0);
931             if (xvers->vn_rcs != NULL)
932                 use_rev1 = xstrdup (xvers->vn_rcs);
933             freevers_ts (&xvers);
934         }
935     }
936     if (diff_rev2 || diff_date2)
937     {
938         /* special handling for TAG_HEAD XXX */
939         if (diff_rev2 && strcmp (diff_rev2, TAG_HEAD) == 0)
940         {
941             if (vers->vn_rcs != NULL && vers->srcfile != NULL)
942                 use_rev2 = RCS_branch_head (vers->srcfile, vers->vn_rcs);
943         }
944         else
945         {
946             xvers = Version_TS (finfo, NULL, diff_rev2, diff_date2, 1, 0);
947             if (xvers->vn_rcs != NULL)
948                 use_rev2 = xstrdup (xvers->vn_rcs);
949             freevers_ts (&xvers);
950         }
951
952         if( use_rev1 == NULL || RCS_isdead( vers->srcfile, use_rev1 ) )
953         {
954             /* The first revision does not exist.  If EMPTY_FILES is
955                true, treat this as an added file.  Otherwise, warn
956                about the missing tag.  */
957             if( use_rev2 == NULL || RCS_isdead( vers->srcfile, use_rev2 ) )
958                 /* At least in the case where DIFF_REV1 and DIFF_REV2
959                  * are both numeric (and non-existant (NULL), as opposed to
960                  * dead?), we should be returning some kind of error (see
961                  * basicb-8a0 in testsuite).  The symbolic case may be more
962                  * complicated.
963                  */
964                 return DIFF_SAME;
965             if( empty_files )
966                 return DIFF_ADDED;
967             if( use_rev1 != NULL )
968             {
969                 if (diff_rev1)
970                 {
971                     error( 0, 0,
972                        "Tag %s refers to a dead (removed) revision in file `%s'.",
973                        diff_rev1, finfo->fullname );
974                 }
975                 else
976                 {
977                     error( 0, 0,
978                        "Date %s refers to a dead (removed) revision in file `%s'.",
979                        diff_date1, finfo->fullname );
980                 }
981                 error( 0, 0,
982                        "No comparison available.  Pass `-N' to `%s diff'?",
983                        program_name );
984             }
985             else if (diff_rev1)
986                 error (0, 0, "tag %s is not in file %s", diff_rev1,
987                        finfo->fullname);
988             else
989                 error (0, 0, "no revision for date %s in file %s",
990                        diff_date1, finfo->fullname);
991             return DIFF_ERROR;
992         }
993
994         assert( use_rev1 != NULL );
995         if( use_rev2 == NULL || RCS_isdead( vers->srcfile, use_rev2 ) )
996         {
997             /* The second revision does not exist.  If EMPTY_FILES is
998                true, treat this as a removed file.  Otherwise warn
999                about the missing tag.  */
1000             if (empty_files)
1001                 return DIFF_REMOVED;
1002             if( use_rev2 != NULL )
1003             {
1004                 if (diff_rev2)
1005                 {
1006                     error( 0, 0,
1007                        "Tag %s refers to a dead (removed) revision in file `%s'.",
1008                        diff_rev2, finfo->fullname );
1009                 }
1010                 else
1011                 {
1012                     error( 0, 0,
1013                        "Date %s refers to a dead (removed) revision in file `%s'.",
1014                        diff_date2, finfo->fullname );
1015                 }
1016                 error( 0, 0,
1017                        "No comparison available.  Pass `-N' to `%s diff'?",
1018                        program_name );
1019             }
1020             else if (diff_rev2)
1021                 error (0, 0, "tag %s is not in file %s", diff_rev2,
1022                        finfo->fullname);
1023             else
1024                 error (0, 0, "no revision for date %s in file %s",
1025                        diff_date2, finfo->fullname);
1026             return DIFF_ERROR;
1027         }
1028         /* Now, see if we really need to do the diff.  We can't assume that the
1029          * files are different when the revs are.
1030          */
1031         assert( use_rev2 != NULL );
1032         if( strcmp (use_rev1, use_rev2) == 0 )
1033             return DIFF_SAME;
1034         /* else fall through and do the diff */
1035     }
1036
1037     /* If we had a r1/d1 & r2/d2, then at this point we must have a C3P0...
1038      * err...  ok, then both rev1 & rev2 must have resolved to an existing,
1039      * live version due to if statement we just closed.
1040      */
1041     assert (!(diff_rev2 || diff_date2) || (use_rev1 && use_rev2));
1042
1043     if ((diff_rev1 || diff_date1) &&
1044         (use_rev1 == NULL || RCS_isdead (vers->srcfile, use_rev1)))
1045     {
1046         /* The first revision does not exist, and no second revision
1047            was given.  */
1048         if (empty_files)
1049         {
1050             if (empty_file == DIFF_REMOVED)
1051                 return DIFF_SAME;
1052             if( user_file_rev && use_rev2 == NULL )
1053                 use_rev2 = xstrdup( user_file_rev );
1054             return DIFF_ADDED;
1055         }
1056         if( use_rev1 != NULL )
1057         {
1058             if (diff_rev1)
1059             {
1060                 error( 0, 0,
1061                    "Tag %s refers to a dead (removed) revision in file `%s'.",
1062                    diff_rev1, finfo->fullname );
1063             }
1064             else
1065             {
1066                 error( 0, 0,
1067                    "Date %s refers to a dead (removed) revision in file `%s'.",
1068                    diff_date1, finfo->fullname );
1069             }
1070             error( 0, 0,
1071                    "No comparison available.  Pass `-N' to `%s diff'?",
1072                    program_name );
1073         }
1074         else if ( diff_rev1 )
1075             error( 0, 0, "tag %s is not in file %s", diff_rev1,
1076                    finfo->fullname );
1077         else
1078             error( 0, 0, "no revision for date %s in file %s",
1079                    diff_date1, finfo->fullname );
1080         return DIFF_ERROR;
1081     }
1082
1083     assert( !diff_rev1 || use_rev1 );
1084
1085     if (user_file_rev)
1086     {
1087         /* drop user_file_rev into first unused use_rev */
1088         if (!use_rev1) 
1089             use_rev1 = xstrdup (user_file_rev);
1090         else if (!use_rev2)
1091             use_rev2 = xstrdup (user_file_rev);
1092         /* and if not, it wasn't needed anyhow */
1093         user_file_rev = NULL;
1094     }
1095
1096     /* Now, see if we really need to do the diff.  We can't assume that the
1097      * files are different when the revs are.
1098      */
1099     if( use_rev1 && use_rev2) 
1100     {
1101         if (strcmp (use_rev1, use_rev2) == 0)
1102             return DIFF_SAME;
1103         /* Fall through and do the diff. */
1104     }
1105     /* Don't want to do the timestamp check with both use_rev1 & use_rev2 set.
1106      * The timestamp check is just for the default case of diffing the
1107      * workspace file against its base revision.
1108      */
1109     else if( use_rev1 == NULL
1110              || ( vers->vn_user != NULL
1111                   && strcmp( use_rev1, vers->vn_user ) == 0 ) )
1112     {
1113         if (empty_file == DIFF_DIFFERENT
1114             && vers->ts_user != NULL
1115             && strcmp (vers->ts_rcs, vers->ts_user) == 0
1116             && (!(*options) || strcmp (options, vers->options) == 0))
1117         {
1118             return DIFF_SAME;
1119         }
1120         if (use_rev1 == NULL
1121             && (vers->vn_user[0] != '0' || vers->vn_user[1] != '\0'))
1122         {
1123             if (vers->vn_user[0] == '-')
1124                 use_rev1 = xstrdup (vers->vn_user + 1);
1125             else
1126                 use_rev1 = xstrdup (vers->vn_user);
1127         }
1128     }
1129
1130     /* If we already know that the file is being added or removed,
1131        then we don't want to do an actual file comparison here.  */
1132     if (empty_file != DIFF_DIFFERENT)
1133         return empty_file;
1134
1135     /*
1136      * Run a quick cmp to see if we should bother with a full diff.
1137      */
1138
1139     retcode = RCS_cmp_file( vers->srcfile, use_rev1, rev1_cache,
1140                             use_rev2, *options ? options : vers->options,
1141                             finfo->file );
1142
1143     return retcode == 0 ? DIFF_SAME : DIFF_DIFFERENT;
1144 }