2 * Copyright (c) 1992, Brian Berliner and Jeff Polk
3 * Copyright (c) 1989-1992, Brian Berliner
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.
10 * Run diff against versions in the repository. Options that are specified are
11 * passed on directly to "rcsdiff".
13 * Without any file arguments, runs diff against all the currently modified
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));
27 static char *diff_rev1, *diff_rev2;
28 static char *diff_date1, *diff_date2;
29 static char *use_rev1, *use_rev2;
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;
38 static char opts[PATH_MAX];
39 static int diff_errors;
40 static int empty_files = 0;
42 static const char *const diff_usage[] =
44 "Usage: %s %s [-lN] [rcsdiff-options]\n",
46 " [[-r rev1 | -D date1] [-r rev2 | -D date2]] [files...] \n",
48 " [-r rev1 [-r rev2]] [files...] \n",
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",
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.
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). */
83 while ((c = getopt (argc, argv,
84 "abcdefhilnpqtuw0123456789BHNQRTC:D:F:I:L:V:k:r:")) != -1)
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);
102 case 'C': case 'F': case 'I': case 'L': case 'V':
106 (void) sprintf (tmp, " -%c%s", (char) c, optarg);
107 (void) strcat (opts, tmp);
121 options = RCS_check_kflag (optarg);
124 if (diff_rev2 != NULL || diff_date2 != NULL)
126 "no more than two revisions/dates can be specified");
127 if (diff_rev1 != NULL || diff_date1 != NULL)
134 if (diff_rev2 != NULL || diff_date2 != NULL)
136 "no more than two revisions/dates can be specified");
137 if (diff_rev1 != NULL || diff_date1 != NULL)
138 diff_date2 = Make_Date (optarg);
140 diff_date1 = Make_Date (optarg);
155 /* make sure options is non-null */
157 options = xstrdup ("");
159 #ifdef CLIENT_SUPPORT
161 /* We're the client side. Fire up the remote server. */
170 send_option_string (opts);
172 option_with_arg ("-r", diff_rev1);
174 client_senddate (diff_date1);
176 option_with_arg ("-r", diff_rev2);
178 client_senddate (diff_date2);
180 send_file_names (argc, argv, SEND_EXPAND_WILD);
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)
189 send_files (argc, argv, local, 0);
191 send_to_server ("diff\012", 0);
192 err = get_responses_and_close ();
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, "");
204 if (diff_rev2 != NULL || diff_date2 != NULL)
205 which |= W_REPOS | W_ATTIC;
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);
224 diff_fileproc (finfo)
225 struct file_info *finfo;
227 int status, err = 2; /* 2 == trouble, like rcsdiff */
234 } empty_file = DIFF_NEITHER;
235 char tmp[L_tmpnam+1];
237 char fname[PATH_MAX];
239 #ifdef SERVER_SUPPORT
242 vers = Version_TS (finfo->repository, (char *) NULL, (char *) NULL, (char *) NULL,
243 finfo->file, 1, 0, finfo->entries, finfo->rcs);
245 if (diff_rev2 != NULL || diff_date2 != NULL)
247 /* Skip all the following checks regarding the user file; we're
250 else if (vers->vn_user == NULL)
252 error (0, 0, "I know nothing about %s", finfo->fullname);
254 diff_mark_errors (err);
257 else if (vers->vn_user[0] == '0' && vers->vn_user[1] == '\0')
260 empty_file = DIFF_ADDED;
263 error (0, 0, "%s is a new entry, no comparison available",
266 diff_mark_errors (err);
270 else if (vers->vn_user[0] == '-')
273 empty_file = DIFF_REMOVED;
276 error (0, 0, "%s was removed, no comparison available",
279 diff_mark_errors (err);
285 if (vers->vn_rcs == NULL && vers->srcfile == NULL)
287 error (0, 0, "cannot find revision control file for %s",
290 diff_mark_errors (err);
295 if (vers->ts_user == NULL)
297 error (0, 0, "cannot find %s", finfo->fullname);
299 diff_mark_errors (err);
302 #ifdef SERVER_SUPPORT
303 else if (!strcmp (vers->ts_user, vers->ts_rcs))
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;
314 if (empty_file == DIFF_NEITHER && diff_file_nodiff (finfo->file, finfo->repository, finfo->entries, finfo->rcs, vers))
320 /* FIXME: Check whether use_rev1 and use_rev2 are dead and deal
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);
328 tocvsPath = wrap_tocvs_process_file(finfo->file);
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);
341 if (empty_file == DIFF_ADDED || empty_file == DIFF_REMOVED)
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",
347 (void) printf ("diff -N %s\n", finfo->file);
349 if (empty_file == DIFF_ADDED)
351 run_setup ("%s %s %s %s", DIFF, opts, DEVNULL, finfo->file);
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".
362 retcode = RCS_checkout (vers->srcfile->path, NULL, vers->vn_rcs,
363 *options ? options : vers->options, tmpnam (tmp),
368 error (1, errno, "fork failed during checkout of %s",
369 vers->srcfile->path);
371 /* FIXME: what if retcode > 0? */
373 run_setup ("%s %s %s %s", DIFF, opts, tmp, DEVNULL);
380 run_setup ("%s%s -x,v/ %s %s -r%s -r%s", Rcsbin, RCS_DIFF,
381 opts, *options ? options : vers->options,
386 run_setup ("%s%s -x,v/ %s %s -r%s", Rcsbin, RCS_DIFF, opts,
387 *options ? options : vers->options, use_rev1);
389 run_arg (vers->srcfile->path);
392 switch ((status = run_exec (RUN_TTY, RUN_TTY, RUN_TTY,
393 RUN_REALLY|RUN_COMBINED)))
395 case -1: /* fork failed */
396 error (1, errno, "fork failed during rcsdiff of %s",
397 vers->srcfile->path);
398 case 0: /* everything ok */
401 default: /* other error */
408 if (unlink_file_dir (finfo->file) < 0)
409 if (! existence_error (errno))
410 error (1, errno, "cannot remove %s", finfo->file);
412 rename_file (fname,finfo->file);
413 if (unlink_file (tocvsPath) < 0)
414 error (1, errno, "cannot remove %s", finfo->file);
417 if (empty_file == DIFF_REMOVED)
420 (void) fflush (stdout);
422 diff_mark_errors (err);
427 * Remember the exit status for each file.
430 diff_mark_errors (err)
433 if (err > diff_errors)
438 * Print a warm fuzzy message when we enter a dir
440 * Don't try to diff directories that don't exist! -- DW
444 diff_dirproc (dir, pos_repos, update_dir)
449 /* XXX - check for dirs we don't want to process??? */
451 /* YES ... for instance dirs that don't exist!!! -- DW */
456 error (0, 0, "Diffing %s", update_dir);
461 * Concoct the proper exit status - done with files
465 diff_filesdoneproc (err, repos, update_dir)
470 return (diff_errors);
474 * Concoct the proper exit status - leaving directories
478 diff_dirleaveproc (dir, err, update_dir)
483 return (diff_errors);
487 * verify that a file is different 0=same 1=different
490 diff_file_nodiff (file, repository, entries, rcs, vers)
498 char tmp[L_tmpnam+1];
501 /* free up any old use_rev* variables and reset 'em */
506 use_rev1 = use_rev2 = (char *) NULL;
508 if (diff_rev1 || diff_date1)
510 /* special handling for TAG_HEAD */
511 if (diff_rev1 && strcmp (diff_rev1, TAG_HEAD) == 0)
512 use_rev1 = xstrdup (vers->vn_rcs);
515 xvers = Version_TS (repository, (char *) NULL, diff_rev1,
516 diff_date1, file, 1, 0, entries, rcs);
517 if (xvers->vn_rcs == NULL)
519 /* Don't gripe if it doesn't exist, just ignore! */
521 /* null statement */ ;
523 error (0, 0, "tag %s is not in file %s", diff_rev1, file);
525 error (0, 0, "no revision for date %s in file %s",
528 freevers_ts (&xvers);
531 use_rev1 = xstrdup (xvers->vn_rcs);
532 freevers_ts (&xvers);
535 if (diff_rev2 || diff_date2)
537 /* special handling for TAG_HEAD */
538 if (diff_rev2 && strcmp (diff_rev2, TAG_HEAD) == 0)
539 use_rev2 = xstrdup (vers->vn_rcs);
542 xvers = Version_TS (repository, (char *) NULL, diff_rev2,
543 diff_date2, file, 1, 0, entries, rcs);
544 if (xvers->vn_rcs == NULL)
546 /* Don't gripe if it doesn't exist, just ignore! */
548 /* null statement */ ;
550 error (0, 0, "tag %s is not in file %s", diff_rev2, file);
552 error (0, 0, "no revision for date %s in file %s",
555 freevers_ts (&xvers);
558 use_rev2 = xstrdup (xvers->vn_rcs);
559 freevers_ts (&xvers);
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);
566 error(0, 0, "No HEAD revision for file %s", file);
570 #ifdef SERVER_SUPPORT
573 /* drop user_file_rev into first unused use_rev */
575 use_rev1 = xstrdup (user_file_rev);
577 use_rev2 = xstrdup (user_file_rev);
578 /* and if not, it wasn't needed anyhow */
582 /* now, see if we really need to do the diff */
583 if (use_rev1 && use_rev2)
585 return (strcmp (use_rev1, use_rev2) == 0);
587 #endif /* SERVER_SUPPORT */
588 if (use_rev1 == NULL || strcmp (use_rev1, vers->vn_user) == 0)
590 if (strcmp (vers->ts_rcs, vers->ts_user) == 0 &&
591 (!(*options) || strcmp (options, vers->options) == 0))
595 if (use_rev1 == NULL)
596 use_rev1 = xstrdup (vers->vn_user);
600 * with 0 or 1 -r option specified, run a quick diff to see if we
601 * should bother with it at all.
603 retcode = RCS_checkout (vers->srcfile->path, NULL, use_rev1,
604 *options ? options : vers->options, tmpnam (tmp), 0, 0);
607 case 0: /* everything ok */
608 if (xcmp (file, tmp) == 0)
614 case -1: /* fork failed */
616 error (1, errno, "fork failed during checkout of %s",
617 vers->srcfile->path);