2 * Copyright (c) 1992, Brian Berliner and Jeff Polk
3 * Copyright (c) 1989-1992, Brian Berliner
5 * You may distribute under the terms of the GNU General Public License as
6 * specified in the README file that comes with the CVS source distribution.
10 * Create a Larry Wall format "patch" file between a previous release and the
11 * current head of a module, or between two releases. Can specify the
12 * release as either a date or a revision number.
21 static RETSIGTYPE patch_cleanup PROTO((void));
22 static Dtype patch_dirproc PROTO ((void *callerdat, const char *dir,
23 const char *repos, const char *update_dir,
25 static int patch_fileproc PROTO ((void *callerdat, struct file_info *finfo));
26 static int patch_proc PROTO((int argc, char **argv, char *xwhere,
27 char *mwhere, char *mfile, int shorten,
28 int local_specified, char *mname, char *msg));
30 static int force_tag_match = 1;
31 static int patch_short = 0;
32 static int toptwo_diffs = 0;
33 static char *options = NULL;
34 static char *rev1 = NULL;
35 static int rev1_validated = 0;
36 static char *rev2 = NULL;
37 static int rev2_validated = 0;
38 static char *date1 = NULL;
39 static char *date2 = NULL;
40 static char *tmpfile1 = NULL;
41 static char *tmpfile2 = NULL;
42 static char *tmpfile3 = NULL;
43 static int unidiff = 0;
45 static const char *const patch_usage[] =
47 "Usage: %s %s [-flR] [-c|-u] [-s|-t] [-V %%d]\n",
48 " -r rev|-D date [-r rev2 | -D date2] modules...\n",
49 "\t-f\tForce a head revision match if tag/date not found.\n",
50 "\t-l\tLocal directory only, not recursive\n",
51 "\t-R\tProcess directories recursively.\n",
52 "\t-c\tContext diffs (default)\n",
53 "\t-u\tUnidiff format.\n",
54 "\t-s\tShort patch - one liner per file.\n",
55 "\t-t\tTop two diffs - last change made to the file.\n",
57 "\t-r rev\tRevision - symbolic or numeric.\n",
58 "\t-V vers\tUse RCS Version \"vers\" for keyword expansion.\n",
59 "(Specify the --help global option for a list of other help options)\n",
80 while ((c = getopt (argc, argv, "+V:k:cuftsQqlRD:r:")) != -1)
87 /* The CVS 1.5 client sends these options (in addition to
88 Global_option requests), so we must ignore them. */
92 "-q or -Q must be specified before \"%s\"",
111 if (rev2 != NULL || date2 != NULL)
113 "no more than two revisions/dates can be specified");
114 if (rev1 != NULL || date1 != NULL)
115 date2 = Make_Date (optarg);
117 date1 = Make_Date (optarg);
120 if (rev2 != NULL || date2 != NULL)
122 "no more than two revisions/dates can be specified");
123 if (rev1 != NULL || date1 != NULL)
131 options = RCS_check_kflag (optarg);
134 /* This option is pretty seriously broken:
135 1. It is not clear what it does (does it change keyword
136 expansion behavior? If so, how? Or does it have
137 something to do with what version of RCS we are using?
138 Or the format we write RCS files in?).
139 2. Because both it and -k use the options variable,
140 specifying both -V and -k doesn't work.
141 3. At least as of CVS 1.9, it doesn't work (failed
142 assertion in RCS_checkout where it asserts that options
143 starts with -k). Few people seem to be complaining.
144 In the future (perhaps the near future), I have in mind
145 removing it entirely, and updating NEWS and cvs.texinfo,
146 but in case it is a good idea to give people more time
147 to complain if they would miss it, I'll just add this
148 quick and dirty error message for now. */
150 "the -V option is obsolete and should not be used");
152 if (atoi (optarg) <= 0)
153 error (1, 0, "must specify a version number to -V");
156 options = xmalloc (strlen (optarg) + 1 + 2); /* for the -V */
157 (void) sprintf (options, "-V%s", optarg);
161 unidiff = 1; /* Unidiff */
163 case 'c': /* Context diff */
179 if (toptwo_diffs && patch_short)
180 error (1, 0, "-t and -s options are mutually exclusive");
181 if (toptwo_diffs && (date1 != NULL || date2 != NULL ||
182 rev1 != NULL || rev2 != NULL))
183 error (1, 0, "must not specify revisions/dates with -t option!");
185 if (!toptwo_diffs && (date1 == NULL && date2 == NULL &&
186 rev1 == NULL && rev2 == NULL))
187 error (1, 0, "must specify at least one revision/date!");
188 if (date1 != NULL && date2 != NULL)
189 if (RCS_datecmp (date1, date2) >= 0)
190 error (1, 0, "second date must come after first date!");
192 /* if options is NULL, make it a NULL string */
194 options = xstrdup ("");
196 #ifdef CLIENT_SUPPORT
197 if (current_parsed_root->isremote)
199 /* We're the client side. Fire up the remote server. */
206 if (!force_tag_match)
216 option_with_arg ("-r", rev1);
218 client_senddate (date1);
220 option_with_arg ("-r", rev2);
222 client_senddate (date2);
223 if (options[0] != '\0')
228 for (i = 0; i < argc; ++i)
232 send_to_server ("rdiff\012", 0);
233 return get_responses_and_close ();
237 /* clean up if we get a signal */
239 (void)SIG_register (SIGABRT, patch_cleanup);
242 (void)SIG_register (SIGHUP, patch_cleanup);
245 (void)SIG_register (SIGINT, patch_cleanup);
248 (void)SIG_register (SIGQUIT, patch_cleanup);
251 (void)SIG_register (SIGPIPE, patch_cleanup);
254 (void)SIG_register (SIGTERM, patch_cleanup);
258 for (i = 0; i < argc; i++)
259 err += do_module (db, argv[i], PATCH, "Patching", patch_proc,
260 (char *)NULL, 0, local, 0, 0, (char *)NULL);
270 * callback proc for doing the real work of patching
274 patch_proc (argc, argv, xwhere, mwhere, mfile, shorten, local_specified,
292 repository = xmalloc (strlen (current_parsed_root->directory)
294 + (mfile == NULL ? 0 : strlen (mfile) + 1) + 2);
295 (void)sprintf (repository, "%s/%s",
296 current_parsed_root->directory, argv[0]);
297 where = xmalloc (strlen (argv[0]) + (mfile == NULL ? 0 : strlen (mfile) + 1)
299 (void)strcpy (where, argv[0]);
301 /* if mfile isn't null, we need to set up to do only part of the module */
307 /* if the portion of the module is a path, put the dir part on repos */
308 if ((cp = strrchr (mfile, '/')) != NULL)
311 (void)strcat (repository, "/");
312 (void)strcat (repository, mfile);
313 (void)strcat (where, "/");
314 (void)strcat (where, mfile);
318 /* take care of the rest */
319 path = xmalloc (strlen (repository) + strlen (mfile) + 2);
320 (void)sprintf (path, "%s/%s", repository, mfile);
323 /* directory means repository gets the dir tacked on */
324 (void)strcpy (repository, path);
325 (void)strcat (where, "/");
326 (void)strcat (where, mfile);
338 /* cd to the starting repository */
339 if ( CVS_CHDIR (repository) < 0)
341 error (0, errno, "cannot chdir to %s", repository);
347 which = W_REPOS | W_ATTIC;
351 if (rev1 != NULL && !rev1_validated)
353 tag_check_valid (rev1, argc - 1, argv + 1, local_specified, 0,
357 if (rev2 != NULL && !rev2_validated)
359 tag_check_valid (rev2, argc - 1, argv + 1, local_specified, 0,
364 /* start the recursion processor */
365 err = start_recursion (patch_fileproc, (FILESDONEPROC)NULL, patch_dirproc,
366 (DIRLEAVEPROC)NULL, NULL,
367 argc - 1, argv + 1, local_specified,
368 which, 0, CVS_LOCK_READ, where, 1, repository);
378 * Called to examine a particular RCS file, as appropriate with the options
379 * that were set above.
383 patch_fileproc (callerdat, finfo)
385 struct file_info *finfo;
388 char *vers_tag, *vers_head;
390 char *rcs_orig = NULL;
392 FILE *fp1, *fp2, *fp3;
400 size_t line1_chars_allocated;
401 size_t line2_chars_allocated;
407 line1_chars_allocated = 0;
409 line2_chars_allocated = 0;
410 vers_tag = vers_head = NULL;
412 /* find the parsed rcs file */
413 if ((rcsfile = finfo->rcs) == NULL)
418 if ((rcsfile->flags & VALID) && (rcsfile->flags & INATTIC))
421 rcs_orig = rcs = xmalloc (strlen (finfo->file) + sizeof (RCSEXT) + 5);
422 (void) sprintf (rcs, "%s%s", finfo->file, RCSEXT);
424 /* if vers_head is NULL, may have been removed from the release */
425 if (isattic && rev2 == NULL && date2 == NULL)
429 vers_head = RCS_getversion (rcsfile, rev2, date2, force_tag_match,
431 if (vers_head != NULL && RCS_isdead (rcsfile, vers_head))
440 if (vers_head == NULL)
447 date1 = xmalloc (MAXDATELEN);
449 if (RCS_getrevtime (rcsfile, vers_head, date1, 1) == (time_t)-1)
452 error (0, 0, "cannot find date in rcs file %s revision %s",
458 vers_tag = RCS_getversion (rcsfile, rev1, date1, force_tag_match,
460 if (vers_tag != NULL && RCS_isdead (rcsfile, vers_tag))
466 if ((vers_tag == NULL && vers_head == NULL) ||
467 (vers_tag != NULL && vers_head != NULL &&
468 strcmp (vers_head, vers_tag) == 0))
470 /* Nothing known about specified revs or
471 * not changed between releases.
477 if( patch_short && ( vers_tag == NULL || vers_head == NULL ) )
479 /* For adds & removes with a short patch requested, we can print our
480 * error message now and get out.
482 cvs_output ("File ", 0);
483 cvs_output (finfo->fullname, 0);
484 if (vers_tag == NULL)
486 cvs_output( " is new; ", 0 );
487 cvs_output( rev2 ? rev2 : date2 ? date2 : "current", 0 );
488 cvs_output( " revision ", 0 );
489 cvs_output (vers_head, 0);
490 cvs_output ("\n", 1);
494 cvs_output( " is removed; ", 0 );
495 cvs_output( rev1 ? rev1 : date1, 0 );
496 cvs_output( " revision ", 0 );
497 cvs_output( vers_tag, 0 );
498 cvs_output ("\n", 1);
504 /* Create 3 empty files. I'm not really sure there is any advantage
505 * to doing so now rather than just waiting until later.
507 * There is - cvs_temp_file opens the file so that it can guarantee that
508 * we have exclusive write access to the file. Unfortunately we spoil that
509 * by closing it and reopening it again. Of course any better solution
510 * requires that the RCS functions accept open file pointers rather than
513 if ((fp1 = cvs_temp_file (&tmpfile1)) == NULL)
515 error (0, errno, "cannot create temporary file %s", tmpfile1);
520 if (fclose (fp1) < 0)
521 error (0, errno, "warning: cannot close %s", tmpfile1);
522 if ((fp2 = cvs_temp_file (&tmpfile2)) == NULL)
524 error (0, errno, "cannot create temporary file %s", tmpfile2);
529 if (fclose (fp2) < 0)
530 error (0, errno, "warning: cannot close %s", tmpfile2);
531 if ((fp3 = cvs_temp_file (&tmpfile3)) == NULL)
533 error (0, errno, "cannot create temporary file %s", tmpfile3);
538 if (fclose (fp3) < 0)
539 error (0, errno, "warning: cannot close %s", tmpfile3);
541 if (vers_tag != NULL)
543 retcode = RCS_checkout (rcsfile, (char *)NULL, vers_tag,
544 rev1, options, tmpfile1,
545 (RCSCHECKOUTPROC)NULL, (void *)NULL);
549 "cannot check out revision %s of %s", vers_tag, rcs);
553 memset ((char *) &t, 0, sizeof (t));
554 if ((t.actime = t.modtime = RCS_getrevtime (rcsfile, vers_tag,
555 (char *) 0, 0)) != -1)
556 /* I believe this timestamp only affects the dates in our diffs,
557 and therefore should be on the server, not the client. */
558 (void) utime (tmpfile1, &t);
560 else if (toptwo_diffs)
565 if (vers_head != NULL)
567 retcode = RCS_checkout (rcsfile, (char *)NULL, vers_head,
568 rev2, options, tmpfile2,
569 (RCSCHECKOUTPROC)NULL, (void *)NULL);
573 "cannot check out revision %s of %s", vers_head, rcs);
577 if ((t.actime = t.modtime = RCS_getrevtime (rcsfile, vers_head,
578 (char *)0, 0)) != -1)
579 /* I believe this timestamp only affects the dates in our diffs,
580 and therefore should be on the server, not the client. */
581 (void)utime (tmpfile2, &t);
584 switch (diff_exec (tmpfile1, tmpfile2, NULL, NULL, unidiff ? "-u" : "-c",
587 case -1: /* fork/wait failure */
588 error (1, errno, "fork for diff failed on %s", rcs);
590 case 0: /* nothing to do */
594 * The two revisions are really different, so read the first two
595 * lines of the diff output file, and munge them to include more
596 * reasonable file names that "patch" will understand, unless the
597 * user wanted a short patch. In that case, just output the short
602 cvs_output ("File ", 0);
603 cvs_output (finfo->fullname, 0);
604 cvs_output (" changed from revision ", 0);
605 cvs_output (vers_tag, 0);
606 cvs_output (" to ", 0);
607 cvs_output (vers_head, 0);
608 cvs_output ("\n", 1);
613 /* Output an "Index:" line for patch to use */
614 cvs_output ("Index: ", 0);
615 cvs_output (finfo->fullname, 0);
616 cvs_output ("\n", 1);
618 /* Now the munging. */
619 fp = open_file (tmpfile3, "r");
620 if (getline (&line1, &line1_chars_allocated, fp) < 0 ||
621 getline (&line2, &line2_chars_allocated, fp) < 0)
625 failed to read diff file header %s for %s: end of file", tmpfile3, rcs);
628 "failed to read diff file header %s for %s",
632 error (0, errno, "error closing %s", tmpfile3);
637 if (strncmp (line1, "*** ", 4) != 0 ||
638 strncmp (line2, "--- ", 4) != 0 ||
639 (cp1 = strchr (line1, '\t')) == NULL ||
640 (cp2 = strchr (line2, '\t')) == NULL)
642 error (0, 0, "invalid diff header for %s", rcs);
645 error (0, errno, "error closing %s", tmpfile3);
651 if (strncmp (line1, "--- ", 4) != 0 ||
652 strncmp (line2, "+++ ", 4) != 0 ||
653 (cp1 = strchr (line1, '\t')) == NULL ||
654 (cp2 = strchr (line2, '\t')) == NULL)
656 error (0, 0, "invalid unidiff header for %s", rcs);
659 error (0, errno, "error closing %s", tmpfile3);
663 assert (current_parsed_root != NULL);
664 assert (current_parsed_root->directory != NULL);
666 strippath = xmalloc (strlen (current_parsed_root->directory)
668 (void)sprintf (strippath, "%s/",
669 current_parsed_root->directory);
672 strippath = xstrdup (REPOS_STRIP); */
673 if (strncmp (rcs, strippath, strlen (strippath)) == 0)
674 rcs += strlen (strippath);
676 if (vers_tag != NULL)
678 file1 = xmalloc (strlen (finfo->fullname)
681 (void)sprintf (file1, "%s:%s", finfo->fullname, vers_tag);
685 file1 = xstrdup (DEVNULL);
687 file2 = xmalloc (strlen (finfo->fullname)
688 + (vers_head != NULL ? strlen (vers_head) : 10)
690 (void)sprintf (file2, "%s:%s", finfo->fullname,
691 vers_head ? vers_head : "removed");
693 /* Note that the string "diff" is specified by POSIX (for -c)
694 and is part of the diff output format, not the name of a
698 cvs_output ("diff -u ", 0);
699 cvs_output (file1, 0);
701 cvs_output (file2, 0);
702 cvs_output ("\n", 1);
704 cvs_output ("--- ", 0);
705 cvs_output (file1, 0);
707 cvs_output ("+++ ", 0);
711 cvs_output ("diff -c ", 0);
712 cvs_output (file1, 0);
714 cvs_output (file2, 0);
715 cvs_output ("\n", 1);
717 cvs_output ("*** ", 0);
718 cvs_output (file1, 0);
720 cvs_output ("--- ", 0);
723 cvs_output (finfo->fullname, 0);
726 /* spew the rest of the diff out */
728 = getline (&line1, &line1_chars_allocated, fp))
730 cvs_output (line1, 0);
731 if (line_length < 0 && !feof (fp))
732 error (0, errno, "cannot read %s", tmpfile3);
735 error (0, errno, "cannot close %s", tmpfile3);
740 error (0, 0, "diff failed for %s", finfo->fullname);
747 if (CVS_UNLINK (tmpfile1) < 0)
748 error (0, errno, "cannot unlink %s", tmpfile1);
749 if (CVS_UNLINK (tmpfile2) < 0)
750 error (0, errno, "cannot unlink %s", tmpfile2);
751 if (CVS_UNLINK (tmpfile3) < 0)
752 error (0, errno, "cannot unlink %s", tmpfile3);
756 tmpfile1 = tmpfile2 = tmpfile3 = NULL;
759 if (vers_tag != NULL)
761 if (vers_head != NULL)
771 * Print a warm fuzzy message
775 patch_dirproc (callerdat, dir, repos, update_dir, entries)
779 const char *update_dir;
783 error (0, 0, "Diffing %s", update_dir);
788 * Clean up temporary files
793 /* Note that the checks for existence_error are because we are
794 called from a signal handler, without SIG_begincrsect, so
795 we don't know whether the files got created. */
797 if (tmpfile1 != NULL)
799 if (unlink_file (tmpfile1) < 0
800 && !existence_error (errno))
801 error (0, errno, "cannot remove %s", tmpfile1);
804 if (tmpfile2 != NULL)
806 if (unlink_file (tmpfile2) < 0
807 && !existence_error (errno))
808 error (0, errno, "cannot remove %s", tmpfile2);
811 if (tmpfile3 != NULL)
813 if (unlink_file (tmpfile3) < 0
814 && !existence_error (errno))
815 error (0, errno, "cannot remove %s", tmpfile3);
818 tmpfile1 = tmpfile2 = tmpfile3 = NULL;