diff options
Diffstat (limited to 'contrib/cvs/src/patch.c')
-rw-r--r-- | contrib/cvs/src/patch.c | 604 |
1 files changed, 604 insertions, 0 deletions
diff --git a/contrib/cvs/src/patch.c b/contrib/cvs/src/patch.c new file mode 100644 index 0000000..39b4e64 --- /dev/null +++ b/contrib/cvs/src/patch.c @@ -0,0 +1,604 @@ +/* + * 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. + * + * 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. + */ + +#include "cvs.h" +#include "getline.h" + +static RETSIGTYPE patch_cleanup PROTO((void)); +static Dtype patch_dirproc PROTO((char *dir, char *repos, char *update_dir)); +static int patch_fileproc PROTO((struct file_info *finfo)); +static int patch_proc PROTO((int *pargc, 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 int local = 0; +static char *options = NULL; +static char *rev1 = NULL; +static int rev1_validated = 1; +static char *rev2 = NULL; +static int rev2_validated = 1; +static char *date1 = NULL; +static char *date2 = NULL; +static char tmpfile1[L_tmpnam+1], tmpfile2[L_tmpnam+1], tmpfile3[L_tmpnam+1]; +static int unidiff = 0; + +static const char *const patch_usage[] = +{ + "Usage: %s %s [-fl] [-c|-u] [-s|-t] [-V %%d]\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-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-D date\tDate.\n", + "\t-r rev\tRevision - symbolic or numeric.\n", + "\t-V vers\tUse RCS Version \"vers\" for keyword expansion.\n", + NULL +}; + +int +patch (argc, argv) + int argc; + char **argv; +{ + register int i; + int c; + int err = 0; + DBM *db; + + if (argc == -1) + usage (patch_usage); + + optind = 1; + while ((c = getopt (argc, argv, "V:k:cuftsQqlRD:r:")) != -1) + { + switch (c) + { + case 'Q': + case 'q': +#ifdef SERVER_SUPPORT + /* The CVS 1.5 client sends these options (in addition to + Global_option requests), so we must ignore them. */ + if (!server_active) +#endif + error (1, 0, + "-q or -Q must be specified before \"%s\"", + command_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': + 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); + 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 (client_active) + { + /* 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 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, 0, 0, (char *) NULL); + close_module (db); + free (options); + patch_cleanup (); + return (err); +} + +/* + * callback proc for doing the real work of patching + */ +/* ARGSUSED */ +static char where[PATH_MAX]; +static int +patch_proc (pargc, argv, xwhere, mwhere, mfile, shorten, local_specified, + mname, msg) + int *pargc; + char **argv; + char *xwhere; + char *mwhere; + char *mfile; + int shorten; + int local_specified; + char *mname; + char *msg; +{ + int err = 0; + int which; + char repository[PATH_MAX]; + + (void) sprintf (repository, "%s/%s", CVSroot, argv[0]); + (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[PATH_MAX]; + + /* 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 */ + (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 + { + int i; + + /* a file means muck argv */ + for (i = 1; i < *pargc; i++) + free (argv[i]); + argv[1] = xstrdup (mfile); + (*pargc) = 2; + } + } + + /* cd to the starting repository */ + if (chdir (repository) < 0) + { + error (0, errno, "cannot chdir to %s", repository); + return (1); + } + + if (force_tag_match) + which = W_REPOS | W_ATTIC; + else + which = W_REPOS; + + if (rev1 != NULL && !rev1_validated) + { + tag_check_valid (rev1, *pargc - 1, argv + 1, local, 0, NULL); + rev1_validated = 1; + } + if (rev2 != NULL && !rev2_validated) + { + tag_check_valid (rev2, *pargc - 1, argv + 1, local, 0, NULL); + rev2_validated = 1; + } + + /* start the recursion processor */ + err = start_recursion (patch_fileproc, (FILESDONEPROC) NULL, patch_dirproc, + (DIRLEAVEPROC) NULL, *pargc - 1, argv + 1, local, + which, 0, 1, where, 1, 1); + + return (err); +} + +/* + * Called to examine a particular RCS file, as appropriate with the options + * that were set above. + */ +/* ARGSUSED */ +static int +patch_fileproc (finfo) + struct file_info *finfo; +{ + struct utimbuf t; + char *vers_tag, *vers_head; + char rcsspace[1][PATH_MAX]; + char *rcs = rcsspace[0]; + RCSNode *rcsfile; + FILE *fp1, *fp2, *fp3; + int ret = 0; + int isattic = 0; + int retcode = 0; + char file1[PATH_MAX], file2[PATH_MAX], strippath[PATH_MAX]; + char *line1, *line2; + size_t line1_chars_allocated; + size_t line2_chars_allocated; + char *cp1, *cp2; + FILE *fp; + + /* find the parsed rcs file */ + if ((rcsfile = finfo->rcs) == NULL) + return (1); + if ((rcsfile->flags & VALID) && (rcsfile->flags & INATTIC)) + isattic = 1; + + (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, 0); + + if (toptwo_diffs) + { + if (vers_head == NULL) + return (1); + + if (!date1) + date1 = xmalloc (50); /* plenty big :-) */ + *date1 = '\0'; + if (RCS_getrevtime (rcsfile, vers_head, date1, 1) == -1) + { + if (!really_quiet) + error (0, 0, "cannot find date in rcs file %s revision %s", + rcs, vers_head); + return (1); + } + } + vers_tag = RCS_getversion (rcsfile, rev1, date1, force_tag_match, 0); + + if (vers_tag == NULL && (vers_head == NULL || isattic)) + return (0); /* nothing known about specified revs */ + + if (vers_tag && vers_head && strcmp (vers_head, vers_tag) == 0) + return (0); /* not changed between releases */ + + if (patch_short) + { + (void) printf ("File %s ", finfo->fullname); + if (vers_tag == NULL) + (void) printf ("is new; current revision %s\n", vers_head); + else if (vers_head == NULL) + { + (void) printf ("is removed; not included in "); + if (rev2 != NULL) + (void) printf ("release tag %s", rev2); + else if (date2 != NULL) + (void) printf ("release date %s", date2); + else + (void) printf ("current release"); + (void) printf ("\n"); + } + else + (void) printf ("changed from revision %s to %s\n", + vers_tag, vers_head); + return (0); + } + if ((fp1 = fopen (tmpnam (tmpfile1), "w+")) != NULL) + (void) fclose (fp1); + if ((fp2 = fopen (tmpnam (tmpfile2), "w+")) != NULL) + (void) fclose (fp2); + if ((fp3 = fopen (tmpnam (tmpfile3), "w+")) != NULL) + (void) fclose (fp3); + if (fp1 == NULL || fp2 == NULL || fp3 == NULL) + { + error (0, 0, "cannot create temporary files"); + ret = 1; + goto out; + } + if (vers_tag != NULL) + { + retcode = RCS_checkout (rcsfile->path, NULL, vers_tag, options, tmpfile1, + 0, 0); + if (retcode != 0) + { + if (!really_quiet) + error (retcode == -1 ? 1 : 0, retcode == -1 ? errno : 0, + "co of revision %s in %s failed", 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) + (void) utime (tmpfile1, &t); + } + else if (toptwo_diffs) + { + ret = 1; + goto out; + } + if (vers_head != NULL) + { + retcode = RCS_checkout (rcsfile->path, NULL, vers_head, options, tmpfile2, 0, 0); + if (retcode != 0) + { + if (!really_quiet) + error (retcode == -1 ? 1 : 0, retcode == -1 ? errno : 0, + "co of revision %s in %s failed", vers_head, rcs); + ret = 1; + goto out; + } + if ((t.actime = t.modtime = RCS_getrevtime (rcsfile, vers_head, + (char *) 0, 0)) != -1) + (void) utime (tmpfile2, &t); + } + run_setup ("%s -%c", DIFF, unidiff ? 'u' : 'c'); + run_arg (tmpfile1); + run_arg (tmpfile2); + + line1 = NULL; + line1_chars_allocated = 0; + line2 = NULL; + line2_chars_allocated = 0; + + switch (run_exec (RUN_TTY, tmpfile3, RUN_TTY, RUN_REALLY)) + { + 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. + */ + + /* Output an "Index:" line for patch to use */ + (void) fflush (stdout); + (void) printf ("Index: %s\n", finfo->fullname); + (void) fflush (stdout); + + fp = open_file (tmpfile3, "r"); + if (getline (&line1, &line1_chars_allocated, fp) < 0 || + getline (&line2, &line2_chars_allocated, fp) < 0) + { + error (0, errno, "failed to read diff file header %s for %s", + tmpfile3, rcs); + ret = 1; + (void) fclose (fp); + 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; + (void) fclose (fp); + 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; + (void) fclose (fp); + goto out; + } + } + if (CVSroot != NULL) + (void) sprintf (strippath, "%s/", CVSroot); + else + (void) strcpy (strippath, REPOS_STRIP); + if (strncmp (rcs, strippath, strlen (strippath)) == 0) + rcs += strlen (strippath); + if (vers_tag != NULL) + { + (void) sprintf (file1, "%s:%s", finfo->fullname, vers_tag); + } + else + { + (void) strcpy (file1, DEVNULL); + } + (void) sprintf (file2, "%s:%s", finfo->fullname, + vers_head ? vers_head : "removed"); + + /* Note that this prints "diff" not DIFF. The format of a diff + does not depend on the name of the program which happens to + have produced it. */ + if (unidiff) + { + (void) printf ("diff -u %s %s\n", file1, file2); + (void) printf ("--- %s%s+++ ", file1, cp1); + } + else + { + (void) printf ("diff -c %s %s\n", file1, file2); + (void) printf ("*** %s%s--- ", file1, cp1); + } + + (void) printf ("%s%s", finfo->fullname, cp2); + /* spew the rest of the diff out */ + while (getline (&line1, &line1_chars_allocated, fp) >= 0) + (void) fputs (line1, stdout); + (void) fclose (fp); + break; + default: + error (0, 0, "diff failed for %s", finfo->fullname); + } + out: + if (line1) + free (line1); + if (line2) + free (line2); + /* FIXME: should be checking for errors. */ + (void) unlink (tmpfile1); + (void) unlink (tmpfile2); + (void) unlink (tmpfile3); + return (ret); +} + +/* + * Print a warm fuzzy message + */ +/* ARGSUSED */ +static Dtype +patch_dirproc (dir, repos, update_dir) + char *dir; + char *repos; + char *update_dir; +{ + if (!quiet) + error (0, 0, "Diffing %s", update_dir); + return (R_PROCESS); +} + +/* + * Clean up temporary files + */ +static RETSIGTYPE +patch_cleanup () +{ + if (tmpfile1[0] != '\0') + (void) unlink_file (tmpfile1); + if (tmpfile2[0] != '\0') + (void) unlink_file (tmpfile2); + if (tmpfile3[0] != '\0') + (void) unlink_file (tmpfile3); +} |