diff options
Diffstat (limited to 'contrib/cvs/src/patch.c')
-rw-r--r-- | contrib/cvs/src/patch.c | 849 |
1 files changed, 849 insertions, 0 deletions
diff --git a/contrib/cvs/src/patch.c b/contrib/cvs/src/patch.c new file mode 100644 index 0000000..65f5051 --- /dev/null +++ b/contrib/cvs/src/patch.c @@ -0,0 +1,849 @@ +/* + * Copyright (C) 1986-2005 The Free Software Foundation, Inc. + * + * Portions Copyright (C) 1998-2005 Derek Price, Ximbiot <http://ximbiot.com>, + * and others. + * + * Portions Copyright (C) 1992, Brian Berliner and Jeff Polk + * Portions 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 source distribution. + * + * Patch + * + * Create a Larry Wall format "patch" file between a previous release and the + * current head of a module, or between two releases. Can specify the + * release as either a date or a revision number. + * + * $FreeBSD$ + */ + +#include <assert.h> +#include "cvs.h" +#include "getline.h" + +static RETSIGTYPE patch_cleanup PROTO((void)); +static Dtype patch_dirproc PROTO ((void *callerdat, const char *dir, + const char *repos, const char *update_dir, + List *entries)); +static int patch_fileproc PROTO ((void *callerdat, struct file_info *finfo)); +static int patch_proc PROTO((int argc, char **argv, char *xwhere, + char *mwhere, char *mfile, int shorten, + int local_specified, char *mname, char *msg)); + +static int force_tag_match = 1; +static int patch_short = 0; +static int toptwo_diffs = 0; +static char *options = NULL; +static char *rev1 = NULL; +static int rev1_validated = 0; +static char *rev2 = NULL; +static int rev2_validated = 0; +static char *date1 = NULL; +static char *date2 = NULL; +static char *tmpfile1 = NULL; +static char *tmpfile2 = NULL; +static char *tmpfile3 = NULL; +static int unidiff = 0; + +static const char *const patch_usage[] = +{ + "Usage: %s %s [-flR] [-c|-u] [-s|-t] [-V %%d] [-k kopt]\n", + " -r rev|-D date [-r rev2 | -D date2] modules...\n", + "\t-f\tForce a head revision match if tag/date not found.\n", + "\t-l\tLocal directory only, not recursive\n", + "\t-R\tProcess directories recursively.\n", + "\t-c\tContext diffs (default)\n", + "\t-u\tUnidiff format.\n", + "\t-s\tShort patch - one liner per file.\n", + "\t-t\tTop two diffs - last change made to the file.\n", + "\t-V vers\tUse RCS Version \"vers\" for keyword expansion.\n", + "\t-k kopt\tSpecify keyword expansion mode.\n", + "\t-D date\tDate.\n", + "\t-r rev\tRevision - symbolic or numeric.\n", + "(Specify the --help global option for a list of other help options)\n", + NULL +}; + + + +int +patch (argc, argv) + int argc; + char **argv; +{ + register int i; + int local = 0; + int c; + int err = 0; + DBM *db; + + if (argc == -1) + usage (patch_usage); + + optind = 0; + while ((c = getopt (argc, argv, "+V:k:cuftsQqlRD:r:")) != -1) + { + switch (c) + { + case 'Q': + case 'q': + /* The CVS 1.5 client sends these options (in addition to + Global_option requests), so we must ignore them. */ + if (!server_active) + error (1, 0, + "-q or -Q must be specified before \"%s\"", + cvs_cmd_name); + break; + case 'f': + force_tag_match = 0; + break; + case 'l': + local = 1; + break; + case 'R': + local = 0; + break; + case 't': + toptwo_diffs = 1; + break; + case 's': + patch_short = 1; + break; + case 'D': + if (rev2 != NULL || date2 != NULL) + error (1, 0, + "no more than two revisions/dates can be specified"); + if (rev1 != NULL || date1 != NULL) + date2 = Make_Date (optarg); + else + date1 = Make_Date (optarg); + break; + case 'r': + if (rev2 != NULL || date2 != NULL) + error (1, 0, + "no more than two revisions/dates can be specified"); + if (rev1 != NULL || date1 != NULL) + rev2 = optarg; + else + rev1 = optarg; + break; + case 'k': + if (options) + free (options); + options = RCS_check_kflag (optarg); + break; + case 'V': + /* This option is pretty seriously broken: + 1. It is not clear what it does (does it change keyword + expansion behavior? If so, how? Or does it have + something to do with what version of RCS we are using? + Or the format we write RCS files in?). + 2. Because both it and -k use the options variable, + specifying both -V and -k doesn't work. + 3. At least as of CVS 1.9, it doesn't work (failed + assertion in RCS_checkout where it asserts that options + starts with -k). Few people seem to be complaining. + In the future (perhaps the near future), I have in mind + removing it entirely, and updating NEWS and cvs.texinfo, + but in case it is a good idea to give people more time + to complain if they would miss it, I'll just add this + quick and dirty error message for now. */ + error (1, 0, + "the -V option is obsolete and should not be used"); +#if 0 + if (atoi (optarg) <= 0) + error (1, 0, "must specify a version number to -V"); + if (options) + free (options); + options = xmalloc (strlen (optarg) + 1 + 2); /* for the -V */ + (void) sprintf (options, "-V%s", optarg); +#endif + break; + case 'u': + unidiff = 1; /* Unidiff */ + break; + case 'c': /* Context diff */ + unidiff = 0; + break; + case '?': + default: + usage (patch_usage); + break; + } + } + argc -= optind; + argv += optind; + + /* Sanity checks */ + if (argc < 1) + usage (patch_usage); + + if (toptwo_diffs && patch_short) + error (1, 0, "-t and -s options are mutually exclusive"); + if (toptwo_diffs && (date1 != NULL || date2 != NULL || + rev1 != NULL || rev2 != NULL)) + error (1, 0, "must not specify revisions/dates with -t option!"); + + if (!toptwo_diffs && (date1 == NULL && date2 == NULL && + rev1 == NULL && rev2 == NULL)) + error (1, 0, "must specify at least one revision/date!"); + if (date1 != NULL && date2 != NULL) + if (RCS_datecmp (date1, date2) >= 0) + error (1, 0, "second date must come after first date!"); + + /* if options is NULL, make it a NULL string */ + if (options == NULL) + options = xstrdup (""); + +#ifdef CLIENT_SUPPORT + if (current_parsed_root->isremote) + { + /* We're the client side. Fire up the remote server. */ + start_server (); + + ign_setup (); + + if (local) + send_arg("-l"); + if (!force_tag_match) + send_arg("-f"); + if (toptwo_diffs) + send_arg("-t"); + if (patch_short) + send_arg("-s"); + if (unidiff) + send_arg("-u"); + + if (rev1) + option_with_arg ("-r", rev1); + if (date1) + client_senddate (date1); + if (rev2) + option_with_arg ("-r", rev2); + if (date2) + client_senddate (date2); + if (options[0] != '\0') + send_arg (options); + + { + int i; + for (i = 0; i < argc; ++i) + send_arg (argv[i]); + } + + send_to_server ("rdiff\012", 0); + return get_responses_and_close (); + } +#endif + + /* clean up if we get a signal */ +#ifdef SIGABRT + (void)SIG_register (SIGABRT, patch_cleanup); +#endif +#ifdef SIGHUP + (void)SIG_register (SIGHUP, patch_cleanup); +#endif +#ifdef SIGINT + (void)SIG_register (SIGINT, patch_cleanup); +#endif +#ifdef SIGQUIT + (void)SIG_register (SIGQUIT, patch_cleanup); +#endif +#ifdef SIGPIPE + (void)SIG_register (SIGPIPE, patch_cleanup); +#endif +#ifdef SIGTERM + (void)SIG_register (SIGTERM, patch_cleanup); +#endif + + db = open_module (); + for (i = 0; i < argc; i++) + err += do_module (db, argv[i], PATCH, "Patching", patch_proc, + (char *)NULL, 0, local, 0, 0, (char *)NULL); + close_module (db); + free (options); + patch_cleanup (); + return err; +} + + + +/* + * callback proc for doing the real work of patching + */ +/* ARGSUSED */ +static int +patch_proc (argc, argv, xwhere, mwhere, mfile, shorten, local_specified, + mname, msg) + int argc; + char **argv; + char *xwhere; + char *mwhere; + char *mfile; + int shorten; + int local_specified; + char *mname; + char *msg; +{ + char *myargv[2]; + int err = 0; + int which; + char *repository; + char *where; + + repository = xmalloc (strlen (current_parsed_root->directory) + + strlen (argv[0]) + + (mfile == NULL ? 0 : strlen (mfile) + 1) + 2); + (void)sprintf (repository, "%s/%s", + current_parsed_root->directory, argv[0]); + where = xmalloc (strlen (argv[0]) + (mfile == NULL ? 0 : strlen (mfile) + 1) + + 1); + (void)strcpy (where, argv[0]); + + /* if mfile isn't null, we need to set up to do only part of the module */ + if (mfile != NULL) + { + char *cp; + char *path; + + /* if the portion of the module is a path, put the dir part on repos */ + if ((cp = strrchr (mfile, '/')) != NULL) + { + *cp = '\0'; + (void)strcat (repository, "/"); + (void)strcat (repository, mfile); + (void)strcat (where, "/"); + (void)strcat (where, mfile); + mfile = cp + 1; + } + + /* take care of the rest */ + path = xmalloc (strlen (repository) + strlen (mfile) + 2); + (void)sprintf (path, "%s/%s", repository, mfile); + if (isdir (path)) + { + /* directory means repository gets the dir tacked on */ + (void)strcpy (repository, path); + (void)strcat (where, "/"); + (void)strcat (where, mfile); + } + else + { + myargv[0] = argv[0]; + myargv[1] = mfile; + argc = 2; + argv = myargv; + } + free (path); + } + + /* cd to the starting repository */ + if ( CVS_CHDIR (repository) < 0) + { + error (0, errno, "cannot chdir to %s", repository); + free (repository); + free (where); + return 1; + } + + if (force_tag_match) + which = W_REPOS | W_ATTIC; + else + which = W_REPOS; + + if (rev1 != NULL && !rev1_validated) + { + tag_check_valid (rev1, argc - 1, argv + 1, local_specified, 0, + repository); + rev1_validated = 1; + } + if (rev2 != NULL && !rev2_validated) + { + tag_check_valid (rev2, argc - 1, argv + 1, local_specified, 0, + repository); + rev2_validated = 1; + } + + /* start the recursion processor */ + err = start_recursion (patch_fileproc, (FILESDONEPROC)NULL, patch_dirproc, + (DIRLEAVEPROC)NULL, NULL, + argc - 1, argv + 1, local_specified, + which, 0, CVS_LOCK_READ, where, 1, repository); + free (repository); + free (where); + + return err; +} + + + +/* + * Called to examine a particular RCS file, as appropriate with the options + * that were set above. + */ +/* ARGSUSED */ +static int +patch_fileproc (callerdat, finfo) + void *callerdat; + struct file_info *finfo; +{ + struct utimbuf t; + char *vers_tag, *vers_head; + char *rcs = NULL; + char *rcs_orig = NULL; + RCSNode *rcsfile; + FILE *fp1, *fp2, *fp3; + int ret = 0; + int isattic = 0; + int retcode = 0; + char *file1; + char *file2; + char *strippath; + char *line1, *line2; + size_t line1_chars_allocated; + size_t line2_chars_allocated; + char *cp1, *cp2; + FILE *fp; + int line_length; + int dargc = 0; + size_t darg_allocated = 0; + char **dargv = NULL; + + line1 = NULL; + line1_chars_allocated = 0; + line2 = NULL; + line2_chars_allocated = 0; + vers_tag = vers_head = NULL; + + /* find the parsed rcs file */ + if ((rcsfile = finfo->rcs) == NULL) + { + ret = 1; + goto out2; + } + if ((rcsfile->flags & VALID) && (rcsfile->flags & INATTIC)) + isattic = 1; + + rcs_orig = rcs = xmalloc (strlen (finfo->file) + sizeof (RCSEXT) + 5); + (void) sprintf (rcs, "%s%s", finfo->file, RCSEXT); + + /* if vers_head is NULL, may have been removed from the release */ + if (isattic && rev2 == NULL && date2 == NULL) + vers_head = NULL; + else + { + vers_head = RCS_getversion (rcsfile, rev2, date2, force_tag_match, + (int *) NULL); + if (vers_head != NULL && RCS_isdead (rcsfile, vers_head)) + { + free (vers_head); + vers_head = NULL; + } + } + + if (toptwo_diffs) + { + if (vers_head == NULL) + { + ret = 1; + goto out2; + } + + if (!date1) + date1 = xmalloc (MAXDATELEN); + *date1 = '\0'; + if (RCS_getrevtime (rcsfile, vers_head, date1, 1) == (time_t)-1) + { + if (!really_quiet) + error (0, 0, "cannot find date in rcs file %s revision %s", + rcs, vers_head); + ret = 1; + goto out2; + } + } + vers_tag = RCS_getversion (rcsfile, rev1, date1, force_tag_match, + (int *) NULL); + if (vers_tag != NULL && RCS_isdead (rcsfile, vers_tag)) + { + free (vers_tag); + vers_tag = NULL; + } + + if ((vers_tag == NULL && vers_head == NULL) || + (vers_tag != NULL && vers_head != NULL && + strcmp (vers_head, vers_tag) == 0)) + { + /* Nothing known about specified revs or + * not changed between releases. + */ + ret = 0; + goto out2; + } + + if( patch_short && ( vers_tag == NULL || vers_head == NULL ) ) + { + /* For adds & removes with a short patch requested, we can print our + * error message now and get out. + */ + cvs_output ("File ", 0); + cvs_output (finfo->fullname, 0); + if (vers_tag == NULL) + { + cvs_output( " is new; ", 0 ); + cvs_output( rev2 ? rev2 : date2 ? date2 : "current", 0 ); + cvs_output( " revision ", 0 ); + cvs_output (vers_head, 0); + cvs_output ("\n", 1); + } + else + { + cvs_output( " is removed; ", 0 ); + cvs_output( rev1 ? rev1 : date1, 0 ); + cvs_output( " revision ", 0 ); + cvs_output( vers_tag, 0 ); + cvs_output ("\n", 1); + } + ret = 0; + goto out2; + } + + /* Create 3 empty files. I'm not really sure there is any advantage + * to doing so now rather than just waiting until later. + * + * There is - cvs_temp_file opens the file so that it can guarantee that + * we have exclusive write access to the file. Unfortunately we spoil that + * by closing it and reopening it again. Of course any better solution + * requires that the RCS functions accept open file pointers rather than + * simple file names. + */ + if ((fp1 = cvs_temp_file (&tmpfile1)) == NULL) + { + error (0, errno, "cannot create temporary file %s", + tmpfile1 ? tmpfile1 : "(null)"); + ret = 1; + goto out; + } + else + if (fclose (fp1) < 0) + error (0, errno, "warning: cannot close %s", tmpfile1); + if ((fp2 = cvs_temp_file (&tmpfile2)) == NULL) + { + error (0, errno, "cannot create temporary file %s", + tmpfile2 ? tmpfile2 : "(null)"); + ret = 1; + goto out; + } + else + if (fclose (fp2) < 0) + error (0, errno, "warning: cannot close %s", tmpfile2); + if ((fp3 = cvs_temp_file (&tmpfile3)) == NULL) + { + error (0, errno, "cannot create temporary file %s", + tmpfile3 ? tmpfile3 : "(null)"); + ret = 1; + goto out; + } + else + if (fclose (fp3) < 0) + error (0, errno, "warning: cannot close %s", tmpfile3); + + if (vers_tag != NULL) + { + retcode = RCS_checkout (rcsfile, (char *)NULL, vers_tag, + rev1, options, tmpfile1, + (RCSCHECKOUTPROC)NULL, (void *)NULL); + if (retcode != 0) + { + error (0, 0, + "cannot check out revision %s of %s", vers_tag, rcs); + ret = 1; + goto out; + } + memset ((char *) &t, 0, sizeof (t)); + if ((t.actime = t.modtime = RCS_getrevtime (rcsfile, vers_tag, + (char *) 0, 0)) != -1) + /* I believe this timestamp only affects the dates in our diffs, + and therefore should be on the server, not the client. */ + (void) utime (tmpfile1, &t); + } + else if (toptwo_diffs) + { + ret = 1; + goto out; + } + if (vers_head != NULL) + { + retcode = RCS_checkout (rcsfile, (char *)NULL, vers_head, + rev2, options, tmpfile2, + (RCSCHECKOUTPROC)NULL, (void *)NULL); + if (retcode != 0) + { + error (0, 0, + "cannot check out revision %s of %s", vers_head, rcs); + ret = 1; + goto out; + } + if ((t.actime = t.modtime = RCS_getrevtime (rcsfile, vers_head, + (char *)0, 0)) != -1) + /* I believe this timestamp only affects the dates in our diffs, + and therefore should be on the server, not the client. */ + (void)utime (tmpfile2, &t); + } + + if (unidiff) run_add_arg_p (&dargc, &darg_allocated, &dargv, "-u"); + else run_add_arg_p (&dargc, &darg_allocated, &dargv, "-c"); + switch (diff_exec (tmpfile1, tmpfile2, NULL, NULL, dargc, dargv, + tmpfile3)) + { + case -1: /* fork/wait failure */ + error (1, errno, "fork for diff failed on %s", rcs); + break; + case 0: /* nothing to do */ + break; + case 1: + /* + * The two revisions are really different, so read the first two + * lines of the diff output file, and munge them to include more + * reasonable file names that "patch" will understand, unless the + * user wanted a short patch. In that case, just output the short + * message. + */ + if( patch_short ) + { + cvs_output ("File ", 0); + cvs_output (finfo->fullname, 0); + cvs_output (" changed from revision ", 0); + cvs_output (vers_tag, 0); + cvs_output (" to ", 0); + cvs_output (vers_head, 0); + cvs_output ("\n", 1); + ret = 0; + goto out; + } + + /* Output an "Index:" line for patch to use */ + cvs_output ("Index: ", 0); + cvs_output (finfo->fullname, 0); + cvs_output ("\n", 1); + + /* Now the munging. */ + fp = open_file (tmpfile3, "r"); + if (getline (&line1, &line1_chars_allocated, fp) < 0 || + getline (&line2, &line2_chars_allocated, fp) < 0) + { + if (feof (fp)) + error (0, 0, "\ +failed to read diff file header %s for %s: end of file", tmpfile3, rcs); + else + error (0, errno, + "failed to read diff file header %s for %s", + tmpfile3, rcs); + ret = 1; + if (fclose (fp) < 0) + error (0, errno, "error closing %s", tmpfile3); + goto out; + } + if (!unidiff) + { + if (strncmp (line1, "*** ", 4) != 0 || + strncmp (line2, "--- ", 4) != 0 || + (cp1 = strchr (line1, '\t')) == NULL || + (cp2 = strchr (line2, '\t')) == NULL) + { + error (0, 0, "invalid diff header for %s", rcs); + ret = 1; + if (fclose (fp) < 0) + error (0, errno, "error closing %s", tmpfile3); + goto out; + } + } + else + { + if (strncmp (line1, "--- ", 4) != 0 || + strncmp (line2, "+++ ", 4) != 0 || + (cp1 = strchr (line1, '\t')) == NULL || + (cp2 = strchr (line2, '\t')) == NULL) + { + error (0, 0, "invalid unidiff header for %s", rcs); + ret = 1; + if (fclose (fp) < 0) + error (0, errno, "error closing %s", tmpfile3); + goto out; + } + } + assert (current_parsed_root != NULL); + assert (current_parsed_root->directory != NULL); + { + strippath = xmalloc (strlen (current_parsed_root->directory) + + 2); + (void)sprintf (strippath, "%s/", + current_parsed_root->directory); + } + /*else + strippath = xstrdup (REPOS_STRIP); */ + if (strncmp (rcs, strippath, strlen (strippath)) == 0) + rcs += strlen (strippath); + free (strippath); + if (vers_tag != NULL) + { + file1 = xmalloc (strlen (finfo->fullname) + + strlen (vers_tag) + + 10); + (void)sprintf (file1, "%s:%s", finfo->fullname, vers_tag); + } + else + { + file1 = xstrdup (DEVNULL); + } + file2 = xmalloc (strlen (finfo->fullname) + + (vers_head != NULL ? strlen (vers_head) : 10) + + 10); + (void)sprintf (file2, "%s:%s", finfo->fullname, + vers_head ? vers_head : "removed"); + + /* Note that the string "diff" is specified by POSIX (for -c) + and is part of the diff output format, not the name of a + program. */ + if (unidiff) + { + cvs_output ("diff -u ", 0); + cvs_output (file1, 0); + cvs_output (" ", 1); + cvs_output (file2, 0); + cvs_output ("\n", 1); + + cvs_output ("--- ", 0); + cvs_output (file1, 0); + cvs_output (cp1, 0); + cvs_output ("+++ ", 0); + } + else + { + cvs_output ("diff -c ", 0); + cvs_output (file1, 0); + cvs_output (" ", 1); + cvs_output (file2, 0); + cvs_output ("\n", 1); + + cvs_output ("*** ", 0); + cvs_output (file1, 0); + cvs_output (cp1, 0); + cvs_output ("--- ", 0); + } + + cvs_output (finfo->fullname, 0); + cvs_output (cp2, 0); + + /* spew the rest of the diff out */ + while ((line_length + = getline (&line1, &line1_chars_allocated, fp)) + >= 0) + cvs_output (line1, 0); + if (line_length < 0 && !feof (fp)) + error (0, errno, "cannot read %s", tmpfile3); + + if (fclose (fp) < 0) + error (0, errno, "cannot close %s", tmpfile3); + free (file1); + free (file2); + break; + default: + error (0, 0, "diff failed for %s", finfo->fullname); + } + out: + if (line1) + free (line1); + if (line2) + free (line2); + if (tmpfile1 != NULL) + { + if (CVS_UNLINK (tmpfile1) < 0) + error (0, errno, "cannot unlink %s", tmpfile1); + free (tmpfile1); + tmpfile1 = NULL; + } + if (tmpfile2 != NULL) + { + if (CVS_UNLINK (tmpfile2) < 0) + error (0, errno, "cannot unlink %s", tmpfile2); + free (tmpfile2); + tmpfile2 = NULL; + } + if (tmpfile3 != NULL) + { + if (CVS_UNLINK (tmpfile3) < 0) + error (0, errno, "cannot unlink %s", tmpfile3); + free (tmpfile3); + tmpfile3 = NULL; + } + + if (dargc) + { + run_arg_free_p (dargc, dargv); + free (dargv); + } + + out2: + if (vers_tag != NULL) + free (vers_tag); + if (vers_head != NULL) + free (vers_head); + if (rcs_orig) + free (rcs_orig); + return ret; +} + + + +/* + * Print a warm fuzzy message + */ +/* ARGSUSED */ +static Dtype +patch_dirproc (callerdat, dir, repos, update_dir, entries) + void *callerdat; + const char *dir; + const char *repos; + const char *update_dir; + List *entries; +{ + if (!quiet) + error (0, 0, "Diffing %s", update_dir); + return (R_PROCESS); +} + +/* + * Clean up temporary files + */ +static RETSIGTYPE +patch_cleanup () +{ + /* Note that the checks for existence_error are because we are + called from a signal handler, without SIG_begincrsect, so + we don't know whether the files got created. */ + + if (tmpfile1 != NULL) + { + if (unlink_file (tmpfile1) < 0 + && !existence_error (errno)) + error (0, errno, "cannot remove %s", tmpfile1); + free (tmpfile1); + } + if (tmpfile2 != NULL) + { + if (unlink_file (tmpfile2) < 0 + && !existence_error (errno)) + error (0, errno, "cannot remove %s", tmpfile2); + free (tmpfile2); + } + if (tmpfile3 != NULL) + { + if (unlink_file (tmpfile3) < 0 + && !existence_error (errno)) + error (0, errno, "cannot remove %s", tmpfile3); + free (tmpfile3); + } + tmpfile1 = tmpfile2 = tmpfile3 = NULL; +} |