117721Speter/*
2175282Sobrien * Copyright (C) 1986-2005 The Free Software Foundation, Inc.
3175282Sobrien *
4175282Sobrien * Portions Copyright (C) 1998-2005 Derek Price, Ximbiot <http://ximbiot.com>,
5175282Sobrien *                                  and others.
6175282Sobrien *
7175282Sobrien * Portions Copyright (C) 1992, Brian Berliner and Jeff Polk
8175282Sobrien * Portions Copyright (C) 1989-1992, Brian Berliner
917721Speter *
1017721Speter * You may distribute under the terms of the GNU General Public License as
1132785Speter * specified in the README file that comes with the CVS source distribution.
1217721Speter *
1317721Speter * Patch
1417721Speter *
1517721Speter * Create a Larry Wall format "patch" file between a previous release and the
1617721Speter * current head of a module, or between two releases.  Can specify the
1717721Speter * release as either a date or a revision number.
18145406Ssimon *
19145406Ssimon * $FreeBSD$
2017721Speter */
2117721Speter
2281404Speter#include <assert.h>
2317721Speter#include "cvs.h"
2417721Speter#include "getline.h"
2517721Speter
2617721Speterstatic RETSIGTYPE patch_cleanup PROTO((void));
27128266Speterstatic Dtype patch_dirproc PROTO ((void *callerdat, const char *dir,
28128266Speter				   const char *repos, const char *update_dir,
2925839Speter				   List *entries));
3025839Speterstatic int patch_fileproc PROTO ((void *callerdat, struct file_info *finfo));
3166525Speterstatic int patch_proc PROTO((int argc, char **argv, char *xwhere,
3217721Speter		       char *mwhere, char *mfile, int shorten,
3317721Speter		       int local_specified, char *mname, char *msg));
3417721Speter
3517721Speterstatic int force_tag_match = 1;
3617721Speterstatic int patch_short = 0;
3717721Speterstatic int toptwo_diffs = 0;
3817721Speterstatic char *options = NULL;
3917721Speterstatic char *rev1 = NULL;
4025839Speterstatic int rev1_validated = 0;
4117721Speterstatic char *rev2 = NULL;
4225839Speterstatic int rev2_validated = 0;
4317721Speterstatic char *date1 = NULL;
4417721Speterstatic char *date2 = NULL;
4525839Speterstatic char *tmpfile1 = NULL;
4625839Speterstatic char *tmpfile2 = NULL;
4725839Speterstatic char *tmpfile3 = NULL;
4817721Speterstatic int unidiff = 0;
4917721Speter
5017721Speterstatic const char *const patch_usage[] =
5117721Speter{
52175282Sobrien    "Usage: %s %s [-flR] [-c|-u] [-s|-t] [-V %%d] [-k kopt]\n",
5317721Speter    "    -r rev|-D date [-r rev2 | -D date2] modules...\n",
5417721Speter    "\t-f\tForce a head revision match if tag/date not found.\n",
5517721Speter    "\t-l\tLocal directory only, not recursive\n",
5625839Speter    "\t-R\tProcess directories recursively.\n",
5717721Speter    "\t-c\tContext diffs (default)\n",
5817721Speter    "\t-u\tUnidiff format.\n",
5917721Speter    "\t-s\tShort patch - one liner per file.\n",
6017721Speter    "\t-t\tTop two diffs - last change made to the file.\n",
61175282Sobrien    "\t-V vers\tUse RCS Version \"vers\" for keyword expansion.\n",
62175282Sobrien    "\t-k kopt\tSpecify keyword expansion mode.\n",
6317721Speter    "\t-D date\tDate.\n",
6417721Speter    "\t-r rev\tRevision - symbolic or numeric.\n",
6532785Speter    "(Specify the --help global option for a list of other help options)\n",
6617721Speter    NULL
6717721Speter};
6817721Speter
69128266Speter
70128266Speter
7117721Speterint
7217721Speterpatch (argc, argv)
7317721Speter    int argc;
7417721Speter    char **argv;
7517721Speter{
7617721Speter    register int i;
77102840Speter    int local = 0;
7817721Speter    int c;
7917721Speter    int err = 0;
8017721Speter    DBM *db;
8117721Speter
8217721Speter    if (argc == -1)
8317721Speter	usage (patch_usage);
8417721Speter
8526065Speter    optind = 0;
8625839Speter    while ((c = getopt (argc, argv, "+V:k:cuftsQqlRD:r:")) != -1)
8717721Speter    {
8817721Speter	switch (c)
8917721Speter	{
9017721Speter	    case 'Q':
9117721Speter	    case 'q':
9217721Speter		/* The CVS 1.5 client sends these options (in addition to
9317721Speter		   Global_option requests), so we must ignore them.  */
9417721Speter		if (!server_active)
9517721Speter		    error (1, 0,
9617721Speter			   "-q or -Q must be specified before \"%s\"",
97128266Speter			   cvs_cmd_name);
9817721Speter		break;
9917721Speter	    case 'f':
10017721Speter		force_tag_match = 0;
10117721Speter		break;
10217721Speter	    case 'l':
10317721Speter		local = 1;
10417721Speter		break;
10517721Speter	    case 'R':
10617721Speter		local = 0;
10717721Speter		break;
10817721Speter	    case 't':
10917721Speter		toptwo_diffs = 1;
11017721Speter		break;
11117721Speter	    case 's':
11217721Speter		patch_short = 1;
11317721Speter		break;
11417721Speter	    case 'D':
11517721Speter		if (rev2 != NULL || date2 != NULL)
11617721Speter		    error (1, 0,
11717721Speter		       "no more than two revisions/dates can be specified");
11817721Speter		if (rev1 != NULL || date1 != NULL)
11917721Speter		    date2 = Make_Date (optarg);
12017721Speter		else
12117721Speter		    date1 = Make_Date (optarg);
12217721Speter		break;
12317721Speter	    case 'r':
12417721Speter		if (rev2 != NULL || date2 != NULL)
12517721Speter		    error (1, 0,
12617721Speter		       "no more than two revisions/dates can be specified");
12717721Speter		if (rev1 != NULL || date1 != NULL)
12817721Speter		    rev2 = optarg;
12917721Speter		else
13017721Speter		    rev1 = optarg;
13117721Speter		break;
13217721Speter	    case 'k':
13317721Speter		if (options)
13417721Speter		    free (options);
13517721Speter		options = RCS_check_kflag (optarg);
13617721Speter		break;
13717721Speter	    case 'V':
13825839Speter		/* This option is pretty seriously broken:
13925839Speter		   1.  It is not clear what it does (does it change keyword
14025839Speter		   expansion behavior?  If so, how?  Or does it have
14125839Speter		   something to do with what version of RCS we are using?
14225839Speter		   Or the format we write RCS files in?).
14325839Speter		   2.  Because both it and -k use the options variable,
14425839Speter		   specifying both -V and -k doesn't work.
14525839Speter		   3.  At least as of CVS 1.9, it doesn't work (failed
14625839Speter		   assertion in RCS_checkout where it asserts that options
14725839Speter		   starts with -k).  Few people seem to be complaining.
14825839Speter		   In the future (perhaps the near future), I have in mind
14925839Speter		   removing it entirely, and updating NEWS and cvs.texinfo,
15025839Speter		   but in case it is a good idea to give people more time
15125839Speter		   to complain if they would miss it, I'll just add this
15225839Speter		   quick and dirty error message for now.  */
15325839Speter		error (1, 0,
15425839Speter		       "the -V option is obsolete and should not be used");
15525839Speter#if 0
15617721Speter		if (atoi (optarg) <= 0)
15717721Speter		    error (1, 0, "must specify a version number to -V");
15817721Speter		if (options)
15917721Speter		    free (options);
16017721Speter		options = xmalloc (strlen (optarg) + 1 + 2);	/* for the -V */
16117721Speter		(void) sprintf (options, "-V%s", optarg);
16225839Speter#endif
16317721Speter		break;
16417721Speter	    case 'u':
16517721Speter		unidiff = 1;		/* Unidiff */
16617721Speter		break;
16717721Speter	    case 'c':			/* Context diff */
16817721Speter		unidiff = 0;
16917721Speter		break;
17017721Speter	    case '?':
17117721Speter	    default:
17217721Speter		usage (patch_usage);
17317721Speter		break;
17417721Speter	}
17517721Speter    }
17617721Speter    argc -= optind;
17717721Speter    argv += optind;
17817721Speter
17917721Speter    /* Sanity checks */
18017721Speter    if (argc < 1)
18117721Speter	usage (patch_usage);
18217721Speter
18317721Speter    if (toptwo_diffs && patch_short)
18417721Speter	error (1, 0, "-t and -s options are mutually exclusive");
18517721Speter    if (toptwo_diffs && (date1 != NULL || date2 != NULL ||
18617721Speter			 rev1 != NULL || rev2 != NULL))
18717721Speter	error (1, 0, "must not specify revisions/dates with -t option!");
18817721Speter
18917721Speter    if (!toptwo_diffs && (date1 == NULL && date2 == NULL &&
19017721Speter			  rev1 == NULL && rev2 == NULL))
19117721Speter	error (1, 0, "must specify at least one revision/date!");
19217721Speter    if (date1 != NULL && date2 != NULL)
19317721Speter	if (RCS_datecmp (date1, date2) >= 0)
19417721Speter	    error (1, 0, "second date must come after first date!");
19517721Speter
19617721Speter    /* if options is NULL, make it a NULL string */
19717721Speter    if (options == NULL)
19817721Speter	options = xstrdup ("");
19917721Speter
20017721Speter#ifdef CLIENT_SUPPORT
20181404Speter    if (current_parsed_root->isremote)
20217721Speter    {
20317721Speter	/* We're the client side.  Fire up the remote server.  */
20417721Speter	start_server ();
20517721Speter
20617721Speter	ign_setup ();
20717721Speter
20817721Speter	if (local)
20917721Speter	    send_arg("-l");
21025839Speter	if (!force_tag_match)
21117721Speter	    send_arg("-f");
21217721Speter	if (toptwo_diffs)
21317721Speter	    send_arg("-t");
21417721Speter	if (patch_short)
21517721Speter	    send_arg("-s");
21617721Speter	if (unidiff)
21717721Speter	    send_arg("-u");
21817721Speter
21917721Speter	if (rev1)
22017721Speter	    option_with_arg ("-r", rev1);
22117721Speter	if (date1)
22217721Speter	    client_senddate (date1);
22317721Speter	if (rev2)
22417721Speter	    option_with_arg ("-r", rev2);
22517721Speter	if (date2)
22617721Speter	    client_senddate (date2);
22717721Speter	if (options[0] != '\0')
22817721Speter	    send_arg (options);
22917721Speter
23017721Speter	{
23117721Speter	    int i;
23217721Speter	    for (i = 0; i < argc; ++i)
23317721Speter		send_arg (argv[i]);
23417721Speter	}
23517721Speter
23617721Speter	send_to_server ("rdiff\012", 0);
23717721Speter        return get_responses_and_close ();
23817721Speter    }
23917721Speter#endif
24017721Speter
24117721Speter    /* clean up if we get a signal */
24266525Speter#ifdef SIGABRT
243128266Speter    (void)SIG_register (SIGABRT, patch_cleanup);
24466525Speter#endif
24517721Speter#ifdef SIGHUP
246128266Speter    (void)SIG_register (SIGHUP, patch_cleanup);
24717721Speter#endif
24817721Speter#ifdef SIGINT
249128266Speter    (void)SIG_register (SIGINT, patch_cleanup);
25017721Speter#endif
25117721Speter#ifdef SIGQUIT
252128266Speter    (void)SIG_register (SIGQUIT, patch_cleanup);
25317721Speter#endif
25417721Speter#ifdef SIGPIPE
255128266Speter    (void)SIG_register (SIGPIPE, patch_cleanup);
25617721Speter#endif
25717721Speter#ifdef SIGTERM
258128266Speter    (void)SIG_register (SIGTERM, patch_cleanup);
25917721Speter#endif
26017721Speter
26117721Speter    db = open_module ();
26217721Speter    for (i = 0; i < argc; i++)
26317721Speter	err += do_module (db, argv[i], PATCH, "Patching", patch_proc,
264128266Speter			  (char *)NULL, 0, local, 0, 0, (char *)NULL);
26517721Speter    close_module (db);
26617721Speter    free (options);
26717721Speter    patch_cleanup ();
268128266Speter    return err;
26917721Speter}
27017721Speter
271128266Speter
272128266Speter
27317721Speter/*
27417721Speter * callback proc for doing the real work of patching
27517721Speter */
27617721Speter/* ARGSUSED */
27717721Speterstatic int
27866525Speterpatch_proc (argc, argv, xwhere, mwhere, mfile, shorten, local_specified,
27917721Speter	    mname, msg)
28066525Speter    int argc;
28117721Speter    char **argv;
28217721Speter    char *xwhere;
28317721Speter    char *mwhere;
28417721Speter    char *mfile;
28517721Speter    int shorten;
28617721Speter    int local_specified;
28717721Speter    char *mname;
28817721Speter    char *msg;
28917721Speter{
29066525Speter    char *myargv[2];
29117721Speter    int err = 0;
29217721Speter    int which;
29325839Speter    char *repository;
29425839Speter    char *where;
29517721Speter
296128266Speter    repository = xmalloc (strlen (current_parsed_root->directory)
297128266Speter                          + strlen (argv[0])
29881404Speter			  + (mfile == NULL ? 0 : strlen (mfile) + 1) + 2);
299128266Speter    (void)sprintf (repository, "%s/%s",
300128266Speter                   current_parsed_root->directory, argv[0]);
30181404Speter    where = xmalloc (strlen (argv[0]) + (mfile == NULL ? 0 : strlen (mfile) + 1)
30281404Speter		     + 1);
303128266Speter    (void)strcpy (where, argv[0]);
30417721Speter
30517721Speter    /* if mfile isn't null, we need to set up to do only part of the module */
30617721Speter    if (mfile != NULL)
30717721Speter    {
30817721Speter	char *cp;
30925839Speter	char *path;
31017721Speter
31117721Speter	/* if the portion of the module is a path, put the dir part on repos */
31217721Speter	if ((cp = strrchr (mfile, '/')) != NULL)
31317721Speter	{
31417721Speter	    *cp = '\0';
315128266Speter	    (void)strcat (repository, "/");
316128266Speter	    (void)strcat (repository, mfile);
317128266Speter	    (void)strcat (where, "/");
318128266Speter	    (void)strcat (where, mfile);
31917721Speter	    mfile = cp + 1;
32017721Speter	}
32117721Speter
32217721Speter	/* take care of the rest */
32381404Speter	path = xmalloc (strlen (repository) + strlen (mfile) + 2);
324128266Speter	(void)sprintf (path, "%s/%s", repository, mfile);
32517721Speter	if (isdir (path))
32617721Speter	{
32717721Speter	    /* directory means repository gets the dir tacked on */
328128266Speter	    (void)strcpy (repository, path);
329128266Speter	    (void)strcat (where, "/");
330128266Speter	    (void)strcat (where, mfile);
33117721Speter	}
33217721Speter	else
33317721Speter	{
33466525Speter	    myargv[0] = argv[0];
33566525Speter	    myargv[1] = mfile;
33666525Speter	    argc = 2;
33766525Speter	    argv = myargv;
33817721Speter	}
33925839Speter	free (path);
34017721Speter    }
34117721Speter
34217721Speter    /* cd to the starting repository */
34325839Speter    if ( CVS_CHDIR (repository) < 0)
34417721Speter    {
34517721Speter	error (0, errno, "cannot chdir to %s", repository);
34625839Speter	free (repository);
347175282Sobrien	free (where);
348128266Speter	return 1;
34917721Speter    }
35017721Speter
35117721Speter    if (force_tag_match)
35217721Speter	which = W_REPOS | W_ATTIC;
35317721Speter    else
35417721Speter	which = W_REPOS;
35517721Speter
35617721Speter    if (rev1 != NULL && !rev1_validated)
35717721Speter    {
358128266Speter	tag_check_valid (rev1, argc - 1, argv + 1, local_specified, 0,
359128266Speter			 repository);
36017721Speter	rev1_validated = 1;
36117721Speter    }
36217721Speter    if (rev2 != NULL && !rev2_validated)
36317721Speter    {
364128266Speter	tag_check_valid (rev2, argc - 1, argv + 1, local_specified, 0,
365128266Speter			 repository);
36617721Speter	rev2_validated = 1;
36717721Speter    }
36817721Speter
36917721Speter    /* start the recursion processor */
370128266Speter    err = start_recursion (patch_fileproc, (FILESDONEPROC)NULL, patch_dirproc,
371128266Speter			   (DIRLEAVEPROC)NULL, NULL,
372102840Speter			   argc - 1, argv + 1, local_specified,
373128266Speter			   which, 0, CVS_LOCK_READ, where, 1, repository);
374128266Speter    free (repository);
37525839Speter    free (where);
37617721Speter
377128266Speter    return err;
37817721Speter}
37917721Speter
380128266Speter
381128266Speter
38217721Speter/*
38317721Speter * Called to examine a particular RCS file, as appropriate with the options
38417721Speter * that were set above.
38517721Speter */
38617721Speter/* ARGSUSED */
38717721Speterstatic int
38825839Speterpatch_fileproc (callerdat, finfo)
38925839Speter    void *callerdat;
39017721Speter    struct file_info *finfo;
39117721Speter{
39217721Speter    struct utimbuf t;
39317721Speter    char *vers_tag, *vers_head;
39425839Speter    char *rcs = NULL;
395145406Ssimon    char *rcs_orig = NULL;
39617721Speter    RCSNode *rcsfile;
39717721Speter    FILE *fp1, *fp2, *fp3;
39817721Speter    int ret = 0;
39917721Speter    int isattic = 0;
40017721Speter    int retcode = 0;
40125839Speter    char *file1;
40225839Speter    char *file2;
40325839Speter    char *strippath;
40417721Speter    char *line1, *line2;
40517721Speter    size_t line1_chars_allocated;
40617721Speter    size_t line2_chars_allocated;
40717721Speter    char *cp1, *cp2;
40817721Speter    FILE *fp;
40932785Speter    int line_length;
410175282Sobrien    int dargc = 0;
411175282Sobrien    size_t darg_allocated = 0;
412175282Sobrien    char **dargv = NULL;
41317721Speter
41425839Speter    line1 = NULL;
41525839Speter    line1_chars_allocated = 0;
41625839Speter    line2 = NULL;
41725839Speter    line2_chars_allocated = 0;
418128266Speter    vers_tag = vers_head = NULL;
41925839Speter
42017721Speter    /* find the parsed rcs file */
42117721Speter    if ((rcsfile = finfo->rcs) == NULL)
42225839Speter    {
42325839Speter	ret = 1;
42425839Speter	goto out2;
42525839Speter    }
42617721Speter    if ((rcsfile->flags & VALID) && (rcsfile->flags & INATTIC))
42717721Speter	isattic = 1;
42817721Speter
429145406Ssimon    rcs_orig = rcs = xmalloc (strlen (finfo->file) + sizeof (RCSEXT) + 5);
43017721Speter    (void) sprintf (rcs, "%s%s", finfo->file, RCSEXT);
43117721Speter
43217721Speter    /* if vers_head is NULL, may have been removed from the release */
43317721Speter    if (isattic && rev2 == NULL && date2 == NULL)
43417721Speter	vers_head = NULL;
43517721Speter    else
43625839Speter    {
43725839Speter	vers_head = RCS_getversion (rcsfile, rev2, date2, force_tag_match,
43825839Speter				    (int *) NULL);
43925839Speter	if (vers_head != NULL && RCS_isdead (rcsfile, vers_head))
44025839Speter	{
44125839Speter	    free (vers_head);
44225839Speter	    vers_head = NULL;
44325839Speter	}
44425839Speter    }
44517721Speter
44617721Speter    if (toptwo_diffs)
44717721Speter    {
44817721Speter	if (vers_head == NULL)
44925839Speter	{
45025839Speter	    ret = 1;
45125839Speter	    goto out2;
45225839Speter	}
45317721Speter
45417721Speter	if (!date1)
45525839Speter	    date1 = xmalloc (MAXDATELEN);
45617721Speter	*date1 = '\0';
457102840Speter	if (RCS_getrevtime (rcsfile, vers_head, date1, 1) == (time_t)-1)
45817721Speter	{
45917721Speter	    if (!really_quiet)
46017721Speter		error (0, 0, "cannot find date in rcs file %s revision %s",
46117721Speter		       rcs, vers_head);
46225839Speter	    ret = 1;
46325839Speter	    goto out2;
46417721Speter	}
46517721Speter    }
46625839Speter    vers_tag = RCS_getversion (rcsfile, rev1, date1, force_tag_match,
46725839Speter			       (int *) NULL);
46825839Speter    if (vers_tag != NULL && RCS_isdead (rcsfile, vers_tag))
46925839Speter    {
47025839Speter        free (vers_tag);
47125839Speter	vers_tag = NULL;
47225839Speter    }
47317721Speter
474128266Speter    if ((vers_tag == NULL && vers_head == NULL) ||
475128266Speter        (vers_tag != NULL && vers_head != NULL &&
476128266Speter	 strcmp (vers_head, vers_tag) == 0))
47725839Speter    {
478128266Speter	/* Nothing known about specified revs or
479128266Speter	 * not changed between releases.
480128266Speter	 */
48125839Speter	ret = 0;
48225839Speter	goto out2;
48325839Speter    }
48417721Speter
485128266Speter    if( patch_short && ( vers_tag == NULL || vers_head == NULL ) )
48625839Speter    {
487128266Speter	/* For adds & removes with a short patch requested, we can print our
488128266Speter	 * error message now and get out.
489128266Speter	 */
49032785Speter	cvs_output ("File ", 0);
49132785Speter	cvs_output (finfo->fullname, 0);
49217721Speter	if (vers_tag == NULL)
49332785Speter	{
494128266Speter	    cvs_output( " is new; ", 0 );
495128266Speter	    cvs_output( rev2 ? rev2 : date2 ? date2 : "current", 0 );
496128266Speter	    cvs_output( " revision ", 0 );
49732785Speter	    cvs_output (vers_head, 0);
49832785Speter	    cvs_output ("\n", 1);
49932785Speter	}
50017721Speter	else
50132785Speter	{
502128266Speter	    cvs_output( " is removed; ", 0 );
503128266Speter	    cvs_output( rev1 ? rev1 : date1, 0 );
504128266Speter	    cvs_output( " revision ", 0 );
505128266Speter	    cvs_output( vers_tag, 0 );
50632785Speter	    cvs_output ("\n", 1);
50732785Speter	}
50825839Speter	ret = 0;
50925839Speter	goto out2;
51017721Speter    }
51134461Speter
51234461Speter    /* Create 3 empty files.  I'm not really sure there is any advantage
51381404Speter     * to doing so now rather than just waiting until later.
51481404Speter     *
51581404Speter     * There is - cvs_temp_file opens the file so that it can guarantee that
51681404Speter     * we have exclusive write access to the file.  Unfortunately we spoil that
51781404Speter     * by closing it and reopening it again.  Of course any better solution
51881404Speter     * requires that the RCS functions accept open file pointers rather than
51981404Speter     * simple file names.
52081404Speter     */
52181404Speter    if ((fp1 = cvs_temp_file (&tmpfile1)) == NULL)
52234461Speter    {
523175282Sobrien	error (0, errno, "cannot create temporary file %s",
524175282Sobrien	       tmpfile1 ? tmpfile1 : "(null)");
52534461Speter	ret = 1;
52634461Speter	goto out;
52734461Speter    }
52834461Speter    else
52934461Speter	if (fclose (fp1) < 0)
53034461Speter	    error (0, errno, "warning: cannot close %s", tmpfile1);
53181404Speter    if ((fp2 = cvs_temp_file (&tmpfile2)) == NULL)
53234461Speter    {
533175282Sobrien	error (0, errno, "cannot create temporary file %s",
534175282Sobrien	       tmpfile2 ? tmpfile2 : "(null)");
53534461Speter	ret = 1;
53634461Speter	goto out;
53734461Speter    }
53834461Speter    else
53934461Speter	if (fclose (fp2) < 0)
54034461Speter	    error (0, errno, "warning: cannot close %s", tmpfile2);
54181404Speter    if ((fp3 = cvs_temp_file (&tmpfile3)) == NULL)
54217721Speter    {
543175282Sobrien	error (0, errno, "cannot create temporary file %s",
544175282Sobrien	       tmpfile3 ? tmpfile3 : "(null)");
54517721Speter	ret = 1;
54617721Speter	goto out;
54717721Speter    }
54834461Speter    else
54934461Speter	if (fclose (fp3) < 0)
55034461Speter	    error (0, errno, "warning: cannot close %s", tmpfile3);
55134461Speter
55217721Speter    if (vers_tag != NULL)
55317721Speter    {
554128266Speter	retcode = RCS_checkout (rcsfile, (char *)NULL, vers_tag,
55525839Speter				rev1, options, tmpfile1,
556128266Speter				(RCSCHECKOUTPROC)NULL, (void *)NULL);
55717721Speter	if (retcode != 0)
55817721Speter	{
55934461Speter	    error (0, 0,
56034461Speter		   "cannot check out revision %s of %s", vers_tag, rcs);
56117721Speter	    ret = 1;
56217721Speter	    goto out;
56317721Speter	}
56417721Speter	memset ((char *) &t, 0, sizeof (t));
56517721Speter	if ((t.actime = t.modtime = RCS_getrevtime (rcsfile, vers_tag,
56617721Speter						    (char *) 0, 0)) != -1)
56726801Speter	    /* I believe this timestamp only affects the dates in our diffs,
56826801Speter	       and therefore should be on the server, not the client.  */
56926801Speter	    (void) utime (tmpfile1, &t);
57017721Speter    }
57117721Speter    else if (toptwo_diffs)
57217721Speter    {
57317721Speter	ret = 1;
57417721Speter	goto out;
57517721Speter    }
57617721Speter    if (vers_head != NULL)
57717721Speter    {
578128266Speter	retcode = RCS_checkout (rcsfile, (char *)NULL, vers_head,
57925839Speter				rev2, options, tmpfile2,
580128266Speter				(RCSCHECKOUTPROC)NULL, (void *)NULL);
58117721Speter	if (retcode != 0)
58217721Speter	{
58334461Speter	    error (0, 0,
58434461Speter		   "cannot check out revision %s of %s", vers_head, rcs);
58517721Speter	    ret = 1;
58617721Speter	    goto out;
58717721Speter	}
58817721Speter	if ((t.actime = t.modtime = RCS_getrevtime (rcsfile, vers_head,
589128266Speter						    (char *)0, 0)) != -1)
59026801Speter	    /* I believe this timestamp only affects the dates in our diffs,
59126801Speter	       and therefore should be on the server, not the client.  */
592128266Speter	    (void)utime (tmpfile2, &t);
59317721Speter    }
59417721Speter
595175282Sobrien    if (unidiff) run_add_arg_p (&dargc, &darg_allocated, &dargv, "-u");
596175282Sobrien    else run_add_arg_p (&dargc, &darg_allocated, &dargv, "-c");
597175282Sobrien    switch (diff_exec (tmpfile1, tmpfile2, NULL, NULL, dargc, dargv,
598175282Sobrien		       tmpfile3))
59917721Speter    {
60017721Speter	case -1:			/* fork/wait failure */
60117721Speter	    error (1, errno, "fork for diff failed on %s", rcs);
60217721Speter	    break;
60317721Speter	case 0:				/* nothing to do */
60417721Speter	    break;
60517721Speter	case 1:
60617721Speter	    /*
60717721Speter	     * The two revisions are really different, so read the first two
60817721Speter	     * lines of the diff output file, and munge them to include more
609128266Speter	     * reasonable file names that "patch" will understand, unless the
610128266Speter	     * user wanted a short patch.  In that case, just output the short
611128266Speter	     * message.
61217721Speter	     */
613128266Speter	    if( patch_short )
614128266Speter	    {
615128266Speter		cvs_output ("File ", 0);
616128266Speter		cvs_output (finfo->fullname, 0);
617128266Speter		cvs_output (" changed from revision ", 0);
618128266Speter		cvs_output (vers_tag, 0);
619128266Speter		cvs_output (" to ", 0);
620128266Speter		cvs_output (vers_head, 0);
621128266Speter		cvs_output ("\n", 1);
622128266Speter		ret = 0;
623128266Speter		goto out;
624128266Speter	    }
62517721Speter
62617721Speter	    /* Output an "Index:" line for patch to use */
62732785Speter	    cvs_output ("Index: ", 0);
62832785Speter	    cvs_output (finfo->fullname, 0);
62932785Speter	    cvs_output ("\n", 1);
63017721Speter
631128266Speter	    /* Now the munging. */
63217721Speter	    fp = open_file (tmpfile3, "r");
63317721Speter	    if (getline (&line1, &line1_chars_allocated, fp) < 0 ||
63417721Speter		getline (&line2, &line2_chars_allocated, fp) < 0)
63517721Speter	    {
63634461Speter		if (feof (fp))
63734461Speter		    error (0, 0, "\
63834461Speterfailed to read diff file header %s for %s: end of file", tmpfile3, rcs);
63934461Speter		else
64034461Speter		    error (0, errno,
64134461Speter			   "failed to read diff file header %s for %s",
64234461Speter			   tmpfile3, rcs);
64317721Speter		ret = 1;
64434461Speter		if (fclose (fp) < 0)
64534461Speter		    error (0, errno, "error closing %s", tmpfile3);
64617721Speter		goto out;
64717721Speter	    }
64817721Speter	    if (!unidiff)
64917721Speter	    {
65017721Speter		if (strncmp (line1, "*** ", 4) != 0 ||
65117721Speter		    strncmp (line2, "--- ", 4) != 0 ||
65217721Speter		    (cp1 = strchr (line1, '\t')) == NULL ||
65317721Speter		    (cp2 = strchr (line2, '\t')) == NULL)
65417721Speter		{
65517721Speter		    error (0, 0, "invalid diff header for %s", rcs);
65617721Speter		    ret = 1;
65734461Speter		    if (fclose (fp) < 0)
65834461Speter			error (0, errno, "error closing %s", tmpfile3);
65917721Speter		    goto out;
66017721Speter		}
66117721Speter	    }
66217721Speter	    else
66317721Speter	    {
66417721Speter		if (strncmp (line1, "--- ", 4) != 0 ||
66517721Speter		    strncmp (line2, "+++ ", 4) != 0 ||
66617721Speter		    (cp1 = strchr (line1, '\t')) == NULL ||
66717721Speter		    (cp2 = strchr  (line2, '\t')) == NULL)
66817721Speter		{
66917721Speter		    error (0, 0, "invalid unidiff header for %s", rcs);
67017721Speter		    ret = 1;
67134461Speter		    if (fclose (fp) < 0)
67234461Speter			error (0, errno, "error closing %s", tmpfile3);
67317721Speter		    goto out;
67417721Speter		}
67517721Speter	    }
67681404Speter	    assert (current_parsed_root != NULL);
67781404Speter	    assert (current_parsed_root->directory != NULL);
67825839Speter	    {
679128266Speter		strippath = xmalloc (strlen (current_parsed_root->directory)
680128266Speter                                     + 2);
681128266Speter		(void)sprintf (strippath, "%s/",
682128266Speter                               current_parsed_root->directory);
68325839Speter	    }
68481404Speter	    /*else
68581404Speter		strippath = xstrdup (REPOS_STRIP); */
68617721Speter	    if (strncmp (rcs, strippath, strlen (strippath)) == 0)
68717721Speter		rcs += strlen (strippath);
68825839Speter	    free (strippath);
68917721Speter	    if (vers_tag != NULL)
69017721Speter	    {
69125839Speter		file1 = xmalloc (strlen (finfo->fullname)
69225839Speter				 + strlen (vers_tag)
69325839Speter				 + 10);
694128266Speter		(void)sprintf (file1, "%s:%s", finfo->fullname, vers_tag);
69517721Speter	    }
69617721Speter	    else
69717721Speter	    {
69825839Speter		file1 = xstrdup (DEVNULL);
69917721Speter	    }
70025839Speter	    file2 = xmalloc (strlen (finfo->fullname)
70125839Speter			     + (vers_head != NULL ? strlen (vers_head) : 10)
70225839Speter			     + 10);
703128266Speter	    (void)sprintf (file2, "%s:%s", finfo->fullname,
704128266Speter			   vers_head ? vers_head : "removed");
70517721Speter
70632785Speter	    /* Note that the string "diff" is specified by POSIX (for -c)
70732785Speter	       and is part of the diff output format, not the name of a
70832785Speter	       program.  */
70917721Speter	    if (unidiff)
71017721Speter	    {
71132785Speter		cvs_output ("diff -u ", 0);
71232785Speter		cvs_output (file1, 0);
71332785Speter		cvs_output (" ", 1);
71432785Speter		cvs_output (file2, 0);
71532785Speter		cvs_output ("\n", 1);
71632785Speter
71732785Speter		cvs_output ("--- ", 0);
71832785Speter		cvs_output (file1, 0);
71932785Speter		cvs_output (cp1, 0);
72032785Speter		cvs_output ("+++ ", 0);
72117721Speter	    }
72217721Speter	    else
72317721Speter	    {
72432785Speter		cvs_output ("diff -c ", 0);
72532785Speter		cvs_output (file1, 0);
72632785Speter		cvs_output (" ", 1);
72732785Speter		cvs_output (file2, 0);
72832785Speter		cvs_output ("\n", 1);
72932785Speter
73032785Speter		cvs_output ("*** ", 0);
73132785Speter		cvs_output (file1, 0);
73232785Speter		cvs_output (cp1, 0);
73332785Speter		cvs_output ("--- ", 0);
73417721Speter	    }
73517721Speter
73632785Speter	    cvs_output (finfo->fullname, 0);
73732785Speter	    cvs_output (cp2, 0);
73832785Speter
73917721Speter	    /* spew the rest of the diff out */
74032785Speter	    while ((line_length
74132785Speter		    = getline (&line1, &line1_chars_allocated, fp))
74232785Speter		   >= 0)
74332785Speter		cvs_output (line1, 0);
74432785Speter	    if (line_length < 0 && !feof (fp))
74532785Speter		error (0, errno, "cannot read %s", tmpfile3);
74632785Speter
74732785Speter	    if (fclose (fp) < 0)
74832785Speter		error (0, errno, "cannot close %s", tmpfile3);
74925839Speter	    free (file1);
75025839Speter	    free (file2);
75117721Speter	    break;
75217721Speter	default:
75317721Speter	    error (0, 0, "diff failed for %s", finfo->fullname);
75417721Speter    }
75517721Speter  out:
75617721Speter    if (line1)
75717721Speter        free (line1);
75817721Speter    if (line2)
75917721Speter        free (line2);
760175282Sobrien    if (tmpfile1 != NULL)
761175282Sobrien    {
762175282Sobrien	if (CVS_UNLINK (tmpfile1) < 0)
763175282Sobrien	    error (0, errno, "cannot unlink %s", tmpfile1);
764175282Sobrien	free (tmpfile1);
765175282Sobrien	tmpfile1 = NULL;
766175282Sobrien    }
767175282Sobrien    if (tmpfile2 != NULL)
768175282Sobrien    {
769175282Sobrien	if (CVS_UNLINK (tmpfile2) < 0)
770175282Sobrien	    error (0, errno, "cannot unlink %s", tmpfile2);
771175282Sobrien	free (tmpfile2);
772175282Sobrien	tmpfile2 = NULL;
773175282Sobrien    }
774175282Sobrien    if (tmpfile3 != NULL)
775175282Sobrien    {
776175282Sobrien	if (CVS_UNLINK (tmpfile3) < 0)
777175282Sobrien	    error (0, errno, "cannot unlink %s", tmpfile3);
778175282Sobrien	free (tmpfile3);
779175282Sobrien	tmpfile3 = NULL;
780175282Sobrien    }
78125839Speter
782175282Sobrien    if (dargc)
783175282Sobrien    {
784175282Sobrien	run_arg_free_p (dargc, dargv);
785175282Sobrien	free (dargv);
786175282Sobrien    }
787175282Sobrien
78825839Speter out2:
78966525Speter    if (vers_tag != NULL)
79066525Speter	free (vers_tag);
79166525Speter    if (vers_head != NULL)
79266525Speter	free (vers_head);
793145406Ssimon    if (rcs_orig)
794145406Ssimon	free (rcs_orig);
795128266Speter    return ret;
79617721Speter}
79717721Speter
798128266Speter
799128266Speter
80017721Speter/*
80117721Speter * Print a warm fuzzy message
80217721Speter */
80317721Speter/* ARGSUSED */
80417721Speterstatic Dtype
80525839Speterpatch_dirproc (callerdat, dir, repos, update_dir, entries)
80625839Speter    void *callerdat;
807128266Speter    const char *dir;
808128266Speter    const char *repos;
809128266Speter    const char *update_dir;
81025839Speter    List *entries;
81117721Speter{
81217721Speter    if (!quiet)
81317721Speter	error (0, 0, "Diffing %s", update_dir);
81417721Speter    return (R_PROCESS);
81517721Speter}
81617721Speter
81717721Speter/*
81817721Speter * Clean up temporary files
81917721Speter */
82017721Speterstatic RETSIGTYPE
82117721Speterpatch_cleanup ()
82217721Speter{
82354427Speter    /* Note that the checks for existence_error are because we are
82454427Speter       called from a signal handler, without SIG_begincrsect, so
82554427Speter       we don't know whether the files got created.  */
82654427Speter
82725839Speter    if (tmpfile1 != NULL)
82825839Speter    {
82954427Speter	if (unlink_file (tmpfile1) < 0
83054427Speter	    && !existence_error (errno))
83154427Speter	    error (0, errno, "cannot remove %s", tmpfile1);
83225839Speter	free (tmpfile1);
83325839Speter    }
83425839Speter    if (tmpfile2 != NULL)
83525839Speter    {
83654427Speter	if (unlink_file (tmpfile2) < 0
83754427Speter	    && !existence_error (errno))
83854427Speter	    error (0, errno, "cannot remove %s", tmpfile2);
83925839Speter	free (tmpfile2);
84025839Speter    }
84125839Speter    if (tmpfile3 != NULL)
84225839Speter    {
84354427Speter	if (unlink_file (tmpfile3) < 0
84454427Speter	    && !existence_error (errno))
84554427Speter	    error (0, errno, "cannot remove %s", tmpfile3);
84625839Speter	free (tmpfile3);
84725839Speter    }
84825839Speter    tmpfile1 = tmpfile2 = tmpfile3 = NULL;
84917721Speter}
850