xref: /dragonfly/contrib/cvs-1.12/src/diff.c (revision 86d7f5d305c6adaa56ff4582ece9859d73106103)
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
add_diff_args(char opt,const char * longopt,const char * argument)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
diff(int argc,char ** argv)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
diff_fileproc(void * callerdat,struct file_info * finfo)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
diff_mark_errors(int err)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
diff_dirproc(void * callerdat,const char * dir,const char * pos_repos,const char * update_dir,List * entries)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
diff_filesdoneproc(void * callerdat,int err,const char * repos,const char * update_dir,List * entries)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
diff_dirleaveproc(void * callerdat,const char * dir,int err,const char * update_dir,List * entries)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
diff_file_nodiff(struct file_info * finfo,Vers_TS * vers,enum diff_file empty_file,char ** rev1_cache)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 }
1158