home *** CD-ROM | disk | FTP | other *** search
- /*
- * Copyright (c) 1992, Brian Berliner and Jeff Polk
- * Copyright (c) 1989-1992, Brian Berliner
- *
- * You may distribute under the terms of the GNU General Public License as
- * specified in the README file that comes with the CVS 1.4 kit.
- *
- * Difference
- *
- * Run diff against versions in the repository. Options that are specified are
- * passed on directly to "rcsdiff".
- *
- * Without any file arguments, runs diff against all the currently modified
- * files.
- */
-
- #include "cvs.h"
-
- static Dtype diff_dirproc PROTO((char *dir, char *pos_repos, char *update_dir));
- static int diff_filesdoneproc PROTO((int err, char *repos, char *update_dir));
- static int diff_dirleaveproc PROTO((char *dir, int err, char *update_dir));
- static int diff_file_nodiff PROTO((char *file, char *repository, List *entries,
- RCSNode *rcs, Vers_TS *vers));
- static int diff_fileproc PROTO((struct file_info *finfo));
- static void diff_mark_errors PROTO((int err));
-
- static char *diff_rev1, *diff_rev2;
- static char *diff_date1, *diff_date2;
- static char *use_rev1, *use_rev2;
-
- #ifdef SERVER_SUPPORT
- /* Revision of the user file, if it is unchanged from something in the
- repository and we want to use that fact. */
- static char *user_file_rev;
- #endif
-
- static char *options;
- static char opts[PATH_MAX];
- static int diff_errors;
- static int empty_files = 0;
-
- static const char *const diff_usage[] =
- {
- "Usage: %s %s [-lN] [rcsdiff-options]\n",
- #ifdef CVS_DIFFDATE
- " [[-r rev1 | -D date1] [-r rev2 | -D date2]] [files...] \n",
- #else
- " [-r rev1 [-r rev2]] [files...] \n",
- #endif
- "\t-l\tLocal directory only, not recursive\n",
- "\t-D d1\tDiff revision for date against working file.\n",
- "\t-D d2\tDiff rev1/date1 against date2.\n",
- "\t-N\tinclude diffs for added and removed files.\n",
- "\t-r rev1\tDiff revision for rev1 against working file.\n",
- "\t-r rev2\tDiff rev1/date1 against rev2.\n",
- NULL
- };
-
- int
- diff (argc, argv)
- int argc;
- char **argv;
- {
- char tmp[50];
- int c, err = 0;
- int local = 0;
- int which;
-
- if (argc == -1)
- usage (diff_usage);
-
- /*
- * Note that we catch all the valid arguments here, so that we can
- * intercept the -r arguments for doing revision diffs; and -l/-R for a
- * non-recursive/recursive diff.
- */
- #ifdef SERVER_SUPPORT
- /* Need to be able to do this command more than once (according to
- the protocol spec, even if the current client doesn't use it). */
- opts[0] = '\0';
- #endif
- optind = 1;
- while ((c = getopt (argc, argv,
- "abcdefhilnpqtuw0123456789BHNQRTC:D:F:I:L:V:k:r:")) != -1)
- {
- switch (c)
- {
- case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
- case 'h': case 'i': case 'n': case 'p': case 't': case 'u':
- case 'w': case '0': case '1': case '2': case '3': case '4':
- case '5': case '6': case '7': case '8': case '9': case 'B':
- case 'H': case 'T': case 'Q':
- (void) sprintf (tmp, " -%c", (char) c);
- (void) strcat (opts, tmp);
- if (c == 'Q')
- {
- quiet = 1;
- really_quiet = 1;
- c = 'q';
- }
- break;
- case 'C': case 'F': case 'I': case 'L': case 'V':
- #ifndef CVS_DIFFDATE
- case 'D':
- #endif
- (void) sprintf (tmp, " -%c%s", (char) c, optarg);
- (void) strcat (opts, tmp);
- break;
- case 'R':
- local = 0;
- break;
- case 'l':
- local = 1;
- break;
- case 'q':
- quiet = 1;
- break;
- case 'k':
- if (options)
- free (options);
- options = RCS_check_kflag (optarg);
- break;
- case 'r':
- if (diff_rev2 != NULL || diff_date2 != NULL)
- error (1, 0,
- "no more than two revisions/dates can be specified");
- if (diff_rev1 != NULL || diff_date1 != NULL)
- diff_rev2 = optarg;
- else
- diff_rev1 = optarg;
- break;
- #ifdef CVS_DIFFDATE
- case 'D':
- if (diff_rev2 != NULL || diff_date2 != NULL)
- error (1, 0,
- "no more than two revisions/dates can be specified");
- if (diff_rev1 != NULL || diff_date1 != NULL)
- diff_date2 = Make_Date (optarg);
- else
- diff_date1 = Make_Date (optarg);
- break;
- #endif
- case 'N':
- empty_files = 1;
- break;
- case '?':
- default:
- usage (diff_usage);
- break;
- }
- }
- argc -= optind;
- argv += optind;
-
- /* make sure options is non-null */
- if (!options)
- options = xstrdup ("");
-
- #ifdef CLIENT_SUPPORT
- if (client_active) {
- /* We're the client side. Fire up the remote server. */
- start_server ();
-
- ign_setup ();
-
- if (local)
- send_arg("-l");
- if (empty_files)
- send_arg("-N");
- send_option_string (opts);
- if (diff_rev1)
- option_with_arg ("-r", diff_rev1);
- if (diff_date1)
- client_senddate (diff_date1);
- if (diff_rev2)
- option_with_arg ("-r", diff_rev2);
- if (diff_date2)
- client_senddate (diff_date2);
-
- send_file_names (argc, argv, SEND_EXPAND_WILD);
- #if 0
- /* FIXME: We shouldn't have to send current files to diff two
- revs, but it doesn't work yet and I haven't debugged it.
- So send the files -- it's slower but it works.
- gnu@cygnus.com Apr94 */
- /* Send the current files unless diffing two revs from the archive */
- if (diff_rev2 == NULL && diff_date2 == NULL)
- #endif
- send_files (argc, argv, local, 0);
-
- send_to_server ("diff\012", 0);
- err = get_responses_and_close ();
- free (options);
- return (err);
- }
- #endif
-
- if (diff_rev1 != NULL)
- tag_check_valid (diff_rev1, argc, argv, local, 0, "");
- if (diff_rev2 != NULL)
- tag_check_valid (diff_rev2, argc, argv, local, 0, "");
-
- which = W_LOCAL;
- if (diff_rev2 != NULL || diff_date2 != NULL)
- which |= W_REPOS | W_ATTIC;
-
- wrap_setup ();
-
- /* start the recursion processor */
- err = start_recursion (diff_fileproc, diff_filesdoneproc, diff_dirproc,
- diff_dirleaveproc, argc, argv, local,
- which, 0, 1, (char *) NULL, 1, 0);
-
- /* clean up */
- free (options);
- return (err);
- }
-
- /*
- * Do a file diff
- */
- /* ARGSUSED */
- static int
- diff_fileproc (finfo)
- struct file_info *finfo;
- {
- int status, err = 2; /* 2 == trouble, like rcsdiff */
- Vers_TS *vers;
- enum {
- DIFF_ERROR,
- DIFF_ADDED,
- DIFF_REMOVED,
- DIFF_NEITHER
- } empty_file = DIFF_NEITHER;
- char tmp[L_tmpnam+1];
- char *tocvsPath;
- char fname[PATH_MAX];
-
- #ifdef SERVER_SUPPORT
- user_file_rev = 0;
- #endif
- vers = Version_TS (finfo->repository, (char *) NULL, (char *) NULL, (char *) NULL,
- finfo->file, 1, 0, finfo->entries, finfo->rcs);
-
- if (diff_rev2 != NULL || diff_date2 != NULL)
- {
- /* Skip all the following checks regarding the user file; we're
- not using it. */
- }
- else if (vers->vn_user == NULL)
- {
- error (0, 0, "I know nothing about %s", finfo->fullname);
- freevers_ts (&vers);
- diff_mark_errors (err);
- return (err);
- }
- else if (vers->vn_user[0] == '0' && vers->vn_user[1] == '\0')
- {
- if (empty_files)
- empty_file = DIFF_ADDED;
- else
- {
- error (0, 0, "%s is a new entry, no comparison available",
- finfo->fullname);
- freevers_ts (&vers);
- diff_mark_errors (err);
- return (err);
- }
- }
- else if (vers->vn_user[0] == '-')
- {
- if (empty_files)
- empty_file = DIFF_REMOVED;
- else
- {
- error (0, 0, "%s was removed, no comparison available",
- finfo->fullname);
- freevers_ts (&vers);
- diff_mark_errors (err);
- return (err);
- }
- }
- else
- {
- if (vers->vn_rcs == NULL && vers->srcfile == NULL)
- {
- error (0, 0, "cannot find revision control file for %s",
- finfo->fullname);
- freevers_ts (&vers);
- diff_mark_errors (err);
- return (err);
- }
- else
- {
- if (vers->ts_user == NULL)
- {
- error (0, 0, "cannot find %s", finfo->fullname);
- freevers_ts (&vers);
- diff_mark_errors (err);
- return (err);
- }
- #ifdef SERVER_SUPPORT
- else if (!strcmp (vers->ts_user, vers->ts_rcs))
- {
- /* The user file matches some revision in the repository
- Diff against the repository (for remote CVS, we might not
- have a copy of the user file around). */
- user_file_rev = vers->vn_user;
- }
- #endif
- }
- }
-
- if (empty_file == DIFF_NEITHER && diff_file_nodiff (finfo->file, finfo->repository, finfo->entries, finfo->rcs, vers))
- {
- freevers_ts (&vers);
- return (0);
- }
-
- /* FIXME: Check whether use_rev1 and use_rev2 are dead and deal
- accordingly. */
-
- /* Output an "Index:" line for patch to use */
- (void) fflush (stdout);
- (void) printf ("Index: %s\n", finfo->fullname);
- (void) fflush (stdout);
-
- tocvsPath = wrap_tocvs_process_file(finfo->file);
- if (tocvsPath)
- {
- /* Backup the current version of the file to CVS/,,filename */
- sprintf(fname,"%s/%s%s",CVSADM, CVSPREFIX, finfo->file);
- if (unlink_file_dir (fname) < 0)
- if (! existence_error (errno))
- error (1, errno, "cannot remove %s", finfo->file);
- rename_file (finfo->file, fname);
- /* Copy the wrapped file to the current directory then go to work */
- copy_file (tocvsPath, finfo->file);
- }
-
- if (empty_file == DIFF_ADDED || empty_file == DIFF_REMOVED)
- {
- /* This is file, not fullname, because it is the "Index:" line which
- is supposed to contain the directory. */
- (void) printf ("===================================================================\nRCS file: %s\n",
- finfo->file);
- (void) printf ("diff -N %s\n", finfo->file);
-
- if (empty_file == DIFF_ADDED)
- {
- run_setup ("%s %s %s %s", DIFF, opts, DEVNULL, finfo->file);
- }
- else
- {
- int retcode;
-
- /*
- * FIXME: Should be setting use_rev1 using the logic in
- * diff_file_nodiff, and using that revision. This code
- * is broken for "cvs diff -N -r foo".
- */
- retcode = RCS_checkout (vers->srcfile->path, NULL, vers->vn_rcs,
- *options ? options : vers->options, tmpnam (tmp),
- 0, 0);
- if (retcode == -1)
- {
- (void) unlink (tmp);
- error (1, errno, "fork failed during checkout of %s",
- vers->srcfile->path);
- }
- /* FIXME: what if retcode > 0? */
-
- run_setup ("%s %s %s %s", DIFF, opts, tmp, DEVNULL);
- }
- }
- else
- {
- if (use_rev2)
- {
- run_setup ("%s%s -x,v/ %s %s -r%s -r%s", Rcsbin, RCS_DIFF,
- opts, *options ? options : vers->options,
- use_rev1, use_rev2);
- }
- else
- {
- run_setup ("%s%s -x,v/ %s %s -r%s", Rcsbin, RCS_DIFF, opts,
- *options ? options : vers->options, use_rev1);
- }
- run_arg (vers->srcfile->path);
- }
-
- switch ((status = run_exec (RUN_TTY, RUN_TTY, RUN_TTY,
- RUN_REALLY|RUN_COMBINED)))
- {
- case -1: /* fork failed */
- error (1, errno, "fork failed during rcsdiff of %s",
- vers->srcfile->path);
- case 0: /* everything ok */
- err = 0;
- break;
- default: /* other error */
- err = status;
- break;
- }
-
- if (tocvsPath)
- {
- if (unlink_file_dir (finfo->file) < 0)
- if (! existence_error (errno))
- error (1, errno, "cannot remove %s", finfo->file);
-
- rename_file (fname,finfo->file);
- if (unlink_file (tocvsPath) < 0)
- error (1, errno, "cannot remove %s", finfo->file);
- }
-
- if (empty_file == DIFF_REMOVED)
- (void) unlink (tmp);
-
- (void) fflush (stdout);
- freevers_ts (&vers);
- diff_mark_errors (err);
- return (err);
- }
-
- /*
- * Remember the exit status for each file.
- */
- static void
- diff_mark_errors (err)
- int err;
- {
- if (err > diff_errors)
- diff_errors = err;
- }
-
- /*
- * Print a warm fuzzy message when we enter a dir
- *
- * Don't try to diff directories that don't exist! -- DW
- */
- /* ARGSUSED */
- static Dtype
- diff_dirproc (dir, pos_repos, update_dir)
- char *dir;
- char *pos_repos;
- char *update_dir;
- {
- /* XXX - check for dirs we don't want to process??? */
-
- /* YES ... for instance dirs that don't exist!!! -- DW */
- if (!isdir (dir) )
- return (R_SKIP_ALL);
-
- if (!quiet)
- error (0, 0, "Diffing %s", update_dir);
- return (R_PROCESS);
- }
-
- /*
- * Concoct the proper exit status - done with files
- */
- /* ARGSUSED */
- static int
- diff_filesdoneproc (err, repos, update_dir)
- int err;
- char *repos;
- char *update_dir;
- {
- return (diff_errors);
- }
-
- /*
- * Concoct the proper exit status - leaving directories
- */
- /* ARGSUSED */
- static int
- diff_dirleaveproc (dir, err, update_dir)
- char *dir;
- int err;
- char *update_dir;
- {
- return (diff_errors);
- }
-
- /*
- * verify that a file is different 0=same 1=different
- */
- static int
- diff_file_nodiff (file, repository, entries, rcs, vers)
- char *file;
- char *repository;
- List *entries;
- RCSNode *rcs;
- Vers_TS *vers;
- {
- Vers_TS *xvers;
- char tmp[L_tmpnam+1];
- int retcode;
-
- /* free up any old use_rev* variables and reset 'em */
- if (use_rev1)
- free (use_rev1);
- if (use_rev2)
- free (use_rev2);
- use_rev1 = use_rev2 = (char *) NULL;
-
- if (diff_rev1 || diff_date1)
- {
- /* special handling for TAG_HEAD */
- if (diff_rev1 && strcmp (diff_rev1, TAG_HEAD) == 0)
- use_rev1 = xstrdup (vers->vn_rcs);
- else
- {
- xvers = Version_TS (repository, (char *) NULL, diff_rev1,
- diff_date1, file, 1, 0, entries, rcs);
- if (xvers->vn_rcs == NULL)
- {
- /* Don't gripe if it doesn't exist, just ignore! */
- if (! isfile (file))
- /* null statement */ ;
- else if (diff_rev1)
- error (0, 0, "tag %s is not in file %s", diff_rev1, file);
- else
- error (0, 0, "no revision for date %s in file %s",
- diff_date1, file);
-
- freevers_ts (&xvers);
- return (1);
- }
- use_rev1 = xstrdup (xvers->vn_rcs);
- freevers_ts (&xvers);
- }
- }
- if (diff_rev2 || diff_date2)
- {
- /* special handling for TAG_HEAD */
- if (diff_rev2 && strcmp (diff_rev2, TAG_HEAD) == 0)
- use_rev2 = xstrdup (vers->vn_rcs);
- else
- {
- xvers = Version_TS (repository, (char *) NULL, diff_rev2,
- diff_date2, file, 1, 0, entries, rcs);
- if (xvers->vn_rcs == NULL)
- {
- /* Don't gripe if it doesn't exist, just ignore! */
- if (! isfile (file))
- /* null statement */ ;
- else if (diff_rev1)
- error (0, 0, "tag %s is not in file %s", diff_rev2, file);
- else
- error (0, 0, "no revision for date %s in file %s",
- diff_date2, file);
-
- freevers_ts (&xvers);
- return (1);
- }
- use_rev2 = xstrdup (xvers->vn_rcs);
- freevers_ts (&xvers);
- }
-
- /* now, see if we really need to do the diff */
- if (use_rev1 && use_rev2) {
- return (strcmp (use_rev1, use_rev2) == 0);
- } else {
- error(0, 0, "No HEAD revision for file %s", file);
- return (1);
- }
- }
- #ifdef SERVER_SUPPORT
- if (user_file_rev)
- {
- /* drop user_file_rev into first unused use_rev */
- if (!use_rev1)
- use_rev1 = xstrdup (user_file_rev);
- else if (!use_rev2)
- use_rev2 = xstrdup (user_file_rev);
- /* and if not, it wasn't needed anyhow */
- user_file_rev = 0;
- }
-
- /* now, see if we really need to do the diff */
- if (use_rev1 && use_rev2)
- {
- return (strcmp (use_rev1, use_rev2) == 0);
- }
- #endif /* SERVER_SUPPORT */
- if (use_rev1 == NULL || strcmp (use_rev1, vers->vn_user) == 0)
- {
- if (strcmp (vers->ts_rcs, vers->ts_user) == 0 &&
- (!(*options) || strcmp (options, vers->options) == 0))
- {
- return (1);
- }
- if (use_rev1 == NULL)
- use_rev1 = xstrdup (vers->vn_user);
- }
-
- /*
- * with 0 or 1 -r option specified, run a quick diff to see if we
- * should bother with it at all.
- */
- retcode = RCS_checkout (vers->srcfile->path, NULL, use_rev1,
- *options ? options : vers->options, tmpnam (tmp), 0, 0);
- switch (retcode)
- {
- case 0: /* everything ok */
- if (xcmp (file, tmp) == 0)
- {
- (void) unlink (tmp);
- return (1);
- }
- break;
- case -1: /* fork failed */
- (void) unlink (tmp);
- error (1, errno, "fork failed during checkout of %s",
- vers->srcfile->path);
- default:
- break;
- }
- (void) unlink (tmp);
- return (0);
- }
-