]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/cvs/src/diff.c
Import of slightly trimmed cvs-1.8 distribution. Generated files
[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 1.4 kit.
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
17 #include "cvs.h"
18
19 static Dtype diff_dirproc PROTO((char *dir, char *pos_repos, char *update_dir));
20 static int diff_filesdoneproc PROTO((int err, char *repos, char *update_dir));
21 static int diff_dirleaveproc PROTO((char *dir, int err, char *update_dir));
22 static int diff_file_nodiff PROTO((char *file, char *repository, List *entries,
23                              RCSNode *rcs, Vers_TS *vers));
24 static int diff_fileproc PROTO((struct file_info *finfo));
25 static void diff_mark_errors PROTO((int err));
26
27 static char *diff_rev1, *diff_rev2;
28 static char *diff_date1, *diff_date2;
29 static char *use_rev1, *use_rev2;
30
31 #ifdef SERVER_SUPPORT
32 /* Revision of the user file, if it is unchanged from something in the
33    repository and we want to use that fact.  */
34 static char *user_file_rev;
35 #endif
36
37 static char *options;
38 static char opts[PATH_MAX];
39 static int diff_errors;
40 static int empty_files = 0;
41
42 static const char *const diff_usage[] =
43 {
44     "Usage: %s %s [-lN] [rcsdiff-options]\n",
45 #ifdef CVS_DIFFDATE
46     "    [[-r rev1 | -D date1] [-r rev2 | -D date2]] [files...] \n",
47 #else
48     "    [-r rev1 [-r rev2]] [files...] \n",
49 #endif
50     "\t-l\tLocal directory only, not recursive\n",
51     "\t-D d1\tDiff revision for date against working file.\n",
52     "\t-D d2\tDiff rev1/date1 against date2.\n",
53     "\t-N\tinclude diffs for added and removed files.\n",
54     "\t-r rev1\tDiff revision for rev1 against working file.\n",
55     "\t-r rev2\tDiff rev1/date1 against rev2.\n",
56     NULL
57 };
58
59 int
60 diff (argc, argv)
61     int argc;
62     char **argv;
63 {
64     char tmp[50];
65     int c, err = 0;
66     int local = 0;
67     int which;
68
69     if (argc == -1)
70         usage (diff_usage);
71
72     /*
73      * Note that we catch all the valid arguments here, so that we can
74      * intercept the -r arguments for doing revision diffs; and -l/-R for a
75      * non-recursive/recursive diff.
76      */
77 #ifdef SERVER_SUPPORT
78     /* Need to be able to do this command more than once (according to
79        the protocol spec, even if the current client doesn't use it).  */
80     opts[0] = '\0';
81 #endif
82     optind = 1;
83     while ((c = getopt (argc, argv,
84                    "abcdefhilnpqtuw0123456789BHNQRTC:D:F:I:L:V:k:r:")) != -1)
85     {
86         switch (c)
87         {
88             case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
89             case 'h': case 'i': case 'n': case 'p': case 't': case 'u':
90             case 'w': case '0': case '1': case '2': case '3': case '4':
91             case '5': case '6': case '7': case '8': case '9': case 'B':
92             case 'H': case 'T': case 'Q':
93                 (void) sprintf (tmp, " -%c", (char) c);
94                 (void) strcat (opts, tmp);
95                 if (c == 'Q')
96                 {
97                     quiet = 1;
98                     really_quiet = 1;
99                     c = 'q';
100                 }
101                 break;
102             case 'C': case 'F': case 'I': case 'L': case 'V':
103 #ifndef CVS_DIFFDATE
104             case 'D':
105 #endif
106                 (void) sprintf (tmp, " -%c%s", (char) c, optarg);
107                 (void) strcat (opts, tmp);
108                 break;
109             case 'R':
110                 local = 0;
111                 break;
112             case 'l':
113                 local = 1;
114                 break;
115             case 'q':
116                 quiet = 1;
117                 break;
118             case 'k':
119                 if (options)
120                     free (options);
121                 options = RCS_check_kflag (optarg);
122                 break;
123             case 'r':
124                 if (diff_rev2 != NULL || diff_date2 != NULL)
125                     error (1, 0,
126                        "no more than two revisions/dates can be specified");
127                 if (diff_rev1 != NULL || diff_date1 != NULL)
128                     diff_rev2 = optarg;
129                 else
130                     diff_rev1 = optarg;
131                 break;
132 #ifdef CVS_DIFFDATE
133             case 'D':
134                 if (diff_rev2 != NULL || diff_date2 != NULL)
135                     error (1, 0,
136                        "no more than two revisions/dates can be specified");
137                 if (diff_rev1 != NULL || diff_date1 != NULL)
138                     diff_date2 = Make_Date (optarg);
139                 else
140                     diff_date1 = Make_Date (optarg);
141                 break;
142 #endif
143             case 'N':
144                 empty_files = 1;
145                 break;
146             case '?':
147             default:
148                 usage (diff_usage);
149                 break;
150         }
151     }
152     argc -= optind;
153     argv += optind;
154
155     /* make sure options is non-null */
156     if (!options)
157         options = xstrdup ("");
158
159 #ifdef CLIENT_SUPPORT
160     if (client_active) {
161         /* We're the client side.  Fire up the remote server.  */
162         start_server ();
163         
164         ign_setup ();
165
166         if (local)
167             send_arg("-l");
168         if (empty_files)
169             send_arg("-N");
170         send_option_string (opts);
171         if (diff_rev1)
172             option_with_arg ("-r", diff_rev1);
173         if (diff_date1)
174             client_senddate (diff_date1);
175         if (diff_rev2)
176             option_with_arg ("-r", diff_rev2);
177         if (diff_date2)
178             client_senddate (diff_date2);
179
180         send_file_names (argc, argv, SEND_EXPAND_WILD);
181 #if 0
182         /* FIXME: We shouldn't have to send current files to diff two
183            revs, but it doesn't work yet and I haven't debugged it.
184            So send the files -- it's slower but it works.
185            gnu@cygnus.com Apr94 */
186         /* Send the current files unless diffing two revs from the archive */
187         if (diff_rev2 == NULL && diff_date2 == NULL)
188 #endif
189         send_files (argc, argv, local, 0);
190
191         send_to_server ("diff\012", 0);
192         err = get_responses_and_close ();
193         free (options);
194         return (err);
195     }
196 #endif
197
198     if (diff_rev1 != NULL)
199         tag_check_valid (diff_rev1, argc, argv, local, 0, "");
200     if (diff_rev2 != NULL)
201         tag_check_valid (diff_rev2, argc, argv, local, 0, "");
202
203     which = W_LOCAL;
204     if (diff_rev2 != NULL || diff_date2 != NULL)
205         which |= W_REPOS | W_ATTIC;
206
207     wrap_setup ();
208
209     /* start the recursion processor */
210     err = start_recursion (diff_fileproc, diff_filesdoneproc, diff_dirproc,
211                            diff_dirleaveproc, argc, argv, local,
212                            which, 0, 1, (char *) NULL, 1, 0);
213
214     /* clean up */
215     free (options);
216     return (err);
217 }
218
219 /*
220  * Do a file diff
221  */
222 /* ARGSUSED */
223 static int
224 diff_fileproc (finfo)
225     struct file_info *finfo;
226 {
227     int status, err = 2;                /* 2 == trouble, like rcsdiff */
228     Vers_TS *vers;
229     enum {
230         DIFF_ERROR,
231         DIFF_ADDED,
232         DIFF_REMOVED,
233         DIFF_NEITHER
234     } empty_file = DIFF_NEITHER;
235     char tmp[L_tmpnam+1];
236     char *tocvsPath;
237     char fname[PATH_MAX];
238
239 #ifdef SERVER_SUPPORT
240     user_file_rev = 0;
241 #endif
242     vers = Version_TS (finfo->repository, (char *) NULL, (char *) NULL, (char *) NULL,
243                        finfo->file, 1, 0, finfo->entries, finfo->rcs);
244
245     if (diff_rev2 != NULL || diff_date2 != NULL)
246     {
247         /* Skip all the following checks regarding the user file; we're
248            not using it.  */
249     }
250     else if (vers->vn_user == NULL)
251     {
252         error (0, 0, "I know nothing about %s", finfo->fullname);
253         freevers_ts (&vers);
254         diff_mark_errors (err);
255         return (err);
256     }
257     else if (vers->vn_user[0] == '0' && vers->vn_user[1] == '\0')
258     {
259         if (empty_files)
260             empty_file = DIFF_ADDED;
261         else
262         {
263             error (0, 0, "%s is a new entry, no comparison available",
264                    finfo->fullname);
265             freevers_ts (&vers);
266             diff_mark_errors (err);
267             return (err);
268         }
269     }
270     else if (vers->vn_user[0] == '-')
271     {
272         if (empty_files)
273             empty_file = DIFF_REMOVED;
274         else
275         {
276             error (0, 0, "%s was removed, no comparison available",
277                    finfo->fullname);
278             freevers_ts (&vers);
279             diff_mark_errors (err);
280             return (err);
281         }
282     }
283     else
284     {
285         if (vers->vn_rcs == NULL && vers->srcfile == NULL)
286         {
287             error (0, 0, "cannot find revision control file for %s",
288                    finfo->fullname);
289             freevers_ts (&vers);
290             diff_mark_errors (err);
291             return (err);
292         }
293         else
294         {
295             if (vers->ts_user == NULL)
296             {
297                 error (0, 0, "cannot find %s", finfo->fullname);
298                 freevers_ts (&vers);
299                 diff_mark_errors (err);
300                 return (err);
301             }
302 #ifdef SERVER_SUPPORT
303             else if (!strcmp (vers->ts_user, vers->ts_rcs)) 
304             {
305                 /* The user file matches some revision in the repository
306                    Diff against the repository (for remote CVS, we might not
307                    have a copy of the user file around).  */
308                 user_file_rev = vers->vn_user;
309             }
310 #endif
311         }
312     }
313
314     if (empty_file == DIFF_NEITHER && diff_file_nodiff (finfo->file, finfo->repository, finfo->entries, finfo->rcs, vers))
315     {
316         freevers_ts (&vers);
317         return (0);
318     }
319
320     /* FIXME: Check whether use_rev1 and use_rev2 are dead and deal
321        accordingly.  */
322
323     /* Output an "Index:" line for patch to use */
324     (void) fflush (stdout);
325     (void) printf ("Index: %s\n", finfo->fullname);
326     (void) fflush (stdout);
327
328     tocvsPath = wrap_tocvs_process_file(finfo->file);
329     if (tocvsPath)
330     {
331         /* Backup the current version of the file to CVS/,,filename */
332         sprintf(fname,"%s/%s%s",CVSADM, CVSPREFIX, finfo->file);
333         if (unlink_file_dir (fname) < 0)
334             if (! existence_error (errno))
335                 error (1, errno, "cannot remove %s", finfo->file);
336         rename_file (finfo->file, fname);
337         /* Copy the wrapped file to the current directory then go to work */
338         copy_file (tocvsPath, finfo->file);
339     }
340
341     if (empty_file == DIFF_ADDED || empty_file == DIFF_REMOVED)
342     {
343         /* This is file, not fullname, because it is the "Index:" line which
344            is supposed to contain the directory.  */
345         (void) printf ("===================================================================\nRCS file: %s\n",
346                        finfo->file);
347         (void) printf ("diff -N %s\n", finfo->file);
348
349         if (empty_file == DIFF_ADDED)
350         {
351             run_setup ("%s %s %s %s", DIFF, opts, DEVNULL, finfo->file);
352         }
353         else
354         {
355             int retcode;
356
357             /*
358              * FIXME: Should be setting use_rev1 using the logic in
359              * diff_file_nodiff, and using that revision.  This code
360              * is broken for "cvs diff -N -r foo".
361              */
362             retcode = RCS_checkout (vers->srcfile->path, NULL, vers->vn_rcs,
363                                     *options ? options : vers->options, tmpnam (tmp),
364                                     0, 0);
365             if (retcode == -1)
366             {
367                 (void) unlink (tmp);
368                 error (1, errno, "fork failed during checkout of %s",
369                        vers->srcfile->path);
370             }
371             /* FIXME: what if retcode > 0?  */
372
373             run_setup ("%s %s %s %s", DIFF, opts, tmp, DEVNULL);
374         }
375     }
376     else
377     {
378         if (use_rev2)
379         {
380             run_setup ("%s%s -x,v/ %s %s -r%s -r%s", Rcsbin, RCS_DIFF,
381                        opts, *options ? options : vers->options,
382                        use_rev1, use_rev2);
383         }
384         else
385         {
386             run_setup ("%s%s -x,v/ %s %s -r%s", Rcsbin, RCS_DIFF, opts,
387                        *options ? options : vers->options, use_rev1);
388         }
389         run_arg (vers->srcfile->path);
390     }
391
392     switch ((status = run_exec (RUN_TTY, RUN_TTY, RUN_TTY,
393         RUN_REALLY|RUN_COMBINED)))
394     {
395         case -1:                        /* fork failed */
396             error (1, errno, "fork failed during rcsdiff of %s",
397                    vers->srcfile->path);
398         case 0:                         /* everything ok */
399             err = 0;
400             break;
401         default:                        /* other error */
402             err = status;
403             break;
404     }
405
406     if (tocvsPath)
407     {
408         if (unlink_file_dir (finfo->file) < 0)
409             if (! existence_error (errno))
410                 error (1, errno, "cannot remove %s", finfo->file);
411
412         rename_file (fname,finfo->file);
413         if (unlink_file (tocvsPath) < 0)
414             error (1, errno, "cannot remove %s", finfo->file);
415     }
416
417     if (empty_file == DIFF_REMOVED)
418         (void) unlink (tmp);
419
420     (void) fflush (stdout);
421     freevers_ts (&vers);
422     diff_mark_errors (err);
423     return (err);
424 }
425
426 /*
427  * Remember the exit status for each file.
428  */
429 static void
430 diff_mark_errors (err)
431     int err;
432 {
433     if (err > diff_errors)
434         diff_errors = err;
435 }
436
437 /*
438  * Print a warm fuzzy message when we enter a dir
439  *
440  * Don't try to diff directories that don't exist! -- DW
441  */
442 /* ARGSUSED */
443 static Dtype
444 diff_dirproc (dir, pos_repos, update_dir)
445     char *dir;
446     char *pos_repos;
447     char *update_dir;
448 {
449     /* XXX - check for dirs we don't want to process??? */
450
451     /* YES ... for instance dirs that don't exist!!! -- DW */
452     if (!isdir (dir) )
453       return (R_SKIP_ALL);
454   
455     if (!quiet)
456         error (0, 0, "Diffing %s", update_dir);
457     return (R_PROCESS);
458 }
459
460 /*
461  * Concoct the proper exit status - done with files
462  */
463 /* ARGSUSED */
464 static int
465 diff_filesdoneproc (err, repos, update_dir)
466     int err;
467     char *repos;
468     char *update_dir;
469 {
470     return (diff_errors);
471 }
472
473 /*
474  * Concoct the proper exit status - leaving directories
475  */
476 /* ARGSUSED */
477 static int
478 diff_dirleaveproc (dir, err, update_dir)
479     char *dir;
480     int err;
481     char *update_dir;
482 {
483     return (diff_errors);
484 }
485
486 /*
487  * verify that a file is different 0=same 1=different
488  */
489 static int
490 diff_file_nodiff (file, repository, entries, rcs, vers)
491     char *file;
492     char *repository;
493     List *entries;
494     RCSNode *rcs;
495     Vers_TS *vers;
496 {
497     Vers_TS *xvers;
498     char tmp[L_tmpnam+1];
499     int retcode;
500
501     /* free up any old use_rev* variables and reset 'em */
502     if (use_rev1)
503         free (use_rev1);
504     if (use_rev2)
505         free (use_rev2);
506     use_rev1 = use_rev2 = (char *) NULL;
507
508     if (diff_rev1 || diff_date1)
509     {
510         /* special handling for TAG_HEAD */
511         if (diff_rev1 && strcmp (diff_rev1, TAG_HEAD) == 0)
512             use_rev1 = xstrdup (vers->vn_rcs);
513         else
514         {
515             xvers = Version_TS (repository, (char *) NULL, diff_rev1,
516                                 diff_date1, file, 1, 0, entries, rcs);
517             if (xvers->vn_rcs == NULL)
518             {
519                 /* Don't gripe if it doesn't exist, just ignore! */
520                 if (! isfile (file))
521                   /* null statement */ ;
522                 else if (diff_rev1)
523                     error (0, 0, "tag %s is not in file %s", diff_rev1, file);
524                 else
525                     error (0, 0, "no revision for date %s in file %s",
526                            diff_date1, file);
527
528                 freevers_ts (&xvers);
529                 return (1);
530             }
531             use_rev1 = xstrdup (xvers->vn_rcs);
532             freevers_ts (&xvers);
533         }
534     }
535     if (diff_rev2 || diff_date2)
536     {
537         /* special handling for TAG_HEAD */
538         if (diff_rev2 && strcmp (diff_rev2, TAG_HEAD) == 0)
539             use_rev2 = xstrdup (vers->vn_rcs);
540         else
541         {
542             xvers = Version_TS (repository, (char *) NULL, diff_rev2,
543                                 diff_date2, file, 1, 0, entries, rcs);
544             if (xvers->vn_rcs == NULL)
545             {
546                 /* Don't gripe if it doesn't exist, just ignore! */
547                 if (! isfile (file))
548                   /* null statement */ ;
549                 else if (diff_rev1)
550                     error (0, 0, "tag %s is not in file %s", diff_rev2, file);
551                 else
552                     error (0, 0, "no revision for date %s in file %s",
553                            diff_date2, file);
554
555                 freevers_ts (&xvers);
556                 return (1);
557             }
558             use_rev2 = xstrdup (xvers->vn_rcs);
559             freevers_ts (&xvers);
560         }
561
562         /* now, see if we really need to do the diff */
563         if (use_rev1 && use_rev2) {
564             return (strcmp (use_rev1, use_rev2) == 0);
565         } else {
566             error(0, 0, "No HEAD revision for file %s", file);
567             return (1);
568         }
569     }
570 #ifdef SERVER_SUPPORT
571     if (user_file_rev) 
572     {
573         /* drop user_file_rev into first unused use_rev */
574         if (!use_rev1) 
575           use_rev1 = xstrdup (user_file_rev);
576         else if (!use_rev2)
577           use_rev2 = xstrdup (user_file_rev);
578         /* and if not, it wasn't needed anyhow */
579         user_file_rev = 0;
580     }
581
582     /* now, see if we really need to do the diff */
583     if (use_rev1 && use_rev2) 
584     {
585         return (strcmp (use_rev1, use_rev2) == 0);
586     }
587 #endif /* SERVER_SUPPORT */
588     if (use_rev1 == NULL || strcmp (use_rev1, vers->vn_user) == 0)
589     {
590         if (strcmp (vers->ts_rcs, vers->ts_user) == 0 &&
591             (!(*options) || strcmp (options, vers->options) == 0))
592         {
593             return (1);
594         }
595         if (use_rev1 == NULL)
596             use_rev1 = xstrdup (vers->vn_user);
597     }
598
599     /*
600      * with 0 or 1 -r option specified, run a quick diff to see if we
601      * should bother with it at all.
602      */
603     retcode = RCS_checkout (vers->srcfile->path, NULL, use_rev1,
604                             *options ? options : vers->options, tmpnam (tmp), 0, 0);
605     switch (retcode)
606     {
607         case 0:                         /* everything ok */
608             if (xcmp (file, tmp) == 0)
609             {
610                 (void) unlink (tmp);
611                 return (1);
612             }
613             break;
614         case -1:                        /* fork failed */
615             (void) unlink (tmp);
616             error (1, errno, "fork failed during checkout of %s",
617                    vers->srcfile->path);
618         default:
619             break;
620     }
621     (void) unlink (tmp);
622     return (0);
623 }