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.
19 static RETSIGTYPE patch_cleanup PROTO((void));
20 static Dtype patch_dirproc PROTO ((void *callerdat, const char *dir,
21 const char *repos, const char *update_dir,
23 static int patch_fileproc PROTO ((void *callerdat, struct file_info *finfo));
24 static int patch_proc PROTO((int argc, char **argv, char *xwhere,
25 char *mwhere, char *mfile, int shorten,
26 int local_specified, char *mname, char *msg));
28 static int force_tag_match = 1;
29 static int patch_short = 0;
30 static int toptwo_diffs = 0;
31 static char *options = NULL;
32 static char *rev1 = NULL;
33 static int rev1_validated = 0;
34 static char *rev2 = NULL;
35 static int rev2_validated = 0;
36 static char *date1 = NULL;
37 static char *date2 = NULL;
38 static char *tmpfile1 = NULL;
39 static char *tmpfile2 = NULL;
40 static char *tmpfile3 = NULL;
41 static int unidiff = 0;
43 static const char *const patch_usage[] =
45 "Usage: %s %s [-flR] [-c|-u] [-s|-t] [-V %%d]\n",
46 " -r rev|-D date [-r rev2 | -D date2] modules...\n",
47 "\t-f\tForce a head revision match if tag/date not found.\n",
48 "\t-l\tLocal directory only, not recursive\n",
49 "\t-R\tProcess directories recursively.\n",
50 "\t-c\tContext diffs (default)\n",
51 "\t-u\tUnidiff format.\n",
52 "\t-s\tShort patch - one liner per file.\n",
53 "\t-t\tTop two diffs - last change made to the file.\n",
55 "\t-r rev\tRevision - symbolic or numeric.\n",
56 "\t-V vers\tUse RCS Version \"vers\" for keyword expansion.\n",
57 "(Specify the --help global option for a list of other help options)\n",
78 while ((c = getopt (argc, argv, "+V:k:cuftsQqlRD:r:")) != -1)
85 /* The CVS 1.5 client sends these options (in addition to
86 Global_option requests), so we must ignore them. */
90 "-q or -Q must be specified before \"%s\"",
109 if (rev2 != NULL || date2 != NULL)
111 "no more than two revisions/dates can be specified");
112 if (rev1 != NULL || date1 != NULL)
113 date2 = Make_Date (optarg);
115 date1 = Make_Date (optarg);
118 if (rev2 != NULL || date2 != NULL)
120 "no more than two revisions/dates can be specified");
121 if (rev1 != NULL || date1 != NULL)
129 options = RCS_check_kflag (optarg);
132 /* This option is pretty seriously broken:
133 1. It is not clear what it does (does it change keyword
134 expansion behavior? If so, how? Or does it have
135 something to do with what version of RCS we are using?
136 Or the format we write RCS files in?).
137 2. Because both it and -k use the options variable,
138 specifying both -V and -k doesn't work.
139 3. At least as of CVS 1.9, it doesn't work (failed
140 assertion in RCS_checkout where it asserts that options
141 starts with -k). Few people seem to be complaining.
142 In the future (perhaps the near future), I have in mind
143 removing it entirely, and updating NEWS and cvs.texinfo,
144 but in case it is a good idea to give people more time
145 to complain if they would miss it, I'll just add this
146 quick and dirty error message for now. */
148 "the -V option is obsolete and should not be used");
150 if (atoi (optarg) <= 0)
151 error (1, 0, "must specify a version number to -V");
154 options = xmalloc (strlen (optarg) + 1 + 2); /* for the -V */
155 (void) sprintf (options, "-V%s", optarg);
159 unidiff = 1; /* Unidiff */
161 case 'c': /* Context diff */
177 if (toptwo_diffs && patch_short)
178 error (1, 0, "-t and -s options are mutually exclusive");
179 if (toptwo_diffs && (date1 != NULL || date2 != NULL ||
180 rev1 != NULL || rev2 != NULL))
181 error (1, 0, "must not specify revisions/dates with -t option!");
183 if (!toptwo_diffs && (date1 == NULL && date2 == NULL &&
184 rev1 == NULL && rev2 == NULL))
185 error (1, 0, "must specify at least one revision/date!");
186 if (date1 != NULL && date2 != NULL)
187 if (RCS_datecmp (date1, date2) >= 0)
188 error (1, 0, "second date must come after first date!");
190 /* if options is NULL, make it a NULL string */
192 options = xstrdup ("");
194 #ifdef CLIENT_SUPPORT
195 if (current_parsed_root->isremote)
197 /* We're the client side. Fire up the remote server. */
204 if (!force_tag_match)
214 option_with_arg ("-r", rev1);
216 client_senddate (date1);
218 option_with_arg ("-r", rev2);
220 client_senddate (date2);
221 if (options[0] != '\0')
226 for (i = 0; i < argc; ++i)
230 send_to_server ("rdiff\012", 0);
231 return get_responses_and_close ();
235 /* clean up if we get a signal */
237 (void)SIG_register (SIGABRT, patch_cleanup);
240 (void)SIG_register (SIGHUP, patch_cleanup);
243 (void)SIG_register (SIGINT, patch_cleanup);
246 (void)SIG_register (SIGQUIT, patch_cleanup);
249 (void)SIG_register (SIGPIPE, patch_cleanup);
252 (void)SIG_register (SIGTERM, patch_cleanup);
256 for (i = 0; i < argc; i++)
257 err += do_module (db, argv[i], PATCH, "Patching", patch_proc,
258 (char *)NULL, 0, local, 0, 0, (char *)NULL);
268 * callback proc for doing the real work of patching
272 patch_proc (argc, argv, xwhere, mwhere, mfile, shorten, local_specified,
290 repository = xmalloc (strlen (current_parsed_root->directory)
292 + (mfile == NULL ? 0 : strlen (mfile) + 1) + 2);
293 (void)sprintf (repository, "%s/%s",
294 current_parsed_root->directory, argv[0]);
295 where = xmalloc (strlen (argv[0]) + (mfile == NULL ? 0 : strlen (mfile) + 1)
297 (void)strcpy (where, argv[0]);
299 /* if mfile isn't null, we need to set up to do only part of the module */
305 /* if the portion of the module is a path, put the dir part on repos */
306 if ((cp = strrchr (mfile, '/')) != NULL)
309 (void)strcat (repository, "/");
310 (void)strcat (repository, mfile);
311 (void)strcat (where, "/");
312 (void)strcat (where, mfile);
316 /* take care of the rest */
317 path = xmalloc (strlen (repository) + strlen (mfile) + 2);
318 (void)sprintf (path, "%s/%s", repository, mfile);
321 /* directory means repository gets the dir tacked on */
322 (void)strcpy (repository, path);
323 (void)strcat (where, "/");
324 (void)strcat (where, mfile);
336 /* cd to the starting repository */
337 if ( CVS_CHDIR (repository) < 0)
339 error (0, errno, "cannot chdir to %s", repository);
345 which = W_REPOS | W_ATTIC;
349 if (rev1 != NULL && !rev1_validated)
351 tag_check_valid (rev1, argc - 1, argv + 1, local_specified, 0,
355 if (rev2 != NULL && !rev2_validated)
357 tag_check_valid (rev2, argc - 1, argv + 1, local_specified, 0,
362 /* start the recursion processor */
363 err = start_recursion (patch_fileproc, (FILESDONEPROC)NULL, patch_dirproc,
364 (DIRLEAVEPROC)NULL, NULL,
365 argc - 1, argv + 1, local_specified,
366 which, 0, CVS_LOCK_READ, where, 1, repository);
376 * Called to examine a particular RCS file, as appropriate with the options
377 * that were set above.
381 patch_fileproc (callerdat, finfo)
383 struct file_info *finfo;
386 char *vers_tag, *vers_head;
389 FILE *fp1, *fp2, *fp3;
397 size_t line1_chars_allocated;
398 size_t line2_chars_allocated;
404 line1_chars_allocated = 0;
406 line2_chars_allocated = 0;
407 vers_tag = vers_head = NULL;
409 /* find the parsed rcs file */
410 if ((rcsfile = finfo->rcs) == NULL)
415 if ((rcsfile->flags & VALID) && (rcsfile->flags & INATTIC))
418 rcs = xmalloc (strlen (finfo->file) + sizeof (RCSEXT) + 5);
419 (void) sprintf (rcs, "%s%s", finfo->file, RCSEXT);
421 /* if vers_head is NULL, may have been removed from the release */
422 if (isattic && rev2 == NULL && date2 == NULL)
426 vers_head = RCS_getversion (rcsfile, rev2, date2, force_tag_match,
428 if (vers_head != NULL && RCS_isdead (rcsfile, vers_head))
437 if (vers_head == NULL)
444 date1 = xmalloc (MAXDATELEN);
446 if (RCS_getrevtime (rcsfile, vers_head, date1, 1) == (time_t)-1)
449 error (0, 0, "cannot find date in rcs file %s revision %s",
455 vers_tag = RCS_getversion (rcsfile, rev1, date1, force_tag_match,
457 if (vers_tag != NULL && RCS_isdead (rcsfile, vers_tag))
463 if ((vers_tag == NULL && vers_head == NULL) ||
464 (vers_tag != NULL && vers_head != NULL &&
465 strcmp (vers_head, vers_tag) == 0))
467 /* Nothing known about specified revs or
468 * not changed between releases.
474 if( patch_short && ( vers_tag == NULL || vers_head == NULL ) )
476 /* For adds & removes with a short patch requested, we can print our
477 * error message now and get out.
479 cvs_output ("File ", 0);
480 cvs_output (finfo->fullname, 0);
481 if (vers_tag == NULL)
483 cvs_output( " is new; ", 0 );
484 cvs_output( rev2 ? rev2 : date2 ? date2 : "current", 0 );
485 cvs_output( " revision ", 0 );
486 cvs_output (vers_head, 0);
487 cvs_output ("\n", 1);
491 cvs_output( " is removed; ", 0 );
492 cvs_output( rev1 ? rev1 : date1, 0 );
493 cvs_output( " revision ", 0 );
494 cvs_output( vers_tag, 0 );
495 cvs_output ("\n", 1);
501 /* Create 3 empty files. I'm not really sure there is any advantage
502 * to doing so now rather than just waiting until later.
504 * There is - cvs_temp_file opens the file so that it can guarantee that
505 * we have exclusive write access to the file. Unfortunately we spoil that
506 * by closing it and reopening it again. Of course any better solution
507 * requires that the RCS functions accept open file pointers rather than
510 if ((fp1 = cvs_temp_file (&tmpfile1)) == NULL)
512 error (0, errno, "cannot create temporary file %s", tmpfile1);
517 if (fclose (fp1) < 0)
518 error (0, errno, "warning: cannot close %s", tmpfile1);
519 if ((fp2 = cvs_temp_file (&tmpfile2)) == NULL)
521 error (0, errno, "cannot create temporary file %s", tmpfile2);
526 if (fclose (fp2) < 0)
527 error (0, errno, "warning: cannot close %s", tmpfile2);
528 if ((fp3 = cvs_temp_file (&tmpfile3)) == NULL)
530 error (0, errno, "cannot create temporary file %s", tmpfile3);
535 if (fclose (fp3) < 0)
536 error (0, errno, "warning: cannot close %s", tmpfile3);
538 if (vers_tag != NULL)
540 retcode = RCS_checkout (rcsfile, (char *)NULL, vers_tag,
541 rev1, options, tmpfile1,
542 (RCSCHECKOUTPROC)NULL, (void *)NULL);
546 "cannot check out revision %s of %s", vers_tag, rcs);
550 memset ((char *) &t, 0, sizeof (t));
551 if ((t.actime = t.modtime = RCS_getrevtime (rcsfile, vers_tag,
552 (char *) 0, 0)) != -1)
553 /* I believe this timestamp only affects the dates in our diffs,
554 and therefore should be on the server, not the client. */
555 (void) utime (tmpfile1, &t);
557 else if (toptwo_diffs)
562 if (vers_head != NULL)
564 retcode = RCS_checkout (rcsfile, (char *)NULL, vers_head,
565 rev2, options, tmpfile2,
566 (RCSCHECKOUTPROC)NULL, (void *)NULL);
570 "cannot check out revision %s of %s", vers_head, rcs);
574 if ((t.actime = t.modtime = RCS_getrevtime (rcsfile, vers_head,
575 (char *)0, 0)) != -1)
576 /* I believe this timestamp only affects the dates in our diffs,
577 and therefore should be on the server, not the client. */
578 (void)utime (tmpfile2, &t);
581 switch (diff_exec (tmpfile1, tmpfile2, NULL, NULL, unidiff ? "-u" : "-c",
584 case -1: /* fork/wait failure */
585 error (1, errno, "fork for diff failed on %s", rcs);
587 case 0: /* nothing to do */
591 * The two revisions are really different, so read the first two
592 * lines of the diff output file, and munge them to include more
593 * reasonable file names that "patch" will understand, unless the
594 * user wanted a short patch. In that case, just output the short
599 cvs_output ("File ", 0);
600 cvs_output (finfo->fullname, 0);
601 cvs_output (" changed from revision ", 0);
602 cvs_output (vers_tag, 0);
603 cvs_output (" to ", 0);
604 cvs_output (vers_head, 0);
605 cvs_output ("\n", 1);
610 /* Output an "Index:" line for patch to use */
611 cvs_output ("Index: ", 0);
612 cvs_output (finfo->fullname, 0);
613 cvs_output ("\n", 1);
615 /* Now the munging. */
616 fp = open_file (tmpfile3, "r");
617 if (getline (&line1, &line1_chars_allocated, fp) < 0 ||
618 getline (&line2, &line2_chars_allocated, fp) < 0)
622 failed to read diff file header %s for %s: end of file", tmpfile3, rcs);
625 "failed to read diff file header %s for %s",
629 error (0, errno, "error closing %s", tmpfile3);
634 if (strncmp (line1, "*** ", 4) != 0 ||
635 strncmp (line2, "--- ", 4) != 0 ||
636 (cp1 = strchr (line1, '\t')) == NULL ||
637 (cp2 = strchr (line2, '\t')) == NULL)
639 error (0, 0, "invalid diff header for %s", rcs);
642 error (0, errno, "error closing %s", tmpfile3);
648 if (strncmp (line1, "--- ", 4) != 0 ||
649 strncmp (line2, "+++ ", 4) != 0 ||
650 (cp1 = strchr (line1, '\t')) == NULL ||
651 (cp2 = strchr (line2, '\t')) == NULL)
653 error (0, 0, "invalid unidiff header for %s", rcs);
656 error (0, errno, "error closing %s", tmpfile3);
660 assert (current_parsed_root != NULL);
661 assert (current_parsed_root->directory != NULL);
663 strippath = xmalloc (strlen (current_parsed_root->directory)
665 (void)sprintf (strippath, "%s/",
666 current_parsed_root->directory);
669 strippath = xstrdup (REPOS_STRIP); */
670 if (strncmp (rcs, strippath, strlen (strippath)) == 0)
671 rcs += strlen (strippath);
673 if (vers_tag != NULL)
675 file1 = xmalloc (strlen (finfo->fullname)
678 (void)sprintf (file1, "%s:%s", finfo->fullname, vers_tag);
682 file1 = xstrdup (DEVNULL);
684 file2 = xmalloc (strlen (finfo->fullname)
685 + (vers_head != NULL ? strlen (vers_head) : 10)
687 (void)sprintf (file2, "%s:%s", finfo->fullname,
688 vers_head ? vers_head : "removed");
690 /* Note that the string "diff" is specified by POSIX (for -c)
691 and is part of the diff output format, not the name of a
695 cvs_output ("diff -u ", 0);
696 cvs_output (file1, 0);
698 cvs_output (file2, 0);
699 cvs_output ("\n", 1);
701 cvs_output ("--- ", 0);
702 cvs_output (file1, 0);
704 cvs_output ("+++ ", 0);
708 cvs_output ("diff -c ", 0);
709 cvs_output (file1, 0);
711 cvs_output (file2, 0);
712 cvs_output ("\n", 1);
714 cvs_output ("*** ", 0);
715 cvs_output (file1, 0);
717 cvs_output ("--- ", 0);
720 cvs_output (finfo->fullname, 0);
723 /* spew the rest of the diff out */
725 = getline (&line1, &line1_chars_allocated, fp))
727 cvs_output (line1, 0);
728 if (line_length < 0 && !feof (fp))
729 error (0, errno, "cannot read %s", tmpfile3);
732 error (0, errno, "cannot close %s", tmpfile3);
737 error (0, 0, "diff failed for %s", finfo->fullname);
744 if (CVS_UNLINK (tmpfile1) < 0)
745 error (0, errno, "cannot unlink %s", tmpfile1);
746 if (CVS_UNLINK (tmpfile2) < 0)
747 error (0, errno, "cannot unlink %s", tmpfile2);
748 if (CVS_UNLINK (tmpfile3) < 0)
749 error (0, errno, "cannot unlink %s", tmpfile3);
753 tmpfile1 = tmpfile2 = tmpfile3 = NULL;
756 if (vers_tag != NULL)
758 if (vers_head != NULL)
768 * Print a warm fuzzy message
772 patch_dirproc (callerdat, dir, repos, update_dir, entries)
776 const char *update_dir;
780 error (0, 0, "Diffing %s", update_dir);
785 * Clean up temporary files
790 /* Note that the checks for existence_error are because we are
791 called from a signal handler, without SIG_begincrsect, so
792 we don't know whether the files got created. */
794 if (tmpfile1 != NULL)
796 if (unlink_file (tmpfile1) < 0
797 && !existence_error (errno))
798 error (0, errno, "cannot remove %s", tmpfile1);
801 if (tmpfile2 != NULL)
803 if (unlink_file (tmpfile2) < 0
804 && !existence_error (errno))
805 error (0, errno, "cannot remove %s", tmpfile2);
808 if (tmpfile3 != NULL)
810 if (unlink_file (tmpfile3) < 0
811 && !existence_error (errno))
812 error (0, errno, "cannot remove %s", tmpfile3);
815 tmpfile1 = tmpfile2 = tmpfile3 = NULL;