ftp.nice.ch/pub/next/unix/developer/cvs.950905.s.tar.gz#/cvs-1.5.1/src/diff.c

This is diff.c in view mode; [Download] [Up]

/*
 * 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"

#ifndef lint
static const char rcsid[] = "$CVSid: @(#)diff.c 1.61 94/10/22 $";
USE(rcsid)
#endif

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,
			     List *srcfiles, Vers_TS *vers));
static int diff_fileproc PROTO((char *file, char *update_dir, char *repository,
			  List * entries, List * srcfiles));
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);

#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)
	    send_files (argc, argv, local);
	else
	    send_file_names (argc, argv);
#else
	send_files (argc, argv, local, 0);
#endif

	if (fprintf (to_server, "diff\n") < 0)
	    error (1, errno, "writing to server");
        err = get_responses_and_close ();
	free (options);
	return (err);
    }
#endif

    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 (file, update_dir, repository, entries, srcfiles)
    char *file;
    char *update_dir;
    char *repository;
    List *entries;
    List *srcfiles;
{
    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 (repository, (char *) NULL, (char *) NULL, (char *) NULL,
		       file, 1, 0, entries, srcfiles);

    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", file);
	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", file);
	    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", file);
	    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", file);
	    freevers_ts (&vers);
	    diff_mark_errors (err);
	    return (err);
	}
	else
	{
	    if (vers->ts_user == NULL)
	    {
		error (0, 0, "cannot find %s", file);
		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 (file, repository, entries, srcfiles, vers))
    {
	freevers_ts (&vers);
	return (0);
    }

#ifdef DEATH_SUPPORT
    /* FIXME: Check whether use_rev1 and use_rev2 are dead and deal
       accordingly.  */
#endif

    /* Output an "Index:" line for patch to use */
    (void) fflush (stdout);
    if (update_dir[0])
	(void) printf ("Index: %s/%s\n", update_dir, file);
    else
	(void) printf ("Index: %s\n", file);
    (void) fflush (stdout);

    tocvsPath = wrap_tocvs_process_file(file);
    if (tocvsPath)
    {
	/* Backup the current version of the file to CVS/,,filename */
	sprintf(fname,"%s/%s%s",CVSADM, CVSPREFIX, file);
	if (unlink_file_dir (fname) < 0)
	    if (errno != ENOENT)
		error (1, errno, "cannot remove %s", file);
	rename_file (file, fname);
	/* Copy the wrapped file to the current directory then go to work */
	copy_file (tocvsPath, file);
    }

    if (empty_file == DIFF_ADDED || empty_file == DIFF_REMOVED)
    {
	(void) printf ("===================================================================\nRCS file: %s\n",
		       file);
	(void) printf ("diff -N %s\n", file);

	if (empty_file == DIFF_ADDED)
	{
	    run_setup ("%s %s %s %s", DIFF, opts, DEVNULL, file);
	}
	else
	{
	    /*
	     * 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".
	     */
	    run_setup ("%s%s -p -q %s -r%s", Rcsbin, RCS_CO,
		       *options ? options : vers->options, vers->vn_rcs);
	    run_arg (vers->srcfile->path);
	    if (run_exec (RUN_TTY, tmpnam (tmp), RUN_TTY, RUN_REALLY) == -1)
	    {
		(void) unlink (tmp);
		error (1, errno, "fork failed during checkout of %s",
		       vers->srcfile->path);
	    }

	    run_setup ("%s %s %s %s", DIFF, opts, tmp, DEVNULL);
	}
    }
    else
    {
	if (use_rev2)
	{
	    run_setup ("%s%s %s %s -r%s -r%s", Rcsbin, RCS_DIFF,
		       opts, *options ? options : vers->options,
		       use_rev1, use_rev2);
	}
	else
	{
	    run_setup ("%s%s %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 (file) < 0)
	    if (errno != ENOENT)
		error (1, errno, "cannot remove %s", file);

	rename_file (fname,file);
	if (unlink_file (tocvsPath) < 0)
	    error (1, errno, "cannot remove %s", 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
 */
/* 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??? */
    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, srcfiles, vers)
    char *file;
    char *repository;
    List *entries;
    List *srcfiles;
    Vers_TS *vers;
{
    Vers_TS *xvers;
    char tmp[L_tmpnam+1];

    /* 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, srcfiles);
	    if (xvers->vn_rcs == NULL)
	    {
		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);
		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, srcfiles);
	    if (xvers->vn_rcs == NULL)
	    {
		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);
		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.
     */
    run_setup ("%s%s -p -q %s -r%s", Rcsbin, RCS_CO,
	       *options ? options : vers->options, use_rev1);
    run_arg (vers->srcfile->path);
    switch (run_exec (RUN_TTY, tmpnam (tmp), RUN_TTY, RUN_REALLY))
    {
	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);
}

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.