diff options
Diffstat (limited to 'contrib/cvs/src/diff.c')
-rw-r--r-- | contrib/cvs/src/diff.c | 623 |
1 files changed, 623 insertions, 0 deletions
diff --git a/contrib/cvs/src/diff.c b/contrib/cvs/src/diff.c new file mode 100644 index 0000000..7520cec --- /dev/null +++ b/contrib/cvs/src/diff.c @@ -0,0 +1,623 @@ +/* + * 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); +} |