/* * 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.3 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" #ifndef lint static char rcsid[] = "@(#)patch.c 1.50 92/04/10"; #endif #if __STDC__ static SIGTYPE patch_cleanup (void); static Dtype patch_dirproc (char *dir, char *repos, char *update_dir); static int patch_fileproc (char *file, char *update_dir, char *repository, List * entries, List * srcfiles); static int patch_proc (int *pargc, char *argv[], char *xwhere, char *mwhere, char *mfile, int shorten, int local_specified, char *mname, char *msg); #else static int patch_proc (); static int patch_fileproc (); static Dtype patch_dirproc (); static SIGTYPE patch_cleanup (); #endif /* __STDC__ */ 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 char *rev2 = NULL; 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 char *patch_usage[] = { "Usage: %s %s [-Qflq] [-c|-u] [-s|-t] [-V %%d]\n", " -r rev|-D date [-r rev2 | -D date2] modules...\n", "\t-Q\tReally quiet.\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 = gnu_getopt (argc, argv, "V:k:cuftsQqlRD:r:")) != -1) { switch (c) { case 'Q': really_quiet = 1; /* FALL THROUGH */ case 'q': quiet = 1; 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 (""); /* clean up if we get a signal */ (void) SIG_register (SIGHUP, patch_cleanup); (void) SIG_register (SIGINT, patch_cleanup); (void) SIG_register (SIGQUIT, patch_cleanup); (void) SIG_register (SIGPIPE, patch_cleanup); (void) SIG_register (SIGTERM, patch_cleanup); 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 = rindex (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; /* start the recursion processor */ err = start_recursion (patch_fileproc, (int (*) ()) NULL, patch_dirproc, (int (*) ()) NULL, *pargc - 1, argv + 1, local, which, 0, 1, where, 1); return (err); } /* * Called to examine a particular RCS file, as appropriate with the options * that were set above. */ /* ARGSUSED */ static int patch_fileproc (file, update_dir, repository, entries, srcfiles) char *file; char *update_dir; char *repository; List *entries; List *srcfiles; { char *vers_tag, *vers_head; char rcsspace[PATH_MAX]; char *rcs = rcsspace; Node *p; 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[MAXLINELEN], line2[MAXLINELEN]; char *cp1, *cp2, *commap; FILE *fp; /* find the parsed rcs file */ p = findnode (srcfiles, file); if (p == NULL) return (1); rcsfile = (RCSNode *) p->data; if ((rcsfile->flags & VALID) && (rcsfile->flags & INATTIC)) isattic = 1; (void) sprintf (rcs, "%s%s", 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); 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); 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 "); if (vers_tag == NULL) (void) printf ("%s is new; current revision %s\n", rcs, vers_head); else if (vers_head == NULL) (void) printf ("%s is removed; not included in release %s\n", rcs, rev2 ? rev2 : date2); else (void) printf ("%s changed from revision %s to %s\n", rcs, 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) { run_setup ("%s%s %s -p -q -r%s", Rcsbin, RCS_CO, options, vers_tag); run_arg (rcsfile->path); if ((retcode = run_exec (RUN_TTY, tmpfile1, RUN_TTY, RUN_NORMAL)) != 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; } } else if (toptwo_diffs) { ret = 1; goto out; } if (vers_head != NULL) { run_setup ("%s%s %s -p -q -r%s", Rcsbin, RCS_CO, options, vers_head); run_arg (rcsfile->path); if ((retcode = run_exec (RUN_TTY, tmpfile2, RUN_TTY, RUN_NORMAL)) != 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; } } run_setup ("%s -%c", DIFF, unidiff ? 'u' : 'c'); run_arg (tmpfile1); run_arg (tmpfile2); switch (run_exec (RUN_TTY, tmpfile3, RUN_TTY, RUN_NORMAL)) { 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. */ fp = open_file (tmpfile3, "r"); if (fgets (line1, sizeof (line1), fp) == NULL || fgets (line2, sizeof (line2), fp) == NULL) { 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 = index (line1, '\t')) == NULL || (cp2 = index (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 = index (line1, '\t')) == NULL || (cp2 = index (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); commap = rindex (rcs, ','); *commap = '\0'; if (vers_tag != NULL) { (void) sprintf (file1, "%s%s%s:%s", update_dir, update_dir[0] ? "/" : "", rcs, vers_tag); } else { (void) strcpy (file1, DEVNULL); } (void) sprintf (file2, "%s%s%s:%s", update_dir, update_dir[0] ? "/" : "", rcs, vers_head ? vers_head : "removed"); 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); } if (update_dir[0] != '\0') (void) printf ("%s/", update_dir); (void) printf ("%s%s", rcs, cp2); while (fgets (line1, sizeof (line1), fp) != NULL) (void) printf ("%s", line1); (void) fclose (fp); break; default: error (0, 0, "diff failed for %s", rcs); } out: (void) unlink_file (tmpfile1); (void) unlink_file (tmpfile2); (void) unlink_file (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 SIGTYPE 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); }