summaryrefslogtreecommitdiffstats
path: root/contrib/cvs/src/diff.c
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/cvs/src/diff.c')
-rw-r--r--contrib/cvs/src/diff.c623
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);
+}
OpenPOWER on IntegriCloud